EF Core: updating an entity without querying it first - entity-framework-core

I'm looking for a way to update a property of an entity by knowing its primary key, without first querying it.
The solution I've come up with is this one:
var order = new OrderEntity()
{
Id = 5
};
db.Orders.Attach(order).State = EntityState.Unchanged;
order.Name = "smth";
db.SaveChanges();
Which seems to work fine, as the generated SQL is exactly what I expect:
UPDATE "Orders" SET "Name" = #p0
WHERE "Id" = #p1;
Question: is this the correct way of doing it?
I can't find any confirmation about this in the official documentation, or anywhere else on the web actually. Similar questions are about Entity Framework (non-Core) and they seem to use different strategies, like setting EntityState.Modified instead of Unchanged. I tried that as well but it has the effect of updating all the properties, which is not what I want to achieve. So I'm wondering if there's something I'm missing about the solution above.
Thanks.

The documentation for DbContext.Attach Method says:
Begins tracking the given entity and entries reachable from the given entity using the Unchanged state by default, but see below for cases when a different state will be used.
[...]
For entity types with generated keys if an entity has its primary key value set then it will be tracked in the Unchanged state. If the primary key value is not set then it will be tracked in the Added state.
[...]
For entity types without generated keys, the state set is always Unchanged.
[...]
So setting the state to Unchanged is not even necessary.
But be careful. If you set e.g. order.Amount = 0m; to clear the amount, this will not work, as no change will be detected. Write
var order = new OrderEntity()
{
Id = 5,
Amount = 1.00m; // Dummy value unequal 0.00m
};
db.Orders.Attach(order);
// Make the change
order.Amount = 0.00m; // Now, this change will be detected.
db.SaveChanges();

Related

Marking navigation property as modified in Entity Framework 7

I have an EF7 DbContext with disabled change tracking because I want to mark all changes explicitly:
var entry = context.Entry(changedEntity);
entry.Property(propertyName).IsModified = true;
This works exactly as I want it to.
However, this does not work when a reference (navigation property) has been updated.
For example, let's say my entity has two properties - ParentId and Parent where ParentId is a foreign key and Parent is the reference to the parent entity.
Calling
entry.Property("Parent").IsModified = true;
does not work and throws ModelItemNotFoundException because Parent is not a property of the entity in terms of EF (it is a navigation instead).
In EF6, this could be done as follows:
var reference = context.Entry(changedEntity).Reference("Parent");
reference.CurrentValue = reference.CurrentValue;
IN EF7, there is no such function. I can get to the INavigation object with
entry.Metadata.GetNavigation("Parent")
but cannot see any way to mark it as modified.
So, how should I do it?
Note:
I know that setting ParentId would work, but this is not suitable for me because the referenced entity does not have ID yet as it has just been created and will get its ID from database when saved. Thus, I need to set it through the reference Parent.
Edit:
The note above was true for EF6 but is no longer valid for EF7 which I was not aware of. Thus, the solution is just as described in the note and answer below.
Wow, it seems that when a new entity is attached to the DbContext, it actually gets ID (-1 in my case). So, I can set ParentId even when the target entity is new and has not been added to the database yet.
I wonder whether there are any checks that the new ID is unique (there could already be an entry with ID -1 in database).

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.

Entity Framework object graph deletion with Breeze

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..

How do I get entity framework to let me set a storegeneratedpatternfield?

I have a field in one of my entities that is being used as a id field. The issue is that this particular field can have duplicates in it due to support versioning data support. I have the field set to populate when it is null using the storegeneratedpatternfield = identity. I need to be able to set the field when I am dealing with a new version of the field.
//so some basic psuedo code when I update.
entity myentity = //find the entity via linq
myentity.version = //next version number
db.entitytable.add(new entity() { entityid = myentity.entityid, etc.});
savechanges
what happens right now is it ignores me setting the field if I use storegeneratedpatternfield = identity or storegeneratedpatternfield = computed
I have a workaround where I look up the new entity but I was wondering if there is another option.

Entity Framework, changing EntityKey leaves Entity as "UnChanged"

I have an entity with a status property that I would like to update.
I would like to do the following:
const int NEW_STATUS = 2;
myEntity.StatusReference.EntityKey = new EntityKey("SetName", "KeyName", NEW_STATUS);
When this is passed to the context, its state is "UnChanged", despite me changing the relationship! This means the save will not be persisted.
The entity comming in is from a different context to the one that its being attached to and saved.
Anyone know how I can update just the entitykey and persist it!?
Thanks in advance,
David
You can't. EntityKeys are designed to be mapped to primary keys, which, in any good DB design, will never change. If you've mapped your EntityKey to something which is not a PK, change it to the PK. If your DB design calls for PKs to change, please reconsider that design. (Removed after you changed the question.)
Added, upon re-reading the question: Are you actually wanting to update the EntityKey of the entity, or do you just want to change the status property? If the latter, try one of the following:
entity.Status = someStatusInstance;
...or...
entity.StatusReference.EntityKey = myEntity.EntityKey = new EntityKey("SetName", "KeyName", NEW_STATUS);
If your entity state isn't modified, you probably have the order of operations wrong when adding to/saving the context. You would need to show that when asking for help on it.