EFCore adding temporary ID by AddAsync() - c#

I am using EFCore 5.0.0.
When I AddAsync (person); I should be getting a temporary ID, and I use this ID to add the PersonId for School (shown in code below). FInally, I will SaveChangesAsync() where everything will be saved. However, the PersonId is set to 0. I want to get the temporary ID stored instead. How can I do this.
await _dbContext.AddAsync(person);
School school = mySchool;
school.PersonId = person.Id;
await _dbContext.AddAsync(school);
await _dbContext.SaveChangesAsync();
Note: There are many SO post that talks about the temporary ID, but none is related to this post.

Currently accepted answer is valid, but technically incorrect. Assigning navigation property is valid approach, but not mandatory. It's even perfectly valid to not have navigation property at all. As well as explicit FK property. But there is always at least shadow FK property which can be used to setup/maintain the relationship.
So the temporary key concept is part of the EF Core from the very beginning. However EF Core 3.0 introduced a breaking change - Temporary key values are no longer set onto entity instances. The link contains an explanation of the old and new behaviors, the reason and possible solutions:
Applications that assign primary key values onto foreign keys to form associations between entities may depend on the old behavior if the primary keys are store-generated and belong to entities in the Added state. This can be avoided by:
Not using store-generated keys.
Setting navigation properties to form relationships instead of setting foreign key values.
Obtain the actual temporary key values from the entity's tracking information. For example, context.Entry(blog).Property(e => e.Id).CurrentValue will return the temporary value even though blog.Id itself hasn't been set.
Bullet #1 makes no sense, Bullet #2 is what is suggested in the other answer. Bullet #3 is the direct answer/solution to your question.
And applying it to your example requires just changing
school.PersonId = person.Id;
to
school.PersonId = _contexy.Entry(person).Property(e => e.Id).CurrentValue;
Of course when you have navigation property and the related entity instance, it's better to use it and let EF Core do its magic. The temporary key is really useful when you don't have navigation property, or you don't have related entity instance and know the key, but don't want to do roundtrip to load it from database (and using fake stub entity instance can lead to unexpected side effects/behaviors). It works well with both explicit and shadow FK properties.

I've never seen linking entities in EF Core using the temporary id.
Typically what you would do is assign the entity and let EF sort out the ids and relationships.
i.e. in this instance, the School will be linked to the Person.
await _dbContext.AddAsync(person);
School school = mySchool;
school.Person = person;
await _dbContext.AddAsync(school);
await _dbContext.SaveChangesAsync();

Related

POCO navigation property not updating when foreign key is assigned a value

I have a POCO class (OPERATION) that is used as an Entity Framework entity. This class has a navigation property (OP) and a foreign key into the same related entity (OP_ID).
In a method, I get an OPERATION and on this OPERATION the OP_ID and OP are both null. When I set the OP_ID to a valid value for this foreign key, the OP navigation property remains null. When I explicitly detect changes in the context, the OP navigation property is now assigned with the correct value.
Sample code
public bool UpdateOperation(operationID)
{
IQueryable<OPERATION> operations = from o in base.ctx.OPERATION
select o;
OPERATION operation = operations
.Where(o => o.OPERATION_ID == operationID)
.Include("OP")
.FirstOrDefault();
if (operation != null)
{
operation.OP_ID = opId;
}
// operation.OP is null here
operation.GetContext().ChangeTracker.DetectChanges();
// operation.OP is populated here
}
I have confirmed that the operation is, in fact, a dynamic proxy. For what it's worth, once I detect changes, operation.OP also becomes a dynamic proxy. However, even then, assigning a different value to operation.OP_ID still requires an explicit DetectChanges() call in order to update the value of operation.OP.
Update
In response to the comment from #ErikPhilips, the documentation here seems to imply that this should happen. Specifically:
The following examples show how to use the foreign key properties and navigation properties to associate the related objects. With foreign key associations, you can use either method to change, create, or modify relationships. With independent associations, you cannot use the foreign key property.
By assigning a new value to a foreign key property, as in the following example.
course.DepartmentID = newCourse.DepartmentID;
...
When you change the relationship of the objects attached to the context by using one of the methods described above, Entity Framework needs to keep foreign keys, references, and collections in sync. Entity Framework automatically manages this synchronization (also known as relationship fix-up) for the POCO entities with proxies.
If you are using POCO entities without proxies, you must make sure that the DetectChanges method is called to synchronize the related objects in the context.
Some additional context may be useful, as well. This is a legacy application that used to work directly with an ObjectContext instead of a DbContext, though even then using EF 6. We are now migrating to the DbContext API. This particular code, without any modifications, used to demonstrate the behavior I'm expecting. Specifically, when OP_ID is assigned, I can see in the debugger that the OP property is automatically populated to point to the correct OPERATION.
In the end, I was doing exactly what the documentation described. I was
assigning a new value to a foreign key property.
Yes, Entity Framework does manage this in fix-up. And yes, the documentation does state this.
It turns out, though, that the egg is ultimately on my face. I had checked the classes generated from my T4 template, and seen that all navigation properties were marked virtual. I had not checked thoroughly enough to note that the foreign key properties were not marked virtual, however. It appears that this is the default behavior of the EF-provided T4 template used when working model- or database-first. I've addressed this by changing this line in the CodeStringGenerator.Property() method in the T4 template
Accessibility.ForProperty(edmProperty)
to
AccessibilityAndVirtual(Accessibility.ForProperty(edmProperty))
In the end, as usual, following the documentation (here, the requirements for EF change tracking on POCOs) often results in dependent code behaving as it is documented. Shame on me.

