C# 您可以从数据库集获取DbContext吗?

C# 您可以从数据库集获取DbContext吗?,c#,entity-framework,entity-framework-4.1,dbcontext,dbset,C#,Entity Framework,Entity Framework 4.1,Dbcontext,Dbset,在我的应用程序中,有时需要在一次操作中将10000行或更多行保存到数据库中。我发现,简单地一次迭代并添加一个项目可能需要半个小时以上的时间 但是,如果禁用AutoDetectChangesEnabled,则需要约5秒钟(这正是我想要的) 我正在尝试为DbSet创建一个名为“AddRange”的扩展方法,该方法将禁用AutoDetectChangesEnabled,然后在完成后重新启用它 public static void AddRange<TEntity>(this DbSet&l

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

但是,如果禁用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;
        }
    }
publicstaticvoidaddrange(此DbSet集合、DbContext con、IEnumerable项),其中tenty:class
{
//禁用自动检测速度变化
var detectChanges=con.Configuration.AutoDetectChangesEnabled;
尝试
{
con.Configuration.AutoDetectChangesEnabled=false;
foreach(项目中的var项目)
{
设置。添加(项目);
}
}
最后
{
con.Configuration.AutoDetectChangesEnabled=detectChanges;
}
}

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

也许您可以创建一个帮助器来为您禁用此功能,然后只需在AddRange方法中调用该帮助器

是的,您可以从
DbSet
获取
DbContext
,但解决方案需要大量的反射。我在下面提供了一个如何做到这一点的示例

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

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); 
    } 
}
公共静态类HackyDbSetGetContextTrick
{ 
公共静态DbContext GetContext(此DbSet DbSet)
地点:班级
{ 
对象内部集=dbSet
.GetType()
.GetField(“_internalSet”,BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(dbSet);
对象internalContext=internalSet
.GetType()
.基本类型
.GetField(“_internalContext”,BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(内部设置);
返回(DbContext)内部上下文
.GetType()
.GetProperty(“所有者”,BindingFlags.Instance | BindingFlags.Public)
.GetValue(内部上下文,null);
} 
}
用法示例:

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

根据Reflector,
DbSet
具有类型为
internalSet
的私有字段
\u internalSet
。该类型是EntityFramework dll的内部类型。它继承自
InternalQuery
(其中
TEntity:TElement
)<代码>内部查询也是EntityFramework dll的内部查询。它有一个类型为
internalContext
的私有字段
\u internalContext
<代码>内部上下文也是EntityFramework的内部。但是,
InternalContext
公开一个名为
Owner
的公共
DbContext
属性。因此,如果您有一个
DbSet
,您可以通过反射性地访问每个属性并将最终结果强制转换为
DbContext
来获得对
DbContext
所有者的引用

更新自
在EF7中,在实现DbSet的类中直接有一个私有字段\u上下文。公开这个字段并不难

为什么要在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;
    }
}

使用EntityFrameworkCore(用版本2.1测试),您可以使用

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

哇,非常感谢。这是一段令人印象深刻的代码。然而,我同意你关于内部和反射的警告,我决定使用@TimothyWalters的答案,因为这似乎是一个“官方支持的”routeAwesome代码片段。我相信从集合中获取DbContext有一些合法的用途。在类中有一个私有字段
\u context
,它直接实现了
DbSet
。公开这个字段并不难。事实上,正如我现在听到的,这应该可以通过EF7实现:
var myContext=mySet.GetService()我现在无法测试它。有人能证实这一点吗?@ygoe你的最后一句话取决于用户如何配置他们的项目,在我的情况下没有,那不起作用,非常感谢!只是想让你知道我将使用这个答案,但是我已经将smartcaveman的答案标记为已接受的答案,因为它实际上回答了我的问题(即使答案没有你的那么有用)。我发现正确的答案是首先重新思考你的问题,在这种情况下,我是根据你的目标思考的,而不是你想如何实现它。我相信这是“跳出框框思考”的核心原则,这是一项很好的学习技能。为什么要使用
DbSet
而不是
DbContext
?因为它更容易使用。想想
context.Entities.Action()
(你已经习惯了)而不是
context.Action()
(丑陋的角度混合)。如果上下文实例更容易从那里访问,那么它肯定是更好的解决方案。如果有该类型的参数,则可以使用类型推断,但更不明显的是,该方法使用的是哪种类型的实体。警告:从.NET Core 3.1开始,这将无法工作,这将导致运行时异常。我没有使用3.x版本对其进行测试,但根据文档,它仍然可以工作
// DbSet<MyModel> myDbSet
var context = myDbSet.GetService<ICurrentDbContext>().Context;