什么时候应该创建一个新的DbContext()


83

我目前正在使用DbContext类似的方法:

namespace Models
{
    public class ContextDB: DbContext
    {

        public DbSet<User> Users { get; set; }
        public DbSet<UserRole> UserRoles { get; set; }

        public ContextDB()
        {

        }
    }
}

然后,在需要访问数据库的所有控制器的顶部使用以下行。我还在UserRepository类中使用了它,该类包含与用户有关的所有方法(例如,获取活动用户,检查他所扮演的角色等)。

ContextDB _db = new ContextDB();

考虑这一点..当一个访问者可以具有多个活动的DbContext时,存在某些情况。如果他要访问使用UserRepository的控制器,那可能不是最好的主意,我对此有一些疑问

  1. 什么时候应该创建一个新的DbContext /应该传递一个全局上下文?
  2. 我可以在所有地方重复使用一个全局上下文吗?
  3. 这会导致性能下降吗?
  4. 其他人怎么做?

我必须将其标记为重复-参见stackoverflow.com/questions/12871666/linq-and-datacontext进行了很好的讨论
SAJ14SAJ 2012年

2
在这种情况下,我将使用依赖项注入(例如Ninject),因此它将DbContext为每个请求创建一个。我还要创建服务层。检查此问题与答案
Zbigniew 2012年

Answers:


82

我使用一个基本控制器,它公开了DataBase派生控制器可以访问的属性。

public abstract class BaseController : Controller
{
    public BaseController()
    {
        Database = new DatabaseContext();
    }

    protected DatabaseContext Database { get; set; }

    protected override void Dispose(bool disposing)
    {
        Database.Dispose();
        base.Dispose(disposing);
    }
}

我的应用程序中的所有控制器BaseController均源自并按如下方式使用:

public class UserController : BaseController
{
    [HttpGet]
    public ActionResult Index()
    {
        return View(Database.Users.OrderBy(p => p.Name).ToList());
    }
}

现在回答您的问题:

什么时候应该创建一个新的DbContext /应该传递一个全局上下文?

应该根据请求创建上下文。创建上下文,执行所需的操作,然后删除它。使用基类解决方案,我只需要担心使用上下文。

不要尝试具有全局上下文(这不是Web应用程序的工作方式)。

我可以在所有地方重复使用一个全局上下文吗?

不,如果您保持上下文相关,它将跟踪所有更新,添加,删除等操作,这会使您的应用程序变慢,甚至可能导致一些非常细小的错误出现在您的应用程序中。

您可能应该选择将存储库上下文公开给控制器,但不要两者都公开。如果使用相同的方法访问两个上下文,则如果它们对应用程序的当前状态有不同的想法,则会导致错误。

就我个人而言,我更喜欢DbContext直接公开,因为我看到的大多数存储库示例DbContext无论如何最终都只是薄薄的包装。

这会导致性能下降吗?

第一次DbContext创建a的代价是非常昂贵的,但是一旦完成,就会缓存很多信息,因此后续实例化会更快。与在每次需要访问数据库时实例化一个上下文相比,保留一个上下文更可能导致性能问题。

其他人怎么做?

这取决于。

有些人喜欢使用依赖项注入框架在创建控制器时将其上下文的具体实例传递给其控制器。两种选择都很好。Mine更适合小型应用程序,在这种应用程序中您知道所使用的特定数据库不会改变。

有些人可能会争辩说您知道这一点,这就是为什么依赖项注入方法更好,因为它使您的应用程序更能适应更改。我对此的看法是,它可能不会改变(几乎不会模糊SQL Server和Entity Framework),并且我的时间最好花在编写针对我的应用程序的代码上。


4
除了对此感到高兴以外,我很高兴看到其他人的帖子中反映了我的一些偏好。我从未真正理解过存储库示例,这些示例导致在DbContext上又添加了一层,却又没有真正添加任何内容,并且我很喜欢创建一个公开受保护的DbContext的基类。
dark_perfect

4
要补充一点,这个答案很好,但是现在随着EF6的发布已经过时了,EF6每次使用后都会自动处理上下文。这样,在某些情况下可以创建每个会话(全局)上下文。如果使用EF6连接到存储过程,并且数据锁定不是问题,那么最好在基础控制器中一次创建上下文,并使所有需要数据库访问的控制器都从基础继承。最正确的答案是您需要了解技术并为您的架构应用正确的模式。
Atters,2015年

如果我想从使用DbContext的控制器操作中调用某些模型方法怎么办?如何将其传递给模型方法?
RMazitov 2015年

然后,您要在不属于它们的控制器中执行数据库查询和业务逻辑。您应该创建一个从控制器调用的服务。即您的UserController调用您的UserService中的方法。
2015年

