How do I replace an entity in the middle of the graph? - c#

I have a disconnected entity framework graph that is sent to the client and returned with updates. One of the updates may replace an entity in the middle of the graph. When I try to replace it, I get an InvalidOperationException with the message:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
For simplicity, the model would look something like this:
The code that's causing the exception would look like this:
// Create a tree of objects
Root root = new Root() { Id = 1 };
Branch origBranch = new Branch() { Id = 3 };
Twig onlyTwig = new Twig() { Id = 5 };
Leaf onlyLeaf = new Leaf() { Id = 7 };
onlyTwig.Leaves.Add(onlyLeaf);
origBranch.Twigs.Add(onlyTwig);
root.Branches.Add(origBranch);
// Store the structure in the database using the container
using (Pot container1 = new Pot())
{
container1.Roots.Add(root);
container1.SaveChanges();
}
// Create a new Branch to replace the original
Branch newBranch = new Branch() { Id = 11 };
// Add the Twig from the original object
newBranch.Twigs.Add(onlyTwig);
using (Pot container2 = new Pot())
{
container2.Roots.Attach(root);
// Replace the branch
root.Branches.Remove(origBranch);
root.Branches.Add(newBranch);
container2.SaveChanges(); // THROWS EXCEPTION !!
}
I thought by removing the old entity, then adding a new one that I would satisfy the "new relationship must be defined..." criteria, but it's failing. In our case, making the column nullable in the database leads to other issues, the first of which is risking database integrity. So how is this normally handled?
I resolved part of the issue by essentially tricking entity framework into believing it was making an update in place, rather than replacing the item with a new item. The key to the solution is setting the same primary key value, then setting the entity state to Modified The replacement code is posted below.
// Create a new Branch to replace the original
// Make sure to use the id from the original saved object
Branch newBranch = new Branch() { Id = origBranch.Id };
// Add the Twig from the original object
newBranch.Twigs.Add(onlyTwig);
// Replace the original branch with the new branch
root.Branches.Remove(origBranch);
root.Branches.Add(newBranch);
using (Pot container2 = new Pot())
{
// Attach the graph to the container. The default state is unchanged.
container2.Roots.Attach(root);
// Trick entity framework into thinking the new branch with the
// same primary key is the original item with modifications
container2.Entry(newBranch).State = EntityState.Modified;
container2.SaveChanges();
}
However, there are occasions to replace the entity with an entirely new entity, so I still need an answer for how to remove then add.

Related

Assigning entity instance instead of entity id creates new record

