I have a MVC web App with EF (code first).
In my "import" function, I want to add a list of items into my DB (DbContext item with DbSets).
With a "foreach" loop, I try to add each item to DB and save.
public static void AddItem(Item item)
{
ItemService.AddItem(item);
UOW.Commit();
}
and -
//UOW
public void Commit()
{
m_context.SaveChanges();
}
Since maybe some of the items already exist in the DB, I put "AddItem" in "try-catch" block, so if it fails to add the item - it will continue to the next item.
The problem is that it works well as long as the items are not in the DB. If there is an item that already exist - I get an error that it can't add a duplicate key, and than I get this error for all the next items (also items that does not exist in DB)!
Could it be that your context is still the same when you get to the next Commit? Because then your newly added item would still be there (it was never removed after all) on the next Commit and fail again.
If your import function isn't running in multiple threads a simple solution could be to check if the item to add is already present in your database before actually adding it to your context.
Related
I am looping through the rows in a database table and updating the account balance of each row if it meets the required conditions.
Instead of the row that meets the condition to be updated alone, it gets updated and a new row with the updated values is also created at the same time with the same query.
I use this same code in the same app to change account balances of single rows and it works perfectly. But when I make calls to it from a foreach loop, the above stated problem occurs:
public bool CreditCustomerAccount(CustomerAccount account, decimal amount)
{
try
{
if (account.Type == AccountType.Current || account.Type == AccountType.Savings)
{
account.AccountBalance += amount;
}
else
{
account.AccountBalance -= amount;
}
_context.Entry(account).State = EntityState.Modified;
_context.SaveChanges();
return true;
}
catch (Exception)
{
return false;
}
}
It updates the row in question but also creates a new database table entry with the updated details
When you post an entity directly back, EF context knows nothing about it. Check out the Attach method on a DBSet. From MS DOCS:
Attaches the given entity to the context underlying the set. That is,
the entity is placed into the context in the Unchanged state, just as
if it had been read from the database... Attach is used to repopulate
a context with an entity that is known to already exist in the
database.
SaveChanges will therefore not attempt to insert an attached entity
into the database because it is assumed to already be there.
So using Attach before modifying any properties will allow the context to "know" about the object, and any changes would then indicate the object is modified.
Platform: .NET 4.5, EF6
Original code:
model.ContentGroups = new List<ContentGroup>();
model.ContentGroups.Add(new ContentGroup());
Working code:
model.ContentGroups.Clear()
model.ContentGroups = new List<ContentGroup>();
model.ContentGroups.Add(new ContentGroup());
ContentGroups definition:
private ICollection<ContentGroup> _contentGroups;
public virtual ICollection<ContentGroup> ContentGroups
{
get { return _contentGroups ?? (_contentGroups = new List<ContentGroup>()); }
set { _contentGroups = value; }
}
If model.ContentGroups already contains one item, the original code resulted two items in the collection unless deliberately make call to collection Clear()
It only occurs when compiling code in release mode, but not in debug build.
Any feedback is appreciated.
Michael
Without seeing all the code related to your DbContext and how you are actually using this I can't be certain but I think it is because setting the property to a new list sort of conflicts with entity framework. I am guessing the issue is related to lazy loading and the way entity framework knows when to do something with the database. Even though you have set it to a new List, EF still goes out to the database and re-fills the list when you enumerate it. Setting to a new List doesn't tell EF to do anything database wise.
If you want to clear the list then all you need is the .Clear() call, there is no need to ever set it to a new list. The Clear() will instruct EF to update the database when you SaveChanges on the DbContext.
I get an error when trying to delete a record:
InvalidOperationException - The object cannot be deleted because it was not found in the ObjectStateManager.
public ActionResult Delete(CustomerModel customer)
{
db.Customer.Remove(customer);
db.SaveChanges();
return View();
}
Update: I checked in the bebugger, customer is completely empty..And therefore the database isn't able to delete that specific record.
Any ideas why?
Your customer object was not loaded into the DbContext called db (I'm assuming it's a DbContext... not entirely clear from your code).
With Entity Framework, the DbContext you are using has to be aware of an object it is acting on. It looks like you created customer in some manner other than by loading it into db.
You can add it to the DbContext like this:
db.Attach(customer);
Then, proceed to remove it and save your changes.
For more details see
http://blogs.msdn.com/b/adonet/archive/2011/01/29/using-dbcontext-in-ef-feature-ctp5-part-4-add-attach-and-entity-states.aspx
Specifically for your case the paragraph Attaching an existing entity to the context
Update (based on your update)
How did you create customer in the first place? Without that detail, it's just guesswork to understand why it's completely empty.
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 trying to have a View where the user can add items in a collection without having to go to a new View (the scenario is a sort of CV site where the user adds info about work experience, skills, etc, and it would seem absurd to go to a new View to add each little thing).
So I have an edit View that shows a number of text boxes for the already added items, and there's an ajax call to go to a method to fetch the collection fresh if the user adds an item.
Here are the methods in question:
public ActionResult Edit(int id)
{
Consultant consultant = _repository.GetConsultant(id);
var vm = GetViewModel(consultant);
return View(vm);
}
private DetailsViewModel GetViewModel(Consultant consultant)
{
return new DetailsViewModel
{
Programs = consultant.Programs.ToList(),
Consultant = consultant
};
}
public ActionResult NewProgram(int id)
{
//TODO: ordering is rather strange, because the entitycollection adds at the beginning rather than the end...
Consultant consultant = _repository.GetConsultant(id);
consultant.Programs.Add(new Program());
_repository.Save();
var vm = GetViewModel(consultant);
return PartialView("ProgramList", vm);
}
Now to the question: When the NewProgram method is called, it adds a new program to the Consultant object and creates a new ViewModel to send back, but it adds the new program to the start of the EntityCollection, not at the end. But then when you post the entire form, and you open the Edit View again, the list will have placed the new added program at the end. This is very strange. The user will think he/she adds an item at the start of the list, but if they go back to the page again, they will find the new item at the end.
Why does it do this, and is there any way I can make NewProgram() have the new program added at the end directly?
And if anyone is thinking "he should be using a ViewModel" with DTOs instead of working directly with EF objects, well, I've been down that road for quite a while now ( Entity Framework and MVC 3: The relationship could not be changed because one or more of the foreign-key properties is non-nullable ), and so far no one has shown me explicitly how to achieve this and still be able to both add and remove items in the same View. There is either a problem with maintaining the indexes of the collections or the Entity Framework won't let me save... And the code became a nightmare.
This way I at least have understandable code, and the only thing is I need to have this adding done in the "normal" order, i.e. add at the end of the collection...
Any ideas?
BTW:
This works, but it seems very unnecessary to first have to add the new program to the Consultant object, create the ViewModel without the new program, and then add it to the ViewModel separately...
public ActionResult NewProgram(int id)
{
//TODO: ordering is rather strange, because the entitycollection adds at the beginning rather than the end...
Consultant consultant = _repository.GetConsultant(id);
var vm = GetViewModel(consultant);
var program = new Program();
consultant.Programs.Add(program);
_repository.Save();
vm.Programs.Add(program);
return PartialView("ProgramList", vm);
}
According to http://blogs.msdn.com/b/adonet/archive/2009/12/22/poco-proxies-part-1.aspx , your navigation property Programs is overridden to invoke some kind of DoLazyLoad() method. Since the property instance itself isn't necessarly changed, DoLazyLoad() might actually by asynchronous, which could account for the behavior you're noticing.
Since you are evaluating the list anyhow, you could call ToList() before adding the new program. It would require you to only change the line a bit:
consultant.Programs.ToList().Add(new Program());
If this doesn't work, try:
consultant.Programs.ToList();
consultant.Programs.Add(new Program());
This actually doesn't work well with my "asynchronous" theory, but might help you out.