@Fred,是的,我如何通过简单的参数将DbContext传递给UserService或否?
RMazitov 2015年

10

我尝试根据自己的经验进行回答。

1.什么时候应该创建一个新的DbContext /应该传递一个全局上下文?

Context应该通过依赖关系注入来注入,而不应该由您自己实例化。最佳实践是通过依赖项注入将其创建为范围服务。(请参阅我对问题4的回答)

还请考虑使用适当的分层应用程序结构,例如Controller> BusinessLogic> Repository。在这种情况下,您的控制器不是接收db-context,而是接收存储库。在控制器中注入/实例化数据库上下文告诉我,您的应用程序体系结构在一个地方混合了许多职责,在任何情况下,我都不建议这样做。

2.我可以在所有地方重复使用一个全局上下文吗?

是的,您可以拥有,但是问题应该是“我应该拥有...”->否。应该根据每个请求使用上下文来更改存储库,然后再次将其删除。

3.这会导致性能下降吗?

是的,它这样做是因为DBContext根本不是为全局而设计的。它存储已输入或查询到的所有数据,直到销毁为止。这意味着全局上下文将变得越来越大,对其进行的操作将越来越慢,直到您遇到内存不足的异常或您死了,因为它们都慢下来了。

当多个线程一次访问同一上下文时,您还将获得异常和许多错误。

4.其他人怎么做?

通过工厂的依赖关系注入来注入DBContext;范围:

services.AddDbContext<UserDbContext>(o => o.UseSqlServer(this.settings.DatabaseOptions.UserDBConnectionString));

希望我的回答对您有所帮助。


如果要在ConfigureServices方法本身中的Startup.cs中使用DBContext怎么办?我有一个OICD中间件,需要在其中访问数据库,但是我无法访问DBContext或我不知道该怎么办?
bbrinck

在configureServices方法中,您的DBContext可能不可用,因为您在此处配置了它。在运行时实际从中获取DBContext的ServiceProvider将首先在Configure()中可用(不是“ ConfigureServices”!)方法。在这里,您可以通过键入“ app.ApplicationServices.GetRequiredService <MyDbContext>();”使用ApplicationBuilder请求上下文。(将MyDbContext替换为您自己的上下文的类名)。
Ravior

如果控制器注入了存储库,它们(控制器)如何保存更改?假设我要发送POST请求以在数据库中插入一些内容,控制器处理该请求,使用存储库添加新创建的对象..那又是什么呢?谁坚持改变?
MMalke

@MMalke存储库可以做到这一点。它通常具有函数“ SaveData(Data myData)”,然后存储库为Data-Class创建其Entity-Framework兄弟实例的实例,将其添加到dbcontext中的相应dbset中,然后调用SaveChanges()。控制器唯一要做的就是调用Repository.SaveData(myData)函数。
Ravior

1

现在,我正在尝试这种方法,它避免了在调用不使用上下文的操作时实例化上下文。

public abstract class BaseController : Controller
{
    public BaseController() { }

    private DatabaseContext _database;
    protected DatabaseContext Database
    {
        get
        {
            if (_database == null)
                _database = new DatabaseContext();
            return _database;
        }
    }

    protected override void Dispose(bool disposing)
    {
        if (_database != null)
            _database.Dispose();
        base.Dispose(disposing);
    }
}

2
尽管您的方法没有错,但我认为,实体框架上下文将以惰性的方式完成所有工作,并且在您实际访问数据库之前,不会执行任何实际工作。因此,创建EF上下文的开销应该很小。
Martin Liversage 2015年

我做了一些研究,这似乎是正确的。我通过比较GC.GetTotalMemory()返回的结果(不是很完美,但这是我发现的结果)进行了一些简单的测试,并且差异从未大于8Kb。
安德鲁(Andrew)

0

显然,这是一个比较老的问题,但是如果您使用DI,则可以执行以下操作,并在请求的生命周期内对所有对象进行范围调整

 public class UnitOfWorkAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            var context = IoC.CurrentNestedContainer.GetInstance<DatabaseContext>();
            context.BeginTransaction();
        }

        public override void OnActionExecuted(HttpActionExecutedContext actionContext)
        {
            var context = IoC.CurrentNestedContainer.GetInstance<DatabaseContext>();
            context.CloseTransaction(actionContext.Exception);
        }
    }

0

您应该在每次Save()操作之后立即处理上下文。否则,随后的每次保存将花费更长的时间。我有一个项目,可以周期创建和保存复杂的数据库实体。令我惊讶的是,在循环内“使用(var ctx = new MyContext()){...}”之后,该操作的速度提高了三倍。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.