Entity Framework: Attaching related objects and other state management - c#

I have an application which uses Entity Framework Code First. I am attempting to write my resource access layer. I have several objects which all have separate database tables and a lot of object relationships. Can someone point me to an up-to-date example of CRUD methods with related objects? Everything I have found uses an older version (I use DbContext, not ObjectContext, etc.) and I am having problems writing it myself.
For example, I am currently working on an object with a parent-child relationship with itself. I am attempting to write the Create method. If I use context.Objects.Add(newObject) then all the children objects also have their state changed to Added, which means that duplicate children are added. So I tried looping through all the children and attaching them to the context, but then any children that did not previously exist do not get added to the database and a DbUpdateException is thrown.
Is there a generic way I can attach all related entities and have their states be correct? Any help you can give me would be appreciated. Thanks!
Edit:
I tried explicitly loading the children using Load() and then adding the initial object. Unfortunately, it caused an exception because the parent comment had the child in its list of children but the parentID of the existing child had not yet been updated.

No there is no way to attach whole graph and let EF automatically set correct state - these problems didn't changed since ObjectContext API. You must always set state manually for each entity and relation or you must build the graph from attached entities. The only exception are Self tracking entities but they are not supported with DbContext API.

Related

EF Core is sharing tracked entities across instances

I am using EF Core 3.1.26 with EntityFrameworkCore.Jet 3.1.1, using MS Access for the database. I would update EF Core, but then it wouldn't be compatible with EntityFrameworkCore.Jet.
My problem is that there seems to be some kind of static cache shared across instances. When after creating a instance I attach a entity, it throws an error because the entity is already being tracked, even though it is the first entity attached to that context (the queries are marked with AsNoTracking). I even checked with the debugger that there are no entities in the Entries property of the Change Tracker.
Even when I execute SaveChanges(), entities are updated that shouldn't exist in that context, since I made no query whatsoever. In the Entries property there is only the entity I attached with is navigation properties to null. And yet a bunch of entities of different tables are updated. Those entities I did query, but in another context and with AsNoTracking.
I have no idea where EF Core or EntityFrameworkCore.Jet are getting this entities from. It is like Attach and SaveChanges are accessing a static Change Tracker, and I don't know how to solve this problem.
Thank you.
Edit: It was my mistake, I didn't check correctly.

ef core best practice to update complexe objects