I have these two tables:
public class FiscalYear
{
... other fields
public int FiscalYears_Id { get; set; }
}
public class SkipHeader
{
... other fields
public int FiscalYears_Id { get; set; }
public virtual FiscalYear FiscalYear { get; set; }
}
Attempting to create a new SkipHeader like so:
var skipHeader = new SkipHeader()
{
... other fields get assigned to
FiscalYear = Session.FiscalYear,
}
Will cause the database to create a new FiscalYear record instead of using the Session.FiscalYear which is simply a static property that gets assigned to at program start. However, if I assign the FiscalYears_Id instead:
var skipHeader = new SkipHeader()
{
... other fields get assigned to
FiscalYears_Id = Session.FiscalYear.FiscalYears_Id,
}
The program uses the existing record as expected.
This bug eluded me and my colleague for months! Now that I found a solution, I would like to know WHY this is the case?
This bug eluded me and my colleague for months! Now that I found a
solution, I would like to know WHY this is the case?
This occurs because the DbContext doesn't know about your FiscalYear object instance, such as whether it represents a new record or an existing one.
Take the following example:
var fiscalYear = new FiscalYear { Id = 4, Name = "2019/20" };
var skipHeader = new SkipHeader { FiscalYear = fiscalYear };
context.SkipHeaders.Add(skipHeader);
context.SaveChanges();
fiscalYear in this instance is an object instance that has been given an ID and Name. When we associate it to a new SkipHeader and add the SkipHeader to the DbContext, EF will see this fiscalYear. Since it isn't an object tracked by the context, it treats it as a new entity like the SkipHeader.
Depending on how your entities are configured for dealing with the PK will determine what happens.
If your PK (Id) is set up as an Identity column (DB will populate) then the FiscalYear will be inserted and assigned the next available Id value. After the SaveChanges() call, fiscalYear.Id would be "6" or "22" or whatever the next new ID assigned to it would be. (Not "4")
If your PK is not an Identity column (App will populate) and a FiscalYear row already exists in the DB for ID 4, then EF will throw a duplicate key Exception on SaveChanges().
Where people get confused is that they assume that since the FiscalYear was at one point (Say during a web request) loaded from a DbContext, it is still somehow acting as a tracked entity when passed into another method outside of the scope of that DbContext. (During another update web request) It's not. When a web request for instance accepts a FinancialYear as a parameter from the client, it is deserializing a FinancialYear. As far as EF is concerned, that is absolutely no different than the new FinancialYear { } example above. The DbContext is not aware of that entity.
Take the following example:
FiscalYear fiscalYear = null;
using (var context = new AppDbContext())
{
fiscalYear = context.FiscalYears.Single(x => x.Id == 4);
}
using (var context = new AppDbContext())
{
var skipHeader = new SkipHeader { FiscalYear = fiscalYear };
context.SkipHeaders.Add(skipHeader);
context.SaveChanges();
}
This provides a basic outline of a Fiscal Year that was loaded by one instance of a DbContext, but then referenced by another instance of a DbContext. When SaveChanges is called, you will get a behaviour like you are getting now. This is what essentially happens in web requests, as when an entity is returned, the entity definition is merely a contract and the Entity is serialized to send to the client. When it comes back into another request, a new untracked object is deserialized.
As a general rule, Entities should not be passed outside the scope of the DbContext they were read from. EF does support this via detaching and re-attaching entities, but this is honestly more trouble than it is typically worth because you cannot 100% rely on just attaching an entity using DbContext.Attach() as often there can be conditional cases where another entity instance is already being tracked by a context and the Attach will fail. In these cases you'd need to replace references with the already tracked entity. (Messy conditional logic to catch possible scenarios) References are everything when dealing with EF. Two different object references with the same key & values are treated as separate and different objects by EF. Rather than passing references around, it's usually a lot simpler, and better to pass just the FK. This has the benefit of being a smaller payload for web requests.
One option you've found out is to update via the FK:
var skipHeader = new SkipHeader()
{
... other fields get assigned to
FiscalYears_Id = Session.FiscalYear.FiscalYears_Id,
}
This works, however when you have entities that are exposing both FK (FiscalYears_Id) and navigation property (FiscalYear) you can potentially find mismatch scenarios when updating records. This is something to be careful with as an application evolves.
For instance, take an example where you are editing an existing SkipHeader with a FiscalYears_Id of "4". This will have an associated FiscalYear reference available with a PK of "4".
Take the following code:
var skipHeader = context.SkipHeaders.Include(x => x.FiscalYear).Single(x => x.Id == skipHeaderId);
skipHeader.FiscalYears_Id = newFiscalYearId; // update FK from "4" to "6"
var fiscalYearId = skipHeader.FiscalYear.Id; // still returns "6"
context.SaveChanges();
We set the FK value on the skip header, however that does not update the reference for FiscalYear until after we call SaveChanges. This can be an important detail when dealing with FKs alongside navigation properties. Now normally we wouldn't bother going to the Navigation Property to get the ID again, but any code we call that is expecting the new FiscalYear reference to be updated will have a different behavior depending on whether SaveChanges had been called before or after the code in question. If before, all FiscalYear details will be for the old fiscal year even though we changed the FK reference.
This can also lead to odd Lazy Loading errors as well such as:
var skipHeader = context.SkipHeaders.Single(x => x.Id == skipHeaderId);
skipHeader.FiscalYears_Id = newFiscalYearId; // update FK from "4" to "6"
var fiscalYearId = skipHeader.FiscalYear.Id; // NullReferenceException!
context.SaveChanges();
Normally, provided you have lazy loading enabled loading a SkipHeader without eager loading the FiscalYear (.Include(x => x.FiscalYear))and then querying a property from the FiscalYear would lazy load this relative. However, if you change the SkipHeader's FiscalYear_ID FK and then try to access a property off the FiscalYear before calling SaveChanges(), you will get a NullReferenceException on the FiscalYear. EF will NOT lazy load either the old or new FiscalYear entity. Bugs in behaviour like that commonly creep in as applications get developed and code starts calling common functions that assume they are dealing with complete entities.
The alternative to setting updated values for known rows by FK is to load the entity to associate, and associate it by reference:
using (var context = new AppDbContext())
{
var fiscalYear = context.FiscalYears.Single(x => x.Id == fiscalYearId);
var skipHeader = new SkipHeader()
{
... other fields get assigned to
FiscalYear = fiscalYear;
}
context.SaveChanges();
}
This example just uses a locally scoped DbContext. If your method has an injected context then use that instead. The context will return any cached, known instance of the Fiscal Year or retrieve it from the DB. If the FiscalYear ID is invalid then that operation will throw an exception specific to the Fiscal Year not being found due to the Single() call rather than a more vague FK violation on SaveChanges(). (Not an issue when there is only one FK relationship, but in entities that have dozens of relationships...)
The advantage of this approach is that the FiscalYear will be in the scope of the DbContext so any methods/code using it will have a valid reference. The entities can define the navigation properties without exposing the extra FK values,using .Map(x => x.MapKey()) [EF6] or Shadow Properties [EFCore] instead to avoid 2 sources of truth for FK values.
This hopefully will provide some insight into what EF is doing and why it resulted in the behaviour you've seen and/or any errors or buggy behaviour you might have also come across.
Assuming you have pretty standard setup with DbContext being scoped (per request) dependency - the reason is that the new instance of your DbContext does not track the Session.FiscalYear instance - so it creates new. Another way to solve this is using DbContext.Attach:
context.Attach(Session.FiscalYear);
var skipHeader = new SkipHeader()
{
... other fields get assigned to
FiscalYears_Id = Session.FiscalYear.FiscalYears_Id,
}
// save skipHeader
More about change tracker in EF.

