Can i use Entry repeatedly? - c#

asp.net mvc 5, entity framework 6.0. I have ADO.NET EDM model. And i need update multiple entities. If i use Entry several times, i take exception: ObjectStateManager already have an object with the same key.
How i can do this, using something like this code:
db.Entry(company.Company).State = EntityState.Modified; //one type object
db.SaveChanges();
db.Entry(company.Preview).State = EntityState.Modified; //another type object
db.SaveChanges();
foreach (CompanyTelephone item in company.Phones) // another type
{
if (item.Id > 0)
{
db.Entry(item).State = EntityState.Modified;
db.SaveChanges();
}
else
{
db.CompanyTelephones.Add(item);
}
}
db.SaveChanges();

You can use Entry as often as you want. The problem is in setting the state. If you set the state the item is attached to the context. Apparently, some items were already attached in the first two lines. You can simply check whether an item is Detached:
if (item.Id > 0 && db.Entry(item).State == EntityState.Detached)
{
db.Entry(item).State = EntityState.Modified;
db.SaveChanges();
}
else
{
db.CompanyTelephones.Add(item);
}
Still, as Andrei said, multiple SaveChanges calls should not be necessary. You break the transactional unit. Maybe it doesn't matter. Nevertheless, esp. the calls within the loop cause a lot of separate database rondtrips.

Related

ASP.NET MVC Updating Master-Detail Records in Single HTTP Post Request

I'm teaching myself C# and MVC but have a background in SQL. When updating an existing master-detail set of records in a single action (let's say for instance a customer order and order details), updating the master record is no problem. Regarding the detail records, I'm seeing examples that simply delete all existing details and then add them back in rather than add, delete or update only what's changed. That seems easy and effective but involves unnecessary changes to database records and might be an issue in complex relationships.
I've tried writing code that checks the existing values against posted values to determine the right EntityState (Added, Deleted, Modified, Unchanged) for each detail. Accomplishing this using LINQ Except and Intersect works but seems to cause an unexpected performance hit.
(Instead, I could load the original values in an "oldValue" hidden field in the original GET request to compare to the POST values except that would be unreliable in a multi-user environment and seems like a bad idea.)
I'll be happy to provide code examples, but my question is more about best practices. Is there a preferred method for updating existing master-detail sets of records?
EDIT: I've added the code below in response to questions. In this example, our application allows additional attributes to be attached to a product, kept in a separate table ProductAttributes. The view allows the user to edit both the product and the attributes on the same webpage and save at the same time. The code works fine but seems slow and lags at SaveChanges.
public ActionResult Edit(Product product)
{
if (ModelState.IsValid)
{
db.Entry(product).State = EntityState.Modified;
// Establish entity states for product attributes.
List<ProductAttribute> existingAttributes = new List<ProductAttribute>();
existingAttributes = db.ProductAttributes.AsNoTracking()
.Where(x => x.Sku == product.Sku).ToList();
// Review each attribute that DID NOT previously exist.
foreach (ProductAttribute pa in product.ProductAttributes
.Except(existingAttributes, new ProductAttributeComparer()))
{
if (pa.Value is null)
{
// Value didn't exist and still doesn't.
db.Entry(pa).State = EntityState.Unchanged;
}
else
{
// New value exists that didn't before.
db.Entry(pa).State = EntityState.Added;
}
}
// Review each attribute that DID previously exist.
foreach (ProductAttribute pa in product.ProductAttributes
.Intersect(existingAttributes, new ProductAttributeComparer()))
{
if (pa.Value is null)
{
// Value existed and has been cleared.
db.Entry(pa).State = EntityState.Deleted;
}
else
{
if (pa.Value != existingAttributes
.FirstOrDefault(x => x.Attribute == pa.Attribute).Value)
{
// Value has been altered.
db.Entry(pa).State = EntityState.Modified;
}
else
{
// Value has not been altered.
db.Entry(pa).State = EntityState.Unchanged;
}
}
}
db.SaveChanges();
return RedirectToAction("Details", new { id = product.ProductId });
}
return View(product);
}
internal class ProductAttributeComparer : IEqualityComparer<ProductAttribute>
{
public bool Equals(ProductAttribute x, ProductAttribute y)
{
if (string.Equals(x.Attribute, y.Attribute,
StringComparison.OrdinalIgnoreCase))
{
return true;
}
return false;
}
public int GetHashCode(ProductAttribute obj)
{
return obj.Attribute.GetHashCode();
}
}
}

Attaching an entity of type.. failed because

