38

在我的应用程序中,有时需要在一次操作中将 10,000 或更多行保存到数据库中。我发现一次简单地迭代和添加每个项目可能需要半个小时以上。

但是,如果我禁用 AutoDetectChangesEnabled 大约需要 5 秒(这正是我想要的)

我正在尝试为 DbSet 创建一个名为“AddRange”的扩展方法,该方法将禁用 AutoDetectChangesEnabled,然后在完成后重新启用它。

public static void AddRange<TEntity>(this DbSet<TEntity> set, DbContext con, IEnumerable<TEntity> items) where TEntity : class
    {
        // Disable auto detect changes for speed
        var detectChanges = con.Configuration.AutoDetectChangesEnabled;
        try
        {
            con.Configuration.AutoDetectChangesEnabled = false;

            foreach (var item in items)
            {
                set.Add(item);
            }
        }
        finally
        {
            con.Configuration.AutoDetectChangesEnabled = detectChanges;
        }
    }

所以,我的问题是:有没有办法从 DbSet 中获取 DbContext?我不喜欢把它作为参数——感觉它应该是不必要的。

4

4 回答 4

29

使用 Entity Framework Core(使用 2.1 版测试),您可以使用获取当前上下文

// DbSet<MyModel> myDbSet
var context = myDbSet.GetService<ICurrentDbContext>().Context;

如何从 EntityFramework Core 2.0 中的 DbSet 获取 DbContext

于 2018-06-14T19:12:25.117 回答
27

是的,您可以DbContext从 aDbSet<TEntity>获得,但解决方案是反射很重。我在下面提供了一个如何执行此操作的示例。

我测试了以下代码,它能够成功检索生成的DbContext实例DbSet。请注意,虽然它确实回答了您的问题,但几乎可以肯定有更好的解决方案来解决您的问题

public static class HackyDbSetGetContextTrick
{ 
    public static DbContext GetContext<TEntity>(this DbSet<TEntity> dbSet)
        where TEntity: class
    { 
        object internalSet = dbSet
            .GetType()
            .GetField("_internalSet",BindingFlags.NonPublic|BindingFlags.Instance)
            .GetValue(dbSet);
        object internalContext = internalSet
            .GetType()
            .BaseType
            .GetField("_internalContext",BindingFlags.NonPublic|BindingFlags.Instance)
            .GetValue(internalSet); 
        return (DbContext)internalContext
            .GetType()
            .GetProperty("Owner",BindingFlags.Instance|BindingFlags.Public)
            .GetValue(internalContext,null); 
    } 
}

示例用法:

using(var originalContextReference = new MyContext())
{
   DbSet<MyObject> set = originalContextReference.Set<MyObject>();
   DbContext retrievedContextReference = set.GetContext();
   Debug.Assert(ReferenceEquals(retrievedContextReference,originalContextReference));
}

解释:

根据 Reflector 的说法,它有一个类型DbSet<TEntity>为私有的字段。该类型在 EntityFramework dll 内部。它继承自(where )。 也是EntityFramework dll的内部。它有一个类型为私有的字段。 也是 EntityFramework 内部的。但是,公开了一个名为. 因此,如果您有,则可以通过反射性地访问每个属性并将最终结果转换为 来获取对所有者的引用。 _internalSetInternalSet<TEntity>InternalQuery<TElement>TEntity : TElementInternalQuery<TElement>_internalContextInternalContextInternalContextInternalContextDbContextOwnerDbSet<TEntity>DbContextDbContext

@LoneyPixel更新

在 EF7 中,在实现 DbSet 的类中直接有一个私有字段 _context。公开这个领域并不难

于 2013-07-18T00:31:47.940 回答
16

为什么要在 DbSet 上这样做?尝试在 DbContext 上执行此操作:

public static void AddRangeFast<T>(this DbContext context, IEnumerable<T> items) where T : class
{
    var detectChanges = context.Configuration.AutoDetectChangesEnabled;
    try
    {
        context.Configuration.AutoDetectChangesEnabled = false;
        var set = context.Set<T>();

        foreach (var item in items)
        {
            set.Add(item);
        }
    }
    finally
    {
        context.Configuration.AutoDetectChangesEnabled = detectChanges;
    }
}

然后使用它很简单:

using (var db = new MyContext())
{
    // slow add
    db.MyObjects.Add(new MyObject { MyProperty = "My Value 1" });
    // fast add
    db.AddRangeFast(new[] {
        new MyObject { MyProperty = "My Value 2" },
        new MyObject { MyProperty = "My Value 3" },
    });
    db.SaveChanges();
}
于 2013-07-18T02:00:32.323 回答
0

也许您可以创建一个为您禁用此功能的助手,然后从 AddRange 方法中调用该助手

于 2013-07-18T00:03:13.467 回答