How to deal with multiple newly Added Entities that depend on each otter with a Foreign key constraint - EF6

I had a bit of difficulties with getting the right title for this problem so I hope my explanation below will make it a bit clearer.
I am using EntityFramework 6 and I am doing multiple inserts within a function.
There are 3 different tables which get updated / inserted: table EntityMethod, EntityRoom and EntityRoomMethod. The table EntityRoomMethod has a foreign key relationship with the table EntityMethod.
In some cases, a EntityMethod row is missing and this is newly created by adding the object with entity framework:
if (mn == null)
{
mn = new Method
{
ElementId = floorProgram.ElementId,
ActionId = m.ActionId,
ElementCount = m.ElementCount,
ColorId = m.ColorId,
IsBase = m.IsBase,
IsHccp = m.IsHccp,
TimeNorm = m.TimeNorm,
Frequency5Id = m.Frequency5Id,
MaterialId = m.MaterialId,
ProductId = m.ProductId,
MethodTypeId = m.MethodTypeId,
};
}
In another part of code the Method foreign key (MethodId) of the EntityRoomMethod table is being set:
roomMethodObject.RightId = mn.Id;
RightId is in this case the relationship with the EntityMethod table.
On a later point the other 2 table objects (EntityRoom and EntityRoomMethod) are also added (DBSet.Add) using EF.
The problem however is, that when the EntityMethod is newly added, it gets the Id value of 0, because SaveChanges() is not yet executed. The foreign key reference in the EntityRoomMethod is therefor also being set to 0.
When the function returns to the caller, the SaveChanges() is being executed and all 3 objects (representing the 3 tables) are being saved.
This however will generate a FK error (because Id 0 does not exist obviously).
I tried to fix this by calling SaveChanges() after Adding the new Method (so directly in the function). This however will cause some other problems.
In the end I have gotten multiple errors but I assume it all has to do with the same thing, the errors were the following:
Unable to determine the principal end of the 'Solution.Data.RoomMethod_Method' relationship. Multiple added entities may have the same primary key.
The property 'RightId' is part of the object's key information and cannot be modified.
Conflicting changes to the role x of the relationship y have been detected
So now the actual question:
Is there an (easy) way to call SaveChanges() after all 3 entities have been added with EF but also handling the FK errors? Does this mean I have to generate the Id's myself? Or was the first approach better (Calling SaveChanges directly after adding the EntityMethod object).
For now I have some not-nice-looking solution with doing a direct INSERT statement after adding a new EntityMethod (using Dapper). This kind-of works but I assume there is a better way wherein I can just use EF6.
P.S. calling SaveChanges() after adding the EntityMethod was basically the same by doing it with Dapper, however this generated some other errors while using Dapper it didn't generate those errors.
You can wrap several SaveChanges within a transaction, and EF (or probably SQL) will generate the required ids at each point, allowing you to reference them later on.
using(var transaction = _context.Database.BeginTransaction())
{
var p1 = new Something { Name = "Fred" };
_context.SaveChanges();
var a2 = new Dependency { SomethingId = p1.Id }; <-- p1.Id now has an Id value
_context.SaveChanges();
var b3 = new OtherDependency { DependencyId = a2.Id }; <-- a2.Id now has an Id value
_context.SaveChanges();
transaction.Commit(); <-- All 3 changes are fully committed to the db at this point.
}

RemoveRange throws InvalidOperationException in Entity Framework Core

I get the error:
The instance of entity type 'Pupil' cannot be tracked because another
instance of this type with the same key is already being tracked. When
adding new entities, for most key types a unique temporary key value
will be created if no key is set (i.e. if the key property is assigned
the default value for its type). If you are explicitly setting key
values for new entities, ensure they do not collide with existing
entities or temporary values generated for other new entities. When
attaching existing entities, ensure that only one entity instance with
a given key value is attached to the context.
I would understand this error if I would have retrieved before this instance which is then loaded in the context cache, but I haven`t!!!
var pupilsToDelete = pupilIds.Select(id => new Pupil { Id = id });
context.RemoveRange(pupilsToDelete.ToList());
await context.SaveChangesAsync();
During runtime pupilIds are all different ids!
why do I get that error?
To remove records needs to be track able from the context. So instead of create new collection of Pupil just reference them from context
var pupilsToDelete = context.Pupils.Where(a => pupilIds.Contains(a.Id)).Select(b => b);
context.Pupils.RemoveRange(pupilsToDelete);
await context.SaveChangesAsync();
To be able to remove records, you need to make sure your ObjectContext is tracking them. Best way to remove items as is answered by #Mostafiz, but you can also attach your entities to context.
var pupilsToDelete = pupilIds.Select(id => new Pupil { Id = id });
foreach(var p in pupilsToDelete )
context.Attach(p);
context.RemoveRange(pupilsToDelete.ToList());
await context.SaveChangesAsync();
Which is not good approach․

Replace entity model property with value from database

I have an entity framework object model with two entities:
Alert (1-*) ----- (1) Check
The Check table has a unique constraint on the column UniqueProperty.
Checks are usually pre-existing entities in my database and any new alert should be added to existing checks.
I create a simple object graph somewhere in my code:
var alert = new Alert();
alert.Check = new Check { UniqueProperty = someValue };
Sometime later I'd like to persist my object graph:
using (var context = new MyContext())
{
context.Alerts.AddObject(alert);
// Replace temp check with actual database check if available.
var checkFromDb = context.Checks.SingleOrDefault(
c => c.UniqueProperty = alert.Check.UniqueProperty);
if (checkFromDb != null)
{
alert.Check = checkFromDb;
}
context.SaveChanges();
}
So, when there is a corresponding check in the database, use that one, otherwise do nothing (and it will simply be added).
The code above causes a unique constraint violation on the UniqueProperty constraint. The reason is that EF remembers the first check, even though I replace it later with the check from the database.
How can I get rid of that first check?
Don't set up the check until you're ready to save. If one already exists, add the alert to the existing check's alerts collection. If it doesn't, create a new one associate with the check, then add the alert to the db.
var alert = new Alert();
...
using (var context = new MyContext())
{
// Replace temp check with actual database check if available.
var checkFromDb = context.Checks.SingleOrDefault(
c => c.UniqueProperty = alert.Check.UniqueProperty);
if (checkFromDb != null)
{
checkFromDb.Alerts.Add( alert );
}
else
{
alert.Check = new Check { UniqueProperty = some value };
context.Alerts.AddObject(alert);
}
context.SaveChanges();
}

C# LINQ-TO-SQL: datacontext.ChangeConflicts not showing all conflicts

I am running into problem when working with linq-to-sql and trying to resolve conflicts. The problem is that sometimes conflict is not detected. Please look on the below code sample:
// Setup the object to re-produce the problem
//
// MyObject has properties: id, my_string, my_int and version (timestamp) to enable
// conflicts detection
var context = new MyDataContext();
var obj = new MyObject();
obj.id = "1";
obj.my_string = "value";
obj.my_int = 0;
context.MyTable.InsertOnSubmit(obj);
context.SubmitChanges();
context.dispose();
// Get 2 data contexts
var context1 = new MyDataContext();
var context2 = new MyDataContext();
// Get 2 instances of obj - 1 from each context
var obj1= context1.MyTable.SingleOrDefault(o => o.id == "1");
var obj2= context2.MyTable.SingleOrDefault(o => o.id == "1");
// Change the values of obj1 and update it to the database
obj1.my_string= "value1";
obj1.my_int= 0;
context1.SubmitChanges();
context1.Dispose();
// Update the changes in obj2
obj2.my_string= "value2";
obj2.my_int= 1;
// Now the database contains:
// id: "1"
// my_string: "value1"
// my_int: 0
// obj2 contains:
// id: "1"
// my_string: "value2"
// my_int: 1
try
{
context2.SubmitChanges();
}
catch(ChangeConflictException ex)
{
LogInfo("Conflicting members:");
context2.ChangeConflicts[0].MemberConflicts.ToList().ForEach(
mcc=>LogInfo("Property '{0}': Database value: {1}, Current Value:{2}", mcc.Member.Name, mcc.DatabaseValue, mcc.CurrentValue)
);
}
context2.Dispose();
In the catch I expects to see 3 member conflicts: my_string, my_int and version but I see only 2 member conflicts: my_string and version. The my_int is not detected as conflict.
If I would have setup the my_int when the object was created to value different from the value that I have assigned to obj2, the conflict is being detected.
I found some commonality: when the value of a property (any property) of the original object is equal to the value of obj1, the conflict for this property is not detected.
I would like to get any idea how to overcome this problem so conflicts will successfully detected
I found the root cause for the problem. The reason that I didn't get conflict for "my_int" property is because it is not conflicting with the database value that context2 "knows". I'll explain this:
I thought that conflict is defined as when the values of the object that being saved are not equal to the values in the database. IT'S NOT!!!!
Conflict is defined as when the current value in the database is not equal to the original value that the context familiar. The original value is the value that was in the database when the select query executed.
Examining the example in my question, when context2 selected the data from the database to create obj2, the proeprties values were: my_string: "value", my_int:0. Those are the original values.
When I tried to save the data, LINQ-TO-SQL compared the original values to the database values. The database values at this time (after obj1 was saved): my_string: "value1", my_int:0
As a result, I got conflict in my_string (original: "value", database:"value1") but not in my_int(original: 0, database: 0).
Discovering this helped me to understand why there is no conflict, but still it didn't helped me to solve the problem because the wrong value was not saved to the database because I analyzed only the MemberChangeConflicts that exists in the ObjectChangeConflict and if the property that I am checking is not exists there, the check logic is skipped.
The solution was to analyze also the modified members enumerable that give access to all the properties that were modified and for each one I can get the original value and the new value.
To get the modified members, there is a need to execute method called GetModifiedMembers. This method is in the table of the object data type and can be executed as follows:
var mmc = context.MyTable.GetModifiedMembers(myobject);
Combining the conflicts and modified members gave me the full picutre of what happened to the object that caused the conflict and handle it properly.
Thanks for Damien_The_Unbeliever for giving the hint in his comment.

Categories

Resources