I am experiencing strange behavior with my Edit action on one of my controllers.
Here is my code:
public ActionResult Edit([Bind(Include = "ID,FirstName,LastName,Email,deleted,AId")] Information personInformation)
{
// to stop multiple calls to database
var lstOfPeople = db.Information.Where(x => x.deleted == false).ToList();
if (ModelState.IsValid)
{
if (lstOfPeople.Any(x => x.Email.Equals(personInformation.Email, StringComparison.CurrentCultureIgnoreCase) && x.ID != personInformation.ID))
{
ModelState.AddModelError("", "This email already exists!");
return View(personInformation);
}
db.Entry(personInformation).State = EntityState.Modified; // error here
db.SaveChanges();
return RedirectToAction("Index");
}
return View(personInformation);
}
Now I have pretty much the exact same code in a different controller edit action and it works fine. Here that is:
public ActionResult Edit([Bind(Include = "ID,RNumber,DateCreated,PersonWhoCreated,deleted")] OtherTable oTable)
{
if (ModelState.IsValid)
{
if (db.OtherTable.Any(x => x.RNumber.Equals(oTable.RNumber, StringComparison.CurrentCultureIgnoreCase) && x.ID != oTable.ID))
{
ModelState.AddModelError("RNumber", "This number already exists!");
return View(oTable);
}
db.Entry(oTable).State = EntityState.Modified; // no error here.. goes through fine.
db.SaveChanges();
return RedirectToAction("Index");
}
return View(oTable);
}
Error I am receiving:
Attaching an entity of type 'ProjectName.Models.Information' failed because another entity of the same type already has the same primary key value. This can happen when using 'Attach'.. etc.
Any idea on why this is happening?
The db.Entry(personInformation).State attachs the personInformation object to the EF context, the error raises because the EF data context has previously loaded a diferent Information instance with the same id (perhaps in the previous Where() you are doing).
There are at least two basic options:
first one is to use the AsNoTracking() extension for your queries, (as in db.Information.AsNoTracking()) this extension tells EF data context to not keep the entities retrieved from database in memory so you can attach your personInformation entity directly to the context and set the modified state.
second option is retrieve the entity from the ef data context and modify that entity (not the personInformation instance). You can retrieve the entity with db.Information.FindAsync and the EF context will retrieve from memory if it is previously attached.
var attachedEntity = db.Information.FindAsync(personInformation.ID);
attachedEntity.ExampleValue = personInformation.ExampleValue;
attachedEntity.ExampleValue2 = personInformation.ExampleValue2;
...
and so on

How to remove related data

I have an MVC Edit page, on which I also edit related data.
On postback, I have the following piece of code that handles my inserts, updates and deletes for related data:
if (ModelState.IsValid)
{
Guid myGuid = new Guid();
//-- Set PK and FK values --
foreach (AccountType_Organisatie myAccType_Org in myOrg.AccountType_Organisaties)
{
if (myAccType_Org.AccountType_Organisatie_GUID == myGuid)
{
//-- Create --
myAccType_Org.AccountType_Organisatie_GUID = Guid.NewGuid();
myAccType_Org.Organisatie_GUID = myOrg.Organisatie_GUID;
csContext.Entry(myAccType_Org).State = EntityState.Added;
}
else
{
//-- Update --
//-- Force MOdified state - even when unchanged --
csContext.Entry(myAccType_Org).State = EntityState.Modified;
}
//myAccType_Org.AccountType = csContext.AccountTypes.Find(myAccType_Org.Accounttype_GUID);
}
//-- Explicitly handle Deletes --
List<Guid> lGuids = myOrg.AccountType_Organisaties.Select(p => p.AccountType_Organisatie_GUID).ToList();
csContext.AccountType_Organisaties.RemoveRange(csContext.AccountType_Organisaties.Where(p => p.Organisatie_GUID == myOrg.Organisatie_GUID).Where(q => !lGuids.Contains(q.AccountType_Organisatie_GUID)));
csContext.Entry(myOrg).State = EntityState.Modified;
csContext.SaveChanges();
return RedirectToAction("Index");
}
I am explicitly deleting items from the context that are no longer present in the new model (posted back from edit page). Is this the best way to do this?
I am explicitly setting the State to modified for all (still) existing items, forcing an update - what is the best way to determine whether an update is necessary or not? Do I need to explicitly compare the new values of the item in the model to the item in the database?
Some help / guidelines on how to implement the above is appreciated.

Entity Framework Adding and Deleting an Entity?