EF6: Modifying an entity property with a foreign key relation - Do I need to change the Id or the related object or both?

I am modifiying the foreign key property on an entity in code, by modifiying the Id only:
ElementData.ServiceLevelId = parameter.ServiceLevelId;
I have found, after persisting, that this only works as expected, when the corresponding navigation property ServiceLevel was null by accident. If it still holds the "old" object, the change will not hit the database.
This means, I need to do
ElementData.ServiceLevelId = parameter.ServiceLevelId;
ElementData.ServiceLevel = null; //Force the update to the Database
Does that mean, that changing the object is "stronger" than changing the id only? Should I always set the related object to null in such situations?
Update (per Tim Copenhaver's comment): The entity in question is a copy (with the mentioned modification) of an existing one. It uses Automapper for copying, and maps everything except the primary key and one unrelated property. Automapper creates a shallow copy AFAIK. Thus, the situation for the copy will be that the updated Id and the untouched object reference will not match at the moment of adding it to the context. I guess, that EF then decides that the "object reference is stronger".
Changing either property will work as long as your data mapping is correct. EF is smart enough to see which of the properties has changed and ignore the other one. You have to be careful, though - if ElementData.ServiceLevel.Id does not equal ElementData.ServiceLevelId, you will get some obscure errors.
If you're having trouble with it not saving, your mapping layer is probably not correct. We can help troubleshoot if you can post the mapping for your ElementData class and some more code around how you're doing the save.

NavigationProperty null on added foreign key

Whenever I add a foreign key entity to my previous entity by setting the ForeignKey-ID, the associated object is null.
Let me explain this:
In a previous step I've set the AddressId property to 28 and have saved the entity context by calling context.SaveChanges().
Now why is AddressId filled, but Address as the NavigationProperty (which should be an Address object of the Address table where Address.Id == 28) is null?
Entity Frameworks (EF) work this by design.
Updating the foreign key never updates the navigation property.
However, updating the navigation property will update the key. Also note that in this case the Address entity should come from the same context. If not .SaveChanges() will consider the Address entity as new and try to add it in the database.
As to the question of which method is better, well, it depends!
- Updating the Key is straightforward and is what we have been doing all along using Data Transfer Objects (DTOs) or even plain SQL. So is easier for newcomers to EF to grasp and use.
- Updating the navigation property is where you truly get an object based data model. The code looks cleaner and more readable. However you need to be very careful with the Context. In my little personal experience with EF, I find that trying to update the navigation property brings more complexity than value, especially in a multi-tier architecture where the Context is hidden behind the Data Access layer.
The most important benefit of EF, in my opinion, is in query operations using LINQ-to-Entities. I have compile-time syntax check for my queries and strong typing. I can easily create an object-based result set with multiple levels of children, data-bind ready without any additional code. I rarely write SQL anymore.
I sorta fixed this by re-creating my entities and reloading then. This seems to work and re-fetch the n:m relationship navigation properties. Weird.

Removing navigation properties from POCO-classes in Entity Framwork

I'm using generated POCO classes and Entity Framework.
In order to make the code less complex I'm trying to remove all navigation properties from the code while still keeping the Foreign Key constraints in the database (navigation properties do more harm than good for us).
If I remove them manually from the POCO-classes I get the following error
The entity type UserEntity is not part of the model for the current context
If I try to remove them from the .edmx-file I get the following error:
Error 3 Error 3015: Problem in mapping fragments starting at lines 479, 562:Foreign key constraint 'fk_StorageContracts_User1' from table StorageContract (OwnerUserID) to table User (ID):: Insufficient mapping: Foreign key must be mapped to some AssociationSet or EntitySets participating in a foreign key association on the conceptual side.
Is there any way to remove navigation properties from POCO-classes without removing the corresponding FK?
I know this is old, but, since there is still no answer, I thought I'd give it a try:
I am still working in EF 4.0, but, following the example to which you referred, you have an xxxModel.tt. If you are willing to tweak that, you can find where it generates the Navigation Properties and change them to be simple auto-properties. I had a similar project where I generated them like this:
public List<NavDataX> NavDataXs
{
get; set;
}
Now, they are still there, but they are null until you explicitly set them. Doing it this way, I did not mess with the EDMX and did not encounter the two errors you mentioned.

Entity Framework 4.1 - default EntityState for a FK?

I'm having a small problem with ASP.NET MVC and Entity Framework 4. I have an entity called "UF" and another one called "Pais", and they have this relation:
UF [* ... 0..1] Pais
I can access the Pais object directly from UF using a navigation property:
UF.Pais.Whatever = "foobar";
Currently I have a View which inserts a new item into the database, and it has an editor for "Pais.Codigo" ("Codigo" is the primary key for Pais). So when I insert a new UF, the framework creates an instance of class UF with a reference to an instance of class Pais. Then this is done:
if (ModelState.IsValid)
{
db.UFs.AddObject(uf);
db.SaveChanges();
return View();
}
The problem is that the EF is inserting a new Pais into the database, so it basically ignores the existing one.
For example, if let's say my object UF has a Pais with an ID of 1. The current value of uf.Pais.Codigo is 1. Other attributes, like the description, is currently null. When I execute the SaveChanges, both "uf" and "uf.Pais" are with the state of Added. The correct state for "uf.Pais" should be Unchanged, since it already exists on the database.
My question is: there's some way of changing the default relationship EntityState for Unchanged? The following code solves the problem, but adding it to each function with adds a new entry to the database (and for each FK) is overkill!
db.ObjectStateManager.ChangeObjectState(uf.Pais, EntityState.Unchanged);
That's it. I'm not sure if I was clear enough. Feel free to ask more information if needed. And sorry for any english mistakes!
Thanks,
Ricardo
PS: "Pais" stands for Country and "UF" for State.
My question is: there's some way of changing the default relationship
EntityState for Unchanged?
Yes by calling Attach instead of Unchanged.
The following code solves the problem, but adding it to each function
with adds a new entry to the database (and for each FK) is overkill!
No it is not overkill, it is a solution because either Attach or AddObject will always make the operation for all entities and associations in entity graph. That means that calling AddObject will make everything what context is not aware of yet as Added and Attach will make everything what context is not aware of as Unchanged (so you will in turn have to set each modified or inserted entity to its correct state). That is how EF works if you are using detached entities.
Another solution for the problem is making the connection after the UF is added:
// Here UF.Pais is null
db.UFs.AddObject(uf);
// Create dummy Pais
var pais = new Pais { Id = "Codigo" };
// Make context aware of Pais
db.Pais.Attach(pais);
// Now make the relation
uf.Pais = pais;
db.SaveChanges();
If you are working with detached entities you are always responsible for setting the correct state for each entity (and independent association). So you will either use attached entities to let EF make the magic for you (as shown in the example) or you will use the approach you dislike. In more complex scenarios you can find out that best approach is to load entity graph again and merge incoming changes into the attached graph.

Categories