I encountered some strange issue using entity framework and I am not sure what's the moral.
Should I use a separate context object for every create, update and delete operation?
I have an entity, that has a unique constraint. I receive values from a different thread and want to store them in an entity and persist them to the database. If I am using one DbContext and adding an entity fails because of unique constraint violation, all following attempts to store an valid entity will fail, too. This is because the invalid entity is still in the DbContext's set.
The problem reduces to this, I think:
/*
* m_context is a memeber variable of the class and created once
* new MyEntity("unique") => creates new entity and sets the unique attribute to the passed value
* the entity's key is set automatically
*/
//works fine
try { m_context.MyEntities.Add(new MyEntity("unique"); m_context.SaveChanges() } catch{}
//fails because of unique constraint violation => this is okay
try { m_context.MyEntities.Add(new MyEntity("unique"); m_context.SaveChanges() } catch{}
//fails, too => not okay and not expected
try { m_context.MyEntities.Add(new MyEntity("unique2"); m_context.SaveChanges() } catch{}
Exceptions are System.Data.Infrastructure.DbUpdateException (an error occurred while updating the entries) -> System.Data.UpdateException (an error occurred while updating the entries) -> System.Data.SQLite.SQLiteException (constraint failed column ... is not unique)
=> In the scenario above twice, with separate context (see below), once, only.
I think the problem will occur with every database provider and is not related to SQLite.
So there are at least two solutions:
Remove the second entity manually from the DbSet in MyContext
Use a seperate context for each create operation
I tend to use the second. This is because I saw a lot of using statements in tutorials/code.
I never understood the reason for this massive recreation of the same object, but this might be a (the) reason. Is this true, or am I completely wrong.
This problem also exists for update and delete operations. And another reasons for failure might be a foreign key violation.
So what's the best practice here? Separate context object per operation? But is the object really that light-weight? And should I apply the "one context per operation" to read operations, too?
As already mentioned, the fix is obvious, but the moral is not totally clear to me, and I am interested in what's the best practice here. Thanks!
Related
I am using entity framework 5.0. I am in a rocess od changing my app from ObjectContext to DbContext model. DbContext should be according to microsoft the recommended approach. I use database forst approach and I have generated model form database.
But, at a very first simple task there is a problem. Namely simple update of a record is broken.
Let's have a simple table Item, for ilustration only:
Item
(
ItemId int NOT NULL, -- Primary key
Name nvarchar(50) NOT NULL,
Description NVARCHAR(50)
)
I have noticed that using DbContext does not support updating a record not as ObjectContext does.
In my application I have a simple update method.
public void UpdateItem()
{
MyContext context = new MyContext();
Item item = new Item();
item.ItemId = 666;
context.Items.Attach(item);
// From this point onward EF tracks the changes I make to Item
Item.Description = "Some description";
context.SaveChanges();
}
Using ObjectContext this method correctly updates a record. Using SQL profiler I can see that it generates something like this (greatly simplified!!!)
UPDATE Item
SET Description = 'Some description'
WHERE ItemId = 666
If, however I try to do the same thing in DbContext I get the exception:
System.Exception: Items.aspx.cs - logged from CustomError() ---> System.Data.Entity.Validation.DbEntityValidationException: Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
at System.Data.Entity.Internal.InternalContext.SaveChanges()
at System.Data.Entity.Internal.LazyInternalContext.SaveChanges()
at System.Data.Entity.DbContext.SaveChanges()
And no database UPDATE is issued to Sql server.
I guess that DbContext validates all the properties and the property Name is null. This by design. I do not intend to modify it, I do not even know what is it and I do not need to know what is it.
Only the property Description was changed. Clearly ObjectContext does not track changes correctly.
How can this problem be resolved?
I have researched the issue and found the something on updating records.
For example this link: https://stackoverflow.com/a/15339512/4601078
db.Users.Attach(updatedUser);
var entry = db.Entry(updatedUser);
entry.Property(e => e.Email).IsModified = true;
// other changed properties
db.SaveChanges();
But this is horrible code. For every property on should add a line like:
entry.Property(e => e.Email).IsModified = true;
This produces ugly unreadable code, an I suspect lamda expression are not stelar in performance.
Even worse are those who propose to make a roundtrip to DB to fetch existing records with all properties populated, update it and the save changes. This is a no go with regard to performance.
So, how to tackle with simple entity updates or is this DbContext just another item in microsofts collection of dead ends which serve no real purpose?
DbContext doesn't really track changes by watching properties, it compares the values to previously known values. And validation always works on the entire entity so the only real choice if you want to do things this way is to disable validation during this operation. See Entity Framework validation with partial updates
If you know for sure that the changes you apply are valid, or you have custom code to validate them, you can turn off validation by EF altogether:
db.Configuration.ValidateOnSaveEnabled = false;
This works OK as long as you do it your way: attach a new entity with a known Id (aka a stub entity) and then modify its properties. EF will only update the properties it detects as having been modified (indeed by comparing original and current values, not, as ObjectContext did, by change notifications). You shouldn't mark the entity itself as modified.
If you don't want to turn off EF's validation, but neither want to mark individual properties as modified, I think this could be a useful alternative (brought to my attention by Alex's answer).
I need to Remove and Add entities with the same primary key value in a single request, can anybody suggest me the solution?
Below is my sample code that gives the error: Violation of PRIMARY KEY constraint 'PK_Table'. Cannot insert duplicate key in object 'dbo.Table'.
context.Set<Entity>().Attach(existingEntityObj);
Entry(existingEntityObj).State = EntityState.Deleted;
context.Set<Entity>().Add(newEntityObj);
context.Entry<Entity>(newEntityObj).State = EntityState.Added;
context.SaveChanges();
Assume both the objects (existingEntityObj and newEntityObj) have the same value in the primary key property.
thanks in advance!!
You'll need to do two SaveChanges() calls in order to make this work. The problem here is that, while it appears you are first deleting the record and then adding a new one, the framework is actually doing the insert first.
The reason is because Entity Framework doesn't give you granular control over what orders the operations happen in. So your best bet is going to be to wrap the two in separate TransactionScope's which will let you control the individual transactions that are occurring.
You can read more here: https://blogs.msdn.microsoft.com/alexj/2009/01/11/savechangesfalse/
A single update statement can work for you so the solution is to update the old entity with the new entity values, if you want to keep the same primary key value. As primary key identifies an entity, deleting old one and adding a new one in place of it will have the same effect as update.
Correct me if you feel I am wrong, by giving me a perfect example.
You can also try using explicit DbContextTransaction, like so:
using (DbContextTransaction transaction = context.Database.BeginTransaction())
{
context.DoSomething();
context.SaveChanges();
context.DoSomethingElse();
context.SaveChanges();
transaction.Commit();
}
Don't forget to also catch exceptions and then do transaction.Rollback();
In our Entity Framework 6 seed method, we're setting user preferences to default values. We had a lot of issues getting stuff to work correctly, so we started printing debugging statements to a file. Now, however, if we remove the debugging lines, we get an exception.
Here is the code:
// Get the preferences.
Preferences prefs = context.Preferences.FirstOrDefault(x => x.UserId == user.Id);
using (StreamWriter write = new StreamWriter(#"C:\myFile.txt"))
{
//foreach (PropertyInfo prop in prefs.GetType().GetProperties())
// write.WriteLine($"{prop.Name} = {prop.GetValue(prefs)}");
prefs.ColumnIds = defaultColumnIds;
prefs.Columns = defaultColumns;
prefs.CategoriesOnYAxis = true;
prefs.TabHorizontal = true;
prefs.OnlyAssignedToUser = true;
context.SaveChanges();
}
If we uncomment that For loop, then the seed method runs fine. With the for loop commented out, we get the following exception:
An error occurred while saving entities that do not expose foreign key
properties for their relationships. The EntityEntries property will
return null because a single entity cannot be identified as the source
of the exception. Handling of exceptions while saving can be made
easier by exposing foreign key properties in your entity types. See
the InnerException for details.
In this example, the User object is 1-1 to the Preferences object, with Preferences having a foreign key to the User table.
We can even take out the stream writer and loop through the properties to the console, and as long as that loop is there the seed method runs correctly. As soon as it's gone, we get the error.
I have a guess what might be happening here. I think this is your problem:
prefs.ColumnIds = defaultColumnIds;
prefs.Columns = defaultColumns;
I assume these are backed by the same field in your database? So you're setting both the Column entities as well as the ColumnId primary keys? You don't need to set both (though it should work).
I bet if you remove the assignment to prefs.Columns (and leave out the debug code), your code will start to work. The issue is defaultColumns. What's in there? Column entities - but are those entities attached to the current DbContext? (Your code doesn't show how they come into being)
When you fetch your prefs with context.Preferences.FirstOrDefault(x => x.UserId == user.Id);, you're asking EF for a Preference entity, but by default EF won't eager-load navigation properties (like those Column entities). Conversely, if you have a bunch of Column entities but they haven't been explicitly attached to (or fetched using) the current DbContext, EF will think those Columns are new, and that relationship might not be mapped in a way that EF can insert new Columns (which you don't want to happen, anyway).
When your debug code runs, prefs.GetType().GetProperties() is enumerating all the properties in the Preference, which I think EF is then lazy loading. When you remove the loop, it no longer enumerates then, so all the Preference's navigation properties didn't get pre-loaded.
There are a few ways you can go about this:
If you're absolutely certain all those defaultColumnIds are already in the database when you run the Seed method, then set only ColumnIds and call it a day. It will match the entities in the database when it executes your query, and as long as the expected foreign keys are there you're fine.
Fetch the defaultColumns from the context as soon as you open the context, or, if you defined them in code, attach them to the ChangeTracker before you call SaveChanges.
I use ISession.Query<T>().ToList() to acquire a list of objects, add them to a ComboBox so the user can browse all the objects, and then I edit one of these and call ISession.Update() with that object. However this throws a NonUniqueObjectReferenceException.
Why does this happen and what approach should I use to avoid/fix this?
Problem explanation
When you load an entity, Session keeps track of the database's primary key for the entity as well as the entity's object reference (location in memory) - for the duration of the Session.
The NonUniqueObjectReferenceException is thrown if you attempt to persist an entity that has the same primary key but a different object reference as an already loaded entity for this Session.
In other words the Session is telling you, "I have an entity in memory with the same primary key as the entity you are trying to persist, but the object reference of my copy doesn't match your copy."
Problem example
Open a Session (#1).
Load the entity (object reference = A, primary key in database = 1).
Close the Session.
Open a new Session (#2).
Load the same entity again (this time, object reference = B, primary key in database = 1).
Change a property on object A and persist it inside Session #2.
NonUniqueObjectReferenceException will be thrown.
It's worth noting that this exception will be thrown, even if object A is simply part of a larger object graph that is being persisted in session #2 (even if object A didn't change).
It's also worth noting that you can load an entity directly (Session.Load, Session.Get, Session.QueryOver, etc) or indirectly (with a query that doesn't return the object, but causes the object to be loaded into memory). The NonUniqueObjectReferenceException can be thrown for both a directly or in-directly loaded entity.
Important note: This problem can be caused in other ways, for example it can be caused with a single Session if you load and then clone an entity and then use this clone to persist some changes using the Session. Reason being, the clone's object reference would differ from the original entity.
Solution explanation
There is a method called Merge on the Session object:
object Session.Merge(object obj)
Merge will take an entity and, using the entity's primary key, retrieve the already loaded version of this entity from the current Session. It will also update the properties of the Session's already loaded entity if they differ from the entity you have just passed.
This method does not change the entity that you pass in, but instead returns a different object which you should use.
One last note about Merge is that if the Session you are in doesn't have a loaded copy of this entity in memory, Merge will go ahead and load the entity from the database before performing it's usual merging functionality.
Solution example
// using the example above, we are at the beginning of step 6 inside session #2
// we have 2 important objects = ISession sessionTwo, Option objectA.
// Option is an entity defined by you, it is not part of NH.
objectA.SomeProperty = "blah";
var optionFromSessionTwo = (Option) sessionTwo.Merge(objectA);
// this will not throw and it will persist the changes to objectA
sessionTwo.Flush();
Hope this helps!
Update method as stated by Denis is used for the persistance of detached objects.This exception is thrown probably because you are trying to use Update() on those objects you probably already have in the session. You should use a transaction or Flush() in order to update your objects, if you already do so; deleting session.Update() from your code should do it.
Here is the appropriate part in the NHibernate documentation.
You need to read the documentation on:
How to use NHibernate on Winforms
What "Update" means.
The quick fix is: Don't call session.Update(), just call session.Flush() so the changes are reflected to the DB.
session.Update() doesn't update the entity, session does that transparently. Update and Save methods aren't related to INSERT and UPDATE, instead, Save() makes transient object persistent, where Update() makes detached object persisted.
In my application, I have a method that I call from code which seeds the database and this works fine.
I Have just created a new big method which also adds a lot more data to the database.
When I call this, it appears to work fine the first time it has run, but, if I run it again within a few minutes of the previous attempt, I get the following error:
The changes to the database were committed successfully, but an error
occurred while updating the object context. The ObjectContext might be
in an inconsistent state. Inner exception message: 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.
I am unsure how to fix this, can anyone advise anything? (other than not to run this within a few minutes of the last attempt!).
This Error usually occurs when you try to save an entity model which has not defined well the primary key (composite key) as like in the database.
Define the keys in EntityTypeConfiguration file like
this.HasKey(f => new { f.ID1, f.ID2 });
Possible duplicate of:
InvalidOperationException when calling SaveChanges in .NET Entity framework
Autonumber with Entity Framework
Here are u useful links on working with self-tracking entities and the Object State Manager
http://msdn.microsoft.com/en-us/library/bb896269.aspx
http://msdn.microsoft.com/en-us/library/ff407090.aspx