I see two codes for adding and deleting entity, I wonder which is the best way and what is the difference between these two.
One is this (for Adding):
using (var context = new BloggingContext())
{
var blog = new Blog { Name = "ADO.NET Blog" };
context.Blogs.Add(blog);
context.SaveChanges();
}
and another is this:
using (var context = new BloggingContext())
{
var blog = new Blog { Name = "ADO.NET Blog" };
context.Entry(blog).State = EntityState.Added;
context.SaveChanges();
}
and I read that calling the Add method on DbSet puts the entity into the Added state.
According to this I think above two codes are nearly same. If its not please tell me the difference.
And another code I've found is:
public virtual void Add(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State != EntityState.Added)
{
dbEntityEntry.State = EntityState.Added;
}
else
{
DbSet.Add(entity);
}
}
and if its true that calling the Add method on DbSet puts the entity into the Added state then I think there is no difference in code in if and else block, so what's the point in here.
And from the above three code which is the best way to add an Entity.
And another code in which I have doubt is what is the use of else block in below code:
public virtual void Delete(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State != EntityState.Deleted)
{
dbEntityEntry.State = EntityState.Deleted;
}
else
{
DbSet.Attach(entity);
DbSet.Remove(entity);
}
}
I don't see a huge benefit in setting the state of an entity to Added since creating a new entity and adding it to the set does exactly that like you mentioned. Where this type of pattern is pretty useful is when you want to delete an entity without having to fetch it from the database first:
// this entity will be unattached at this point, so if you were to call remove
// on the dbset it wouldn't do much, since it doesn't think it's in the database
var deleteThisEntity = new Blog { Id = 5 };
// if you set the state to deleted, it now thinks that it needs to be deleted
db.Entry(deleteThisEntity).State = EntityState.Deleted;
// call SaveChanges to delete
db.SaveChanges();
You can get a similar effect by setting the state to modified, so it will trigger an update statement. Sometimes you just don't want to take the extra hit of fetching an item from the database just to delete it.
ADD Patterns for ASP .NET using the code bellow is very standard practice.
using (var context = new BloggingContext())
{
var blog = new Blog { Name = "ADO.NET Blog" };
context.Blogs.Add(blog);
context.SaveChanges();
}
In the code for delete, the if/else statement is to check if the fetched object is valid.
The code I've been using for DELETE patterns is this:
var fetchedObject = context.Blog.Find(id);
if (fetchedObject == null)
{
return false;
}
else
{
ds.Blog.Remove(fetchedObject);
return true;
}
This is a method inside a Manager class that receives the id.

Enity Framework updating with a child table causing errors

I have the following update function
public void UpdateBatchDefinition(BatchDefinition batchToUpdate)
{
if (batchToUpdate == null)
{
throw new ArgumentNullException("batchToUpdate");
}
BatchDefinition foundDefinition =
this.context.BatchDefinitions.SingleOrDefault(definition => definition.Id == batchToUpdate.Id);
if (foundDefinition != null)
{
if (!string.IsNullOrWhiteSpace(batchToUpdate.Name))
{
foundDefinition.Name = batchToUpdate.Name;
}
if (!string.IsNullOrWhiteSpace(batchToUpdate.Description))
{
foundDefinition.Description = batchToUpdate.Description;
}
if (!string.IsNullOrWhiteSpace(batchToUpdate.LoadType))
{
foundDefinition.LoadType = batchToUpdate.LoadType;
}
if (batchToUpdate.JobId != Guid.Empty)
{
foundDefinition.JobId = batchToUpdate.JobId;
}
foundDefinition.Tables = batchToUpdate.Tables;
this.context.SaveChanges();
}
}
the issue I am having Is when I am trying to update the Tables list. Tables is a List of Table and Table is a Entity of another table
Tables could be added to, removed from or left alone. I need to update that with what ever is being passed in
when I run this right now I get an 'EntityValidationErrors' error, though it wont tell me what the validation issue actually is.
on Inserting I got the same error but was able to fix it using the following
var underlyingContext = this.context as DbContext;
if (underlyingContext != null)
{
foreach (var table in batchDefinition.Tables)
{
// Need to mark the table entity as unchanged or
// else EF will treat it as a new table
underlyingContext.Entry(table).State = EntityState.Unchanged;
}
}
so I tried using that in this update function
var underlyingContext = this.context as DbContext;
if (underlyingContext != null)
{
foreach (var table in foundDefinition.Tables)
{
// Need to mark the table entity as unchanged or
//else EF will treat it as a new table
underlyingContext.Entry(table).State = EntityState.Unchanged;
}
}
foundDefinition.Tables = batchToUpdate.Tables;
and I get the following error instead:
AcceptChanges cannot continue because the object's key values conflict
with another object in the ObjectStateManager. Make sure that the key
values are unique before calling AcceptChanges.
Any thoughts one what I am missing here?
Change end of your update method like this:
foreach (var t in foundDefinition.Tables.ToList())
Context.Tables.Remove(t);
foundDefinition.Tables = batchToUpdate.Tables;
this.context.SaveChanges();
And about your last error, it is said that there are some duplicates in your context. So, EF can't save the context changes into the db (because there are duplicates in the context!)
In fact, I don't know the last error is from add or delete - you didn't mention clearly. So, I don't know the last two code samples are from your add method, or your update method...
However for update, the trick I mentioned here, must solve your problem for update...

Categories