We are leading into some issues with ef-core on sql databases in a web-api when trying to update complexe objects on the database provided by a client.
A detailed example: When receiving an object "Blog" with 1-n "Posts" from an client and trying to update this existing object on database, should we:
Make sure the primary keys are set and just use
dbContext.Update(blogFromClient)
Load and track the blog while
including the posts from database, then patch the changes from
client onto this object and use SaveChanges()
When using approach (1) we got issues with:
Existing posts for the existing blog on database are not deleted
when the client does not post them any more, needing to manually
figure them out and delete them
Getting tracking issues ("is already been tracked") if
dependencies of the blog (for example an "User" as "Creator") are
already in ChangeTracker
Cannot unit test our business logic without using a real DbContext
while using a repository pattern (tracking errors do just not exist)
While using a real DbContext with InMemoryDatabase for tests cannot rely on things like foreign-key exceptions or computed
columns
when using approach (2):
we can easily manage updated relations and keep an easy track of
the object
lead into performance penalty because of loading the
object which we do not really need
need to map many manual things
as tools like AutoMapper cannot be used to automaticlly map
objects with n-n relations while keeping a correct track by ef core (getting some primary key errors, as some objects are deleted from lists and are added again with the same primary
key, which is not allowed as the primary key cannot be set on insert)
n-n relations can be easily damaged by this as on database
there could be n-n blog to post, while the post in blog does hold
the same relation to its posts. if only one relation is (blog to
post, but not post to blog - which is the same in sql) is posted and
the other part is deleted from list, ef core will track this entry
as "deleted".
in vanilla SQL we would manage this by
deleting all existing relations for the blog to posts
updating the post itself
creating all new relations
in ef core we cannot write such statements like deleting of bulk relations without loading them before and then keeping detailed track on each relation.
Is there any best practice, how to handle an update of complexe objects with deep relations while getting the "new" data from a client?
The correct approach is #2: "Load and track the blog while including the posts from database, then patch the changes from client onto this object and use SaveChanges()".
As to your concerns:
lead into performance penalty because of loading the object which we do not really need
You are incorrect in assuming you don't need this. You do in fact need this because you absolutely shouldn't be posting every single property on every single entity and related entity, including things that should not be be changed like audit props and such. If you don't post every property, then you will end up nulling stuff out when you save. As such, the only correct path is to always load the full dataset from the database and then modify that via what was posted. Doing it any other way will cause problems and is totally and completely 100% wrong.
need to map many manual things as tools like AutoMapper cannot be used to automaticlly map objects with n-n relations while keeping a correct track by ef core
What you're describing here is a limitation of any automatic mapping. In order to map entity to entity in collections, the tool would have to somehow know what identifies each entity uniquely. That's usually going to be a PK, of course, but AutoMapper doesn't (and shouldn't) make assumptions about that. Instead, the default and naive behavior is to simply replace the collection on the destination with the collection on the source. To EF, though, that looks like you're deleting everything in the collection and then adding new items to the collection, which is the source of your issue.
There's two paths forward. First, you can simply ignore the collection props on the source, and then manually map these. You can still use AutoMapper for the mapping, but you'd simply need to iterate over each item in the collection individually matching it with the appropriate item that should map to it, based on your knowledge of what identifies the entity (i.e. the part AutoMapper doesn't know).
Second, there's actually an additional library for AutoMapper to make this easier: AutoMapper.Collection. The entire point of this library is to provide the ability to tell AutoMapper how to identify your entities, so that it can then map collections correctly. If you utilize this library and add the additional necessary configuration, then you can map your entities as normal without worrying about collections getting messed up.

How to add an entity without related entities, but saving relation?

As far as I understand, if I change a state of an entry in context like that:
context.Entry(doc).State = EntityState.Added;
the whole object graph behind doc will be set to EntityState.Added. That is how this mechanism described here:
Note that for all of these examples if the entity being added has
references to other entities that are not yet tracked then these new
entities will also be added to the context and will be inserted into
the database the next time that SaveChanges is called.
In my situation this behaviour is undesirable. When I receive doc entity, it's relations are already in database (were added in different context) and adding them again will cause an error. I need to add doc to a database with all references, but don't try to add other objects in graph.
Of course, I can iterate through all graph and set state explicitly, but does an easier way exist?
In Entity Framework Core, the behavior changed, calling:
context.Entry(asset).State = EntityState.Added;
will affect only the entity and not the related ones.
👉 I know the question is for Entity Framework classic (not Core), but surely will be more people using EF Core reaching here (like me) 😉
You may have a look at GraphDiff
According to this dedicated blog entry, it seems to fit your needs :
Say you have a Company which has many Contacts. A contact is not
defined on its own and is a One-To-Many (with required parent) record
of a Company. i.e. The company is the Aggregate Root. Assume you have
a detached Company graph with its Contacts attached and want to
reflect the state of this graph in the database.
At present using the Entity Framework you will need to perform the
updates of the contacts manually, check if each contact is new and
add, check if updated and edit, check if removed then delete it from
the database. Once you have to do this for a few different aggregates
in a large system you start to realize there must be a better, more
generic way.
Well good news is that after a few refactorings I've found a nice solution to this problem.

Entity framework auto eager load

Is there a way to get entity objects to automatically pull all relevant data through the relationships instead of having having to .Include everything I want populated? I can't use lazy loading as this needs to be serialized to go over WCF. It would just be fantastic to have it auto populate the relevant collections.
No there is no build in feature which will automatically eagear load whole object graph. You must always explicitly say which navigation properties you want to eager load (by using Include method or LoadProperty method).
Edit:
Based on your comment: Generally it should be possible to build some auto loader - but it will not be easy. ObjectContext has MetadataWorkspace property which contains all metadata about your entities. You can load all information about your entities from metadata and add needed inclueds to the query. I expect one problem - you must somehow handle cyclic references. If you need some example how to extract information about entities check T4 POCO generation template.
I came across this querstion recently because I was looking for something similar.
Since EF Core 6 there is a AutoInclude method that configures whether a navigation should be included automatically.
This can be done in the OnModelCreation method in the DbContext class:
modelBuilder.Entity<Theme>().Navigation(e => e.ColorScheme).AutoInclude();
This would load the ColorScheme for every Theme when running the query:
var themes = context.Themes.ToList();
See Model configuration for auto-including navigations

Issue adding an inherited object in Entity Framework

I am working on a small project using Entity Framework. I have a Group entity and a MissionTrip:Group entity (and also a SmallGroup:Group entity but that shouldn't be necessary information). I am using Table-Per-type inheritance set up analogously to the inheritance in this blog post by Muhammed Mosa.
I can't save a MissionTrip to the GroupSet using AddObject("GroupSet", missionTrip) or AddToGroupSet(missionTrip).
The MissionTrip object I am trying to save is a new, valid MissionTrip. The AddObject method does not throw any exception or give any indication that it did not work yet the object is not persisted to the database.
I am able to save un-hierarchical objects to the database using the exact same method but with correct results.
Let me know if more information would be helpful, any help would be appreciated
Edit
If it clarifies anything I am using the MySQL Data Provider.
I am able to retrieve MissionTrip typed objects from the GroupSet but am unable to add them. I cannot save instances of the other inherited type (SmallGroup) either.
I figured it out. The problem wasn't related to inheritance at all. I was calling SaveChanges from a different repository than the one that was tracking the change. I'm still pretty new to EF.

Categories

Resources