简体   繁体   中英

Get DbContext from Entity in Entity Framework Core

Is there a way to get an instance of the DbContext an entity is being tracked by (if any)?

I found the following suggestion/solution for EF6 Get DbContext from Entity in Entity Framework

public static DbContext GetDbContextFromEntity(object entity)
{
    var object_context = GetObjectContextFromEntity( entity );

    if ( object_context == null )
        return null;

    return new DbContext( object_context, dbContextOwnsObjectContext: false );
}

private static ObjectContext GetObjectContextFromEntity(object entity)
{
    var field = entity.GetType().GetField("_entityWrapper");

    if ( field == null )
        return null;

    var wrapper  = field.GetValue(entity);
    var property = wrapper.GetType().GetProperty("Context");
    var context  = (ObjectContext)property.GetValue(wrapper, null);

    return context;
}

Is there a way to get this result in EF Core?

No. EF Core does not have lazy loading yet. If it had, then, a proxy generated from it would eventually have a reference to the DbContext that loaded it. As of now, there is no such reference.

There is no good way to do this. There seems to be no easy way to inject any code into the process after an entity object is constructed but before it is enumerated through in the calling code.

Subclassing InternalDbSet was something I considered but you can only fix calls to the .Find methods and the IQueryable implementation (the main way you'd use a DbSet) is out of reach.

So the only option I can see left is to not allow access to the DbSet at all but have accessor functions which will set the .Owner (or whatever you want to call it) property for me. This is messy since you would normally have to write a function for every query type you'd want to make, and the caller couldn't use LINQ any more. But we can use generics and callbacks to preserve most of the flexibility though it looks ugly. Here is what I came up with.

I am working on porting and cleaning up a complex system so I am not in a position to really test this yet but the concept is sound. The code may need further tweaking to work as desired. This should not have any penalties with eg pulling down the entire table before processing any records as long as you use EnumerateEntities to enumerate, instead of QueryEntities, but again I have yet to do any real testing on this.

    private void InitEntity(Entity entity) {
        if (entity == null) {
            return;
        }
        entity.Owner = this;
        // Anything you want to happen goes here!
    }
    private DbSet<Entity> Entities { get; set; }
    public IEnumerable<Entity> EnumerateEntities() {
        foreach (Entity entity in this.Entities) {
            this.InitEntity(entity);
            yield return entity;
        }
    }
    public IEnumerable<Entity> EnumerateEntities(Func<DbSet<Entity>, IEnumerable<Entity>> filter) {
        IEnumerable<Entity> ret = filter(this.Entities);
        foreach (Entity entity in ret) {
            this.InitEntity(entity);
            yield return entity;
        }
    }
    public T QueryEntities<T>(Func<DbSet<Entity>, T> filter) {
        if (filter is Func<DbSet<Entity>, Entity>) {
            T ret = filter(this.Entities);
            this.InitEntity(ret as Entity);
            return ret;
        }

        if (filter is Func<DbSet<Entity>, IEnumerable<Entity>>) {
            IEnumerable<Entity> ret = filter(this.Entities) as IEnumerable<Entity>;
            // You should be using EnumerateEntities, this will prefetch all results!!! Can't be avoided, we can't mix yield and no yield in the same function.
            return (T)ret.Select(x => {
                this.InitEntity(x);
                return x;
            });
        }

        return filter(this.Entities);
    }
    public void QueryEntities(Action<DbSet<Entity>> filter) => filter(this.Entities);

One could use dependency injection on the instance/entity at creation. To allow the owning dbcontext to be retrieved from the entity later.

eg

class Book
{
  public readonly DBContext _dbcontext;

  public Book(DBContext dbcontext)
  {
    _dbcontext = dbcontext;
  }

}

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