Entity Framework object graph deletion with Breeze - entity-framework

I am encountering a recurring problem that just makes no sense, and hoping someone (in the Breeze team?) can shed some light.
The following model illustrates the entities in question.
As you can see, I'm adhering pretty strictly to Entity Framework conventions in my property names, and as a result, if I check in SQL the cascade on delete rules are set by EF code first when it creates the db.
Now, when I try to delete a BusUnit manually in SQL, the delete cascades correctly and the corresponding BusUnitDimensions are also deleted, as it should be. Likewise, if I delete a Dimension in SQL, the corresponding BusUnitDimensions are also deleted.
However, in my application, if I mark a BusUnit as setDeleted with Breeze and then try saveChanges, I get the following error.
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.
Strangely though, if I mark a Dimension for deletion and then save (within Breeze), the cascaded delete works correctly and both the Dimension and its corresponding BusUnitDimensions are deleted.
So, why the inconsistency? Why are the cascaded delete rules in SQL not being applied for BusUnits but yet they're working for Dimensions? I've read elsewhere that Breeze does not support cascaded deletes, but then why is my Dimensions case working?
EDIT:
I've removed my previous edits as they weren't relevant. The changes below follow on from Ward's answer...
My model now looks like this, and BusUnitDims now uses BusUnitId and DimId as a compound key, and I've added a bool, IsBud for the purposes of payload.
I haven't yet implemented deletes for BusUnits, but already if I try delete a Dim, I'm getting the same error 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.
I have noticed that cascaded deletes is no longer enabled, and in fact, to get EF to build the database I to add the following configuration:
modelBuilder.Entity<BusUnitDim>()
.HasRequired(bud => bud.BusUnit)
.WithMany(bu => bu.BusUnitDims)
.HasForeignKey(bud => bud.BusUnitId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<BusUnitDim>()
.HasRequired(bud => bud.Dim)
.WithMany(d => d.BusUnitDims)
.HasForeignKey(bud => bud.DimId)
.WillCascadeOnDelete(false);
So, with cascading now explicitly not in place, I can understand why the error occurs. Does that imply that in the controller, one has to specifically mark each map for deletion when deleting a parent Dim or BusUnit and before saveChanges is called, or is there some way to configure EF to take advantage of cascaded deletes as this would hugely simplify the code in my controller?
(PS: it gets even more complex, because BusUnitDims ends up having a further join table of its own, MetricBusUnitDims to accommodate yet another entity in the model and their relationships. This is why I'm trying to get the principles right early on)
EDIT: (A CONTROLLER SOLUTION FOR BUSUNITS)
So, the following approach works for BusUnits:
function deleteBusUnit(busUnitVm) { // note that you pass in the item viewmodel, not the entity
var busUnit = busUnitVm.busUnit;
var mapVms = busUnitVm.dimMapVms;
var dimHash = createBusUnitDimHash(busUnit);
mapVms.forEach(function (mapVm) {
var map = dimHash[mapVm.dim.id];
if (map) {
datacontext.markDeleted(map);
}
});
datacontext.markDeleted(busUnit);
save().then(function() { getDBoardConfig(); });
}
}
Is this the correct approach? if so, I'll still have to figure out the following:
How to approach Dims. These are different becuase the item viewmodel is defined for BusUnits.
How to approach the situation where there is a join tabel one level down, e.g. MetricBusUnitDIm.
EDIT: (A CONTROLLER SOLUTION FOR DIMS)
function deleteDim(dim) {
return bsDialog.deleteDialog(dim.name, true)
.then(function () {
vm.busUnitVms.forEach(function (busUnitVm) {
busUnitVm.busUnit.busUnitDims.forEach(function (bud) {
if (bud.dimId === dim.id) {
datacontext.markDeleted(bud);
}
});
});
datacontext.markDeleted(dim);
save().then(function () { getDboardConfig(); });
});
}

I believe your problems are traceable to the fact that your mapping table BusUnitDimension has its own primary key, Id, as opposed to the more typical approach in which the BusUnitId and DimensionId FK properties together comprise the compound primary key of BusUnitDimension.
Observe that OrderDetails in Northwind and the HeroPoweMap in the Breeze many-to-many example have compound keys.
Your choice creates complications.
First, it becomes possible to create multiple BusUnitDimension entities representing the same association between BusUnit and Dimension (i.e., they all have the same pair of FKs). The database may be able to prevent this (it's been a long time since I looked) but whether it does or doesn't, it won't prevent you from creating those duplicates in Breeze ... and maybe not in EF either.
Secondly, it opens you up to the problem you're currently facing. If those mapping entities are in the DbContext when you perform the delete, EF may (apparently does) try to null their FK properties as it sets either BusUnit or Dimension to the deleted state.
You can get around this, as has been suggested, by making both the BusUnitId and DimensionId FK properties nullable. But that is contrary to the semantics as a BusUnitDimension must link a real BusUnit to a real Dimension; they aren't optional. The practical consequence may be that you don't get cascade delete from the EF perspective if you do this (not sure if the DB will enforce that either). That means you'd have orphaned BusUnitDimension rows in your database with one or both FKs being null. I speculate because I'm not used to getting into this kind of trouble.
Another approach would be to set their FK values to zero (I think Breeze does this for you). Of course this implies the existence of BusUnit and Dimension table rows with Id == 0, if only during the delete operation.
Btw, you could actually have such "sentinel entities" in your DB.
You must make sure that these BusUnitDimension are in the deleted state or EF (and the DB) will either reject them (referential integrity constraint) or orphan them (you'll have BusUnitDimension rows in your database with one or both FKs being zero).
Alternatively, if you know that the DB will cascade delete them, you can simply remove them from the DbContext (remove from the EntityInfoMap in the EFContextProvider). But now you have to tell the Breeze client to get rid of them too if it happens to have them hanging around.
Enough Already!
These wandering thoughts should tell you that you've got yourself in a jam here with way too much bookkeeping ... and all because you gave BusUnitDimension its own Id primary key.
It gets a lot easier if you give BusUnitDimension the compound key, {BusUnitId, DimensionId}. You must also give it a payload property (anything will do) to prevent EF from hiding it in its "many-to-many" implementation because Breeze doesn't handle that. Adding any nonsense property will do the trick.
HTH

That has nothing to do with Breeze.. The originating message is from Entity Framework..
inside BusUnitDimension Model update BusUnitId property to:
public Nullable<int> BusUnitId { get; set; }
Notice the Nullable struct..

Related

"Cannot insert explicit value for identity column in table 'x' when IDENTITY_INSERT is set to OFF" - inserting record with nested custom object

I get the error "Cannot insert explicit value for identity column in table 'UserPermission' when IDENTITY_INSERT is set to OFF" trying to insert a record as follows:
dbContext.User.Add(someUser);
dbContext.SaveChanges();
That being said, the User file has the custom class UserPermission as one of its parameters, and someUser's UserPermission is not null and has a set ID parameter. Why does this happen and is it possible to avoid getting this error without having to explicitly add a UserPermissionID foreign key parameter in my User model and setting the UserPermission parameter to null?
Thanks in advance.
This issue typically happens when deserializing entities that have related entities in the object graph then attempting to add them. UserPermission is likely an existing record that in the DB is set up with an identity PK, but EF doesn't appear to recognize that in the entity definition. (I.e. set to DatabaseGenerated(DatabaseGeneratedOption.Identity). If it had been you would most likely be seeing a different problem where a completely new duplicate UserPermission was being created.
If someUser, and it's associated someUser.UserPermission are deserialized entities then you need to do a bit of work to ensure EF is aware that UserPermission is an existing row:
void AddUser(User someUser)
{
var existingPermission = _context.UserPermissions.Local
.SingleOrDefault(x => x.UserPermissionId == someUser.UserPermission.UserPermissionId);
if (existingPermission != null)
someUser.UserPermission = existingPermission;
else
_context.Attach(someUser.UserPermission);
_context.Users.Add(someUser);
_context.SaveChanges();
}
In a nutshell, when working with detached entities that a DbContext may not be tracking, we need to check the Local state for any existing tracked instance for that ID. If we find one, we substitute the detached reference for the tracked one. If we don't find one, we attach the detached one before Adding our user.
This still isn't entirely safe because it assumes that the referenced UserPermission will exist in the database. If for any reason a non-existent UserPermission is sent in (row deleted, or fake data) you will get an exception on Save.
Passing detached entity references around can seem like a simple option at first, but you need to do this for every reference within a detached entity. If you simply call Attach without first checking, it will likely work until you come across a scenario where at runtime it doesn't work because the context happens to already be tracking an instance.

Why does DbSet.Add change properties of other entities?

I have a rare case where the call of DbSet<T>.Add() changes some properties of other entities that are already in the DbSet<T>. Unfortunately, it happens very rarely, and the only evidence I have are some log files, so I have not yet been able to reproduce it locally.
The behavior is like this:
First, we load some entities from the DbSet using a LINQ query.
Then, some of these entities are changed. No SaveChanges() yet.
Now we add some entities by calling DbSet<T>.Add().
Some of the entities of step 2 are changed in step 3 (one foreign-key property of them is set to null).
Any idea? Is that something that can happen on a EF 6 Code-First model?
The only possibility I can think of is that the DbContext refreshes some data from the database, but we don't want it to do that at this point.
EDIT: The code is currently scattered with log statements, since we have been chasing this bug since weeks. These are the relevant code sections:
// parameter: List<Entry> entriesFromUser
var entriesFromDb = db.Entries
.Where(...)
.OrderBy(...)
.ToList();
var newEntries = MergeEntries(entriesFromDb, entriesFromUser);
var propertyBefore = entriesFromDb[0].MyForeignKeyId;
for (var i = 0; i < newEntries.Count; i++)
{
// make sure that the "new entry" is not a modified one
if (entriesFromDb.Contains(newEntries[i])
{
throw new Exception();
}
db.Entries.Add(newEntries[i]);
}
var propertyAfter = entriesFromDb[0].MyForeignKeyId;
Debug.Assert(propertyBefore == propertyAfter); // <=== fails sometimes
db.SaveChanges();
Please note that the changed foreign key is NOT on the entity being added to the DbSet. It's on an entity that comes from the database, but has been changed in the same transaction.
D'oh. Found the reason. Hope it helps someone else.
We are using Foreign Key Associations, which means that we have both the navigation property entry.MyForeignKey and the Foreign Key property entry.MyForeignKeyId, which has many advantages, but it also means you have to be careful when using sometimes this, sometimes that property.
Turns out we had the following assignment somewhere deep in the code, where all the data of one entry is copied to another one:
entry.MyForeignKeyId = otherEntry.MyForeignKeyId
entry.MyForeignKey = otherEntry.MyForeignKey
However, in many scenarios, you set a foreign key value to an entity's MyForeignKeyId but leave the property MyForeignKey null, because the parent entity is not loaded. This is fine as long as you don't assign null to the entity's MyForeignKey property, because it seems that EF would then set MyForeignKeyId to null too.
So it seems that after our code assigned null to MyForeignKey, the entity lingered in memory with a null MyForeignKey and a non-null MyForeignKeyId. As soon as the next DbSet command was executed (the Add() operation), the DbSet noticed that MyForeignKey has received a null assignment, so DbSet went on and assigned null to MyForeignKeyId too.

EF Error - Unable to update the EntitySet because it has a DefiningQuery

I have removed the PrimaryKey from my table, refreshed the EDMX, and now I am getting this error message when doing db.SaveChanges():
Unable to update the EntitySet 'Results' because it has a DefiningQuery and no element exists in the element to support the current operation.
Now...
1 - I dont want PrimaryKey in my table. The table is just a bag of values, no PK is required.
2 - I read another post where someone suggested to remove DefiningQuery element from EF generated EDMX. It is not working, and I avoid manual changes to automatically generated EDMX.
Any idea how I can avoid this error, and not define PK in my table?
Thanks.
In Entity Framework everything must have a key, at least in the model. You can remove it from the database but still you'll have to define a key in the model, which may be a composite key to ensure it is unique. Otherwise EF won't be able to materialize or save objects correctly.
However, I would use a primary key in the table anyway. It is no trouble at all to have an identity field in it and you don't have to worry about finding a unique combination of fields yourself. I can't imagine that the table does not need some notion of identity, or can it really have two exactly identical rows?

EntityCollection Clear() and Remove() methods

What is the right way to delete all of the collection items of an EF entity? In the code below, DocumentItems is the collection of related document items for a document. This code proceedes on Clear() but fails on SaveChanges() because related items are connected to their document via FK and FK is mandatory. So I guess they somehow remain floating up in the air without a foreign key after Clear().
Do I solve this with a foreach loop over the collection calling Remove() on each item or is there another way?
// remove existing document items to prepare for refreshing them
existing.DocumentItems.Clear();
// adds new Document Items
PrepareInvoice(existing, collection);
_repository.SaveChanges();
This is one way of deleting the items in the collection.
VB
TEntityCollection.ToList().ForEach(Sub(o) ctx.DeleteObject(o))
C#
TEntityCollection.ToList().ForEach(x => ctx.DeleteObject(x))
Then you need to call
ctx.SaveChanges()
Clear just removes the reference but doesn't delete the entity.
In your situation
existing.DocumentItems.Clear();
All DocumentItems in the EntitySet will get cleared but you will have to Remove/Delete the actual DocumentItem or the commit with fail, just the same as it would if you tried to delete it in the database.
You need to loop through detach any references, and then delete the entity you wish to remove (unless its nullable and in your situation, it is not)
Alternatively, I have seen implementations that use clear, and an AssociationChangedHandler to automatically delete the old object. Basically, if the change is a "delete/remove" it calls DeleteObject() on the orphaned object.
Trick: When setting up the relationship between Parent and Child, you'll HAVE TO create a "composite" key on the child. This way, when you tell the Parent to delete 1 or all of its children, the related records will actually be deleted from the database.
To configure composite key using Fluent API:
modelBuilder.Entity<Child>.HasKey(t => new { t.ParentId, t.ChildId });
Then, to delete the related children:
var parent = _context.Parents.SingleOrDefault(p => p.ParentId == parentId);
var childToRemove = parent.Children.First(); // Change the logic
parent.Children.Remove(childToRemove);
// or, you can delete all children
// parent.Children.Clear();
_context.SaveChanges();
Done!
Yeah, a year old, but on a minor note... since DeleteObject takes one parameter, which is the same type as the argument for the lambda expression, you can just use:
entityCollection.ToList().ForEach(ctx.DeleteObject);
I am not sure if VB supports a similar syntax, though. Anyone?
Just to answer to Nix comment to the answer,
it seems to me that the EntityCollection.Remove() method only marks for deletion the relationships and not the entities, just as the EntityCollection.Clear() method does.
I know that documentation says that also the entity will be marked for deletion but in my test I've got the behavior I described (anyone can explain me why?).
So, if you have a one to many foreign key constraint in your conceptual model, you cannot save the changes to the context in the persistence store.
The only way I found (since I don't want to CascadeDelete) is looping through the children and invoke context.DeleteObject on each of them, thus removing the entity and the associated relationship.

Are nullable foreign keys allowed in Entity Framework 4?

I have a problem updating a foreign key in an Entity Framework entity. I am using self tracking entities and have an entity with some relations where the foreign key is also present as a property (one of the new features of EF4). The key (an integer) is marked as Nullable and Concurrency Mode fixed.
Specifically I have an Alarm entity with a many to 0..1 relationship to a confirming user. (a user may confirm several alarms, but an alarm can be confirmed by only zero or one users).
The entity definitions (simplified):
Alarm properties
Id Int32 non-nullable identity entity key
UserId Int32 nullable concurrency mode fixed
Alarm navigation properties
User 0..1 multiplicity
User properties
Id Int32 non-nullable identity entity key
Name String non-nullable
In my self tracking entity the confirming user id is auto-generated as a Nullable just as expected, however if I assign a user to an already persistent alarm and run ApplyChanges, the self tracking context extension tries to set the original value (null) in the EF context (In SetValue in the context extensions), but silently skips that because the ClrEquivalentType of the EdmType is a non-nullable Int32.
Auto-generated extension code:
private static void SetValue(this OriginalValueRecord record, EdmProperty edmProperty, object value)
{
if (value == null)
{
Type entityClrType = ((PrimitiveType)edmProperty.TypeUsage.EdmType).ClrEquivalentType;
if (entityClrType.IsValueType &&
!(entityClrType.IsGenericType && typeof(Nullable<>) == entityClrType.GetGenericTypeDefinition()))
{
// Skip setting null original values on non-nullable CLR types because the ObjectStateEntry won't allow this
return;
}
}
int ordinal = record.GetOrdinal(edmProperty.Name);
record.SetValue(ordinal, value);
}
When the EF later tries to update my alarm I get an OptimisticConcurrencyException because it constructs a WHERE clause in the UPDATE statement where it uses 0 (zero) as the original user foreign key value instead of the correct "is null". (The WHERE clause is part of the EF optimistic concurrency mechanism, where the original values of the properties marked with "fixed" concurrency mode are checked agains the properties in the database).
Are nullable foreign keys / primitive types not fully supported in self tracking entities for EF?
If not, am I forced to use dummy entities instead of null or are there other workarounds?
Update
I have tried to reproduce the problem without STE, but plain EF seems to handle optimistic concurrency well for nullable foreign keys, so this is an STE problem, not an EF problem.
There is numerous issues with self tracking entities, so it is not surprising that there is a glitch here. If I find a workaround that can be implemented in the STE T4 script I will post it here.
Bill Huth posted a working patch at MSDN.
Yes, nullable foreign keys are certainly allowed. We use them all over the place. You don't show your database or model, so it's difficult to be certain what the problem could be, but it sounds as though the Entity Framework cannot figure out the primary key of one of the tables involved. Perhaps you don't have one, maybe because one of them is a view? I'm guessing here, because you don't give much information about what you're doing.