简体   繁体   中英

.NET 6 Entity Framework Tracking with Dependency Injection

I guess I just don't understanding EF tracking. I have the context added via dependency injection via:

builder.Services.AddDbContext<OracleContext>(options => options.UseOracle(OracleConnectionString, b => b.UseOracleSQLCompatibility("11"))
                          .LogTo(s => System.Diagnostics.Debug.WriteLine(s))
                          .EnableDetailedErrors(Settings.Dev_System)
                          .EnableSensitiveDataLogging(Settings.Dev_System)
                          .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));

I set the tracking behavior to NoTracking here (at least so I thought).

I have a .NET Controller that has the context in its constructor. It passes this context to my class constructor containing my methods. Pretty much everything works fine... except for one:

I have a method that does a context.RawSqlQuery to get a list of objects. I iterate over these objects calling two separate methods from a different class that was generated the same way (using the injected context). This method first does a EF query to verify the object does not already exist, if it does it returns it and we move on - no issues. On the query to check if it exists I also added.AsNoTracking() for SnGs. However, if the object does not exist, and I try to make a new one... every time I do an context.add I get

"The instance of entity type 'Whatever' cannot be tracked because another instance with the key value '{MfrId: 90}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached."

I have tried adding db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking - no change I have tried adding context.Entry(NewItem).State = EntityState.Detached; before and after the call - no change. I tried a loop in the context that gets all tracked objects and sets them to detached - no change.

What am I missing here? First - why is it tracking at all? Second - any suggestions on how to get passed this? Should I just give up using dependency injection for the context (suck... lots of rework for this)?

As requested - here is the class & method that is failing (non related stuff removed):

public class AssetMethods : IAssetMethods
    {
        public OracleContext db; 
        

        public AssetMethods(OracleContext context)
        {
            db = context;   
        }
 
        public CcpManufacturer? CreateNewManufacturer(CcpManufacturer NewMan, string ActorID)
        {
            ...blah blah non DB validation stuff removed...

            //Check if it exists already
            CcpManufacturer? OldMan = db.CcpManufacturers.Where(m=>m.MfrName == NewMan.MfrName).AsNoTracking().FirstOrDefault();
            if (OldMan != null) { 
                return OldMan;
            }

            //Who done did it
            NewMan.CreatedBy = ActorID;
            NewMan.CreationDate = DateTime.Now;
            NewMan.Isenabled = true;            

            //save                         
            db.CcpManufacturers.Add(NewMan);           
            db.SaveChanges(); //fails here

            //Prevent XSS Reflection
            return db.CcpManufacturers.Find(NewMan.MfrId);
        }
    }

this method is called from this code. The OM is also using the injected context

List<MasterItem> Items = OM.RawSqlQuery(Query, x => new MasterItem { MFR_NAME = (string)x[0], MODEL_NUMBER = (string)x[1], LEGAL_NAME= (string)x[2]});

            foreach (MasterItem item in Items)
            {
                CcpManufacturer? Man = new() {
                      MfrName = item.MFR_NAME,
                      Displayname = item.MFR_NAME
                };

                Man = AM.CreateNewManufacturer(Man,System.Id); //if its new.. it never get passed here because of the discussed error...
                if (Man == null || Man.MfrId == 0)
                {
                    continue;
                }
                .... other stuff
             }


So the mfr id is added to a new object that's passed to a pretty much identical methods to create a item (where the mfr id is attached). Now - if I detach THAT item - I am ok. But why is it tracking when I have it turned off pretty much everywhere?

Yes, you found your problem.

Turning off Tracking affects what EF does when querying for entities. This means when I tell EF to read data from the DB and give me entities, it will not hang onto references of those entities.

However, entities you tell a DBContext to ADD to a DbSet and related entities will be tracked, regardless of your tracking setting.

So if I do something like:

var entity1 = context.Entities.Single(x => x.Id == entityId).AsNoTracking();

var entity2 = context.Entities.Single(x => x.Id == entityId).AsNoTracking();

The references to entity1 and entity2 will be 2 distinct references to the same record. Both are detached, so the DbContext isn't tracking either of them. I can use and attach either of them to perform an update, but that entity would be from that point considered Attached until I explicitly detach it again. Attempting to use the other reference for an update would result in that error. If I query specifying NoTracking after I have attached and updated that first entity reference, I will get back a new untracked entity reference. The DbContext doesn't return it's tracked reference, but it doesn't discard it either.

The exact same thing happens if I add a new entity then query for it specifying NoTracking. The query returns an untracked reference. So if you try and attach it to update a row, EF will complain about the reference it is already tracking.

I don't recommend diving down the rabbit hole of passing around detached entities unless you're keen to spend the time to really understand what is going on behind the scenes and prepared for the pretty deliberate and careful handling of references. The implications aren't just things not working as expected, it's having things work or not work on a completely situational basis which can be a nasty thing to debug and fix, even when you know what to look for.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM