i have a view that shows the user a form and the user should upload a file and choose all the categories associated with it.
the controller that is responsible in submitting the data should
retrieve the file info and
insert data in the file category
retrieve the related category ids and
insert them as well in the table
that is abstracted by the EF just
insert the file and the category ids.
this is my problem the controller just gets some info about the category not all of it. basically it only needs the ids for the insertion
i can't use
[HttpPost]
public ActionResult SaveFile(File file, List<Category> Checkbox, HttpPostedFileBase FileUpload)
{
//some stuff
//for example got the first category and named it to category1
file.Categories.Add(category1)
}
i asked someone and he told me you have to select the category you want to insert
is this really necessary ? i only need a category id and a file id to make the insert why would i fire another request to the database that i don't really need
i am using
EF 4
MVC 3
It is better to select category first because it will save you a lot of possible problems but it is not necessary. You can use dummy category object:
var category = new Category { Id = receivedId };
file.Categories.Add(category);
You will only create new category and you will set its PK. Now you need to handle file insertion where you must explicitly instruct ObjectContext to insert only file (because your categories exists in database):
context.Files.Attach(file); // now whole object graph is attached but marked as Unchanged
context.ObjectStateManager.ChangeObjectState(file, EntityState.Added); // mark only file entity as inserted
context.SaveChanges();
You can also take opposite direction:
context.Files.AddObject(file); // all objects in object graph are marked for insertion
foreach (var category in file.Categories)
{
// you don't want to insert categories again
context.ObjectStateManager.ChangeObjectState(category, EntityState.Unchanged);
}
context.SaveChanges();
This scenario works if you know that all categories exist in your database. If you want to insert new categories together with saving file you will need to query categories first or add some information about which category is new and which is existing.
Related
I am new to entity framework and I am trying to get my head around it. I am used to writing stored procedures which have all the data I need on a example by example basis.
I am under the impression that I can get all values from a particular table including the foreign key values direct using entity framework without having to write a select query which joins the data.
I have the following in my controller
public ActionResult Patient()
{
using (var context = new WaysToWellnessDB())
{
var patients = context.Patients.ToList();
return View(patients);
}
}
In my view I have the following
#foreach (var item in Model)
{
<p>
#item.FirstName #item.Surname #item.Gender.GenderDesc
</p>
}
I have two tables, Patient and Gender, GenderId is a foreign key which I am trying to get the GenderDesc from that table.
I am getting the following message
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
Can someone explain why I cannot access GenderDesc. It does work if I remove the using() around my context, but I don't really want to leave that open, is there a way to get this to work still having the using around?
Thanks in advance.
Correct, you have disposed of the context as it is within a using statement, so anything you try to access from then on will not be able to be lazy loaded. The disadvantage with lazy loading is that it will perform a query for the gender for every patient you are iterating over, which is handy, but bad! I would load the related table at query time using Include.
You'll need a new import:
using System.Data.Entity;
And then include the related table:
var patients = context.Patients.Include(p => p.Gender).ToList();
That will result in a query which will join to your "Gender" table and you should be able to output item.Gender.GenderDesc in your view.
I have 2 classes, like the below.
They can have very large collections - a Website may have 2,000+ WebsitePages and vice-versa.
class WebsitePage
{
public int ID {get;set;}
public string Title {get;set;}
public List<Website> Websites {get;set;}
}
class Website
{
public int ID {get;set;}
public string Title {get;set;}
public List<WebsitePage> WebsitePages {get;set;}
}
I am having trouble removing a WebsitePage from a Website. Particularly when removing a WebsitePage from mutliple Websites.
For example, I might have code like this:
var pageToRemove = db.WebsitePages.FirstOrDefault();
var websites = db.Websites.Include(i => i.WebsitePages).ToList();
foreach(var website in websites)
{
website.WebsitePages.Remove(pageToRemove)
}
If each website Include() 2k pages, you can imagine it takes ages to load that second line.
But if I don't Include() the WebsitePages when fetching the Websites, there is no child collection loaded for me to delete from.
I have tried to just Include() the pages that I need to delete, but of course when saving that gives me an empty collection.
Is there a recommended or better way to approach this?
I am working with an existing MVC site and I would rather not have to create an entity class for the join table unless absolutely necessary.
No, you can't... normally.
A many-to-many relationship (with a hidden junction table) can only be affected by adding/removing items in the nested collections. And for this the collections must be loaded.
But there are some options.
Option 1.
Delete data from the junction table by raw SQL. Basically this looks like
context.Database.ExecuteSqlCommand(
"DELETE FROM WebsiteWebsitePage WHERE WebsiteID = x AND WebsitePageID = y"));
(not using parameters).
Option 2.
Include the junction into the class model, i.e. map the junction table to a class WebsiteWebsitePage. Both Website and WebsitePage will now have
public ICollection<WebsiteWebsitePage> WebsiteWebsitePages { get; set; }
and WebsiteWebsitePage will have reference properties to both Website and WebsitePage. Now you can manipulate the junctions directly through the class model.
I consider this the best option, because everything happens the standard way of working with entities with validations and tracking and all. Also, chances are that sooner or later you will need an explicit junction class because you're going to want to add more data to it.
Option 3.
The box of tricks.
I tried to do this by removing a stub entity from the collection. In your case: create a WebsitePage object with a valid primary key value and remove it from Website.WebsitePages without loading the collection. But EF doesn't notice the change because it isn't tracking Website.WebsitePages, and the item is not in the collection to begin with.
But this made me realize I had to make EF track a Website.WebsitePages collection with 1 item in it and then remove that item. I got this working by first building the Website item and then attaching it to a new context. I'll show the code I used (a standard Product - Category model) to prevent typos.
Product prd;
// Step 1: build an object with 1 item in its collection
Category cat = new Category { Id = 3 }; // Stub entity
using(var db = new ProdCatContext())
{
db.Configuration.LazyLoadingEnabled = false;
prd = db.Products.First();
prd.Categories.Add(cat);
}
// Step 2: attach to a new context and remove the category.
using(var db = new ProdCatContext())
{
db.Configuration.LazyLoadingEnabled = false;
db.Products.Attach(prd);
prd.Categories.Remove(cat);
db.SaveChanges(); // Deletes the junction record.
}
Lazy loading is disabled, otherwise the Categories would still be loaded when prd.Categories is addressed.
My interpretation of what happens here is: In the second step, EF not only starts tracking the product when you attach it, but also its associations, because it 'knows' you can't load these associations yourself in a many to many relationship. It doesn't do this, however, when you add the category in the first step.
In my model I have two classes Categories and Products. There is a relation many- to many between them.
I set states of all categories on modified manually and when I watched in the debugger before saveChanges() I saw that all of these categories were marked as modified. But after request mapping between categories and product weren't updated in my database. Code of update function.
public void UpdateProduct(Product product)
{
using (EFDbContext context = new EFDbContext())
{
context.Products.Attach(product);
if (product.Categories != null)
{
foreach (var item in product.Categories)
{
context.Entry(item).State = EntityState.Modified;
}
}
context.Entry(product).State = EntityState.Modified;
context.SaveChanges();
}
}
Setting entity to modified says that you have changed its properties (not navigation properties) and you want to save them. If you changed relations (navigation properties) by for example creating new relation between existing product and category or removing relation between existing product and category setting state to modified will not help you. This is actually very hard to solve (it is same in all current EF versions) because that relation has its own state which must be set and state of relation cannot be Modified = you must know if you added or removed relation. Especially removing is hard because you probably don't have information about relations you have removed from Categories navigation property when you are going to attach entity to the context. Moreover DbContext doesn't offer access to state of the relation so you must convert it to ObjectContext and use ObjectStateManager.
The easiest way to solve this issue is to load product with categories from database prior to saving and manually synchronize your detached object graph (the one you are trying to save at the moment) with loaded attached graph. Once you synchronize all changes in attached graph you will save it back to database. Attached graph will know which relations to categories were added or removed.
I'm new to EF 4.1 and try to move some code from to EF 4.1 (code first). Here is some background. I'm managing products for several companies, so I have a table "Product" with a column called "Company". I need to update (insert if not exist, otherwise update) this table from an Excel file containing products for a given company(let's say C1). Here is what I'm doing (using proprietary db access code and LINQ) :
Retrieve all products for company C1 as a list of products
For each product appearing in Excel:
Search the loaded list of products if the product from Excel exists already
If product does not exist then :
create new product instance
add product to database
add product to the list of loaded products
Else
update product in database
That's pretty straightforward but converting it to EF 4.1 does not seem that easy:
I can easily retrieve and filter all products from the context. I can also easily search for the Excel product in the loaded list. If not present, I can create the product and add it to the context. But how to mimic my "caching" system where I add the newly added product to my list of product loaded in memory (Excel file can contains several times the same product) ? Also when change the entity state and when to do savechanges ?
Christian
Here is a rough outline of how you can do this.
EF caches the loaded entities. You can use Local property to access it. This collection will be modified by EF when you add new entities.
context.Products.Where(p => p.Company == "C1").ToList();//force EF to load products
while(/*loop through excel file)
{
var product = context.Products.Local.Where(p=> p.Id == excelProductId);
if (product == null)
{
//insert
}
else
{
/update
}
}
context.SaveChanges();
I am trying to add a new comment to a comment table but all records in the table are being deleted with the exception of the one I added.
As an example: Let say I have an existing comment in the database for customer 1. I want to add a new comment.
In my controller I have the following:
List<CustomerComment> comments = _commentsRepository.CustomerComments.ToList();
CustomerComment newComment = new CustomerComment()
{
CustId = 1,
RevisionNumber = revNumber,
Comment = comment,
Customer = _commentRespository.GetCustById(1),
CommentDate = DateTime.Now,
UserId = 24,
Users = _commentsRepository.GetUserById(24)
};
comments.Add(newComment);
_commentsRepository.SaveComment();
In my repository I have the following:
public Int32 SaveComment(CustomerComment comment)
{
try
{
_DB.SubmitChanges();
}
catch
{
throw;
}
return comment.CommentId;
}
While stepping through I see no changes to the data until after I create the new comment and step into the SaveComment method. What is strange is that it shows the comments already in the table for Delete and the new comment for insert.
Not understanding why it thinks the existing comments should be deleted.
I have also tried InsertOnSubmit but it does the samething so I took it out.
One thing I have noticed is that the existing comment after loading in the controller (comments) has the customer object as null. When I create the new comment I am assigning the customer to the new comment (Customer = _commentRespository.GetCustById(1).) Is this causing the delete and why doesn't the object get created and assigned when loaded.
Some additional information is that I am using POCOs and an XML mapping file.
Maybe you should not add the comment to an in memory storage try adding the new comment to the data context instead. I am presuming in your repository you have the add method... So something like _commentsRepository.add(newComment); shoukd work...
Regardless of that, why are you storing the whole customer in the database and for that matter user? you should be storing only their ids no? when you need read onky data to be thrown out into the view you may require additional data such as the customer and user details, use a dto object for that. Persistance in one thing, viewing data with certain data possibly populated from various tables is another thing...