关于C#:使用dbContext的正确方法

Right way to work with dbContext

摘要

这个问题是关于方法论的。答案应该是在针对所描述的场景的上下文中与圣杯的链接。

我们在MVC Web应用程序项目中遇到了与dbContext的使用有关的不同问题。

在阅读了许多问答博客,文章……(包括具有存储库和注入模式的提案,Owin,Entity Framework,Ninject之后)之后,我们仍然不清楚如何使用dbContext的正确方法。

在MVVC表示层/域实体层/逻辑层/数据访问层之间进行分隔(包括身份安全处理用户和角色权限)时,是否有文章"演示",而不是" CRUD"操作在更复杂的应用程序中实现?

描述

以前,我们的方法是在每个存储库中需要时创建dbContext对象。
很快,由于连接与存储库功能一起消失,我们发现了诸如" dbContext已处置"之类的错误。这使得检索到的对象"部分可用"到应用程序的上层(使用技巧.ToList(),这是有限的,因为我们可以访问集合和属性,但以后不能导航??到对象子表,依此类推)。同样使用来自不同存储库的2个上下文,我们得到了一个异常,告诉我们2个上下文正在尝试将更改注册到同一对象。

由于要及时交付原型,因此我们为整个应用程序创建了一个共享的静态dbContext,可以在需要时从任何地方调用它(控制器,模型,逻辑,DataAccess,数据库初始化程序)。我们知道这是一个非常肮脏的解决方法,但是它比以前的方法效果更好。

仍然存在问题:dbContext一次只能处理1个异步方法调用,并且我们可以有很多调用(例如,userManager.FindByNameAsync-只有异步方法)。例外:"在先前的异步操作完成之前,第二个操作在此上下文上开始"。

我们正在考虑将上下文创建为在控制器中调用动作的第一步,然后将该对象作为"中继竞赛"传递给所调用的所有其他层或函数。这样,连接将从"在浏览器中单击"开始,直到将响应重新加载到该连接上为止。但是我们不喜欢每个函数都必须有一个额外的参数"上下文"的想法,只是为了共享整个操作路径中各层的连接。

我们确信我们不是第一个想知道使用上下文的正确方法的人。

应用层

我们有这些(逻辑)层,不同的工作空间,但从上到下都是相同的webapp MVC项目:

  • 视图:HTML + Razor + JQuery + CSS。这里的代码仅限于布局,但是某些HTML可能取决于Role。方法调用仅针对控制器,加上utils(如格式设置)。

  • ViewModels:在控制器和视图之间交换的数据容器。类仅定义属性,以及仅与域实体(译者)之间进行转换的函数。

  • 控制器:从浏览器调用的操作导致对逻辑层中功能的调用。此处的身份验证限制了对操作的访问或操作内部的限制。控制器避免使用Domain实体,而避免使用ViewModels,以便与逻辑层进行通信,从而调用ViewModels转换函数。

  • 域实体:用于逻辑层,并用于由实体框架创建数据库表。

  • 逻辑类:域实体具有包含所有操作的EntityLogic类。这些是所有特定客户客户端通用的抽象规则(ViewModel未知)的核心。

  • 存储库:访问数据库。不确定我们是否确实需要这样做,因为Entity Framework已经将Domain实体映射到数据库中的对象。

  • 典型场景

  • 浏览器在产品控制器中调用操作(POST)以编辑产品。 ProductViewModel用作数据的容器。

  • 控制器操作仅限于角色集合。在动作内部,根据角色,将调用其他Logic函数,并将ProductViewModel转换为ProductDomainEntity并作为参数传递。

  • 逻辑EditProduct函数调用不同逻辑类中的其他函数,并且还使用本地化和安全性进行限制或过滤。逻辑可以调用或不调用存储库来访问数据,或为所有对象使用全局上下文,并将生成的域实体集合传递给逻辑。

  • 根据结果??,逻辑可能会或可能不会尝试浏览结果的子级集合。结果作为域实体(或的集合)返回给控制器操作,并且根据此结果,控制器可以调用更多Logic,或者重定向到另一个操作,或者使用将结果转换为正确ViewModel的View进行响应。

  • 在哪里,何时何地以及如何创建dbContext以最佳方式支持整个操作?

    dbContext handling to make objects always navigable through layers?

    更新:逻辑层中的所有类都是静态的。像这样从控制器调用方法:

    1
    UserLogic.GetCompanyUserRoles(user)

    , 要么

    1
    user.GetCompanyRoles()

    其中,GetCompanyRoles()是在UserLogic中实现的User的扩展方法。因此,没有Logic类的实例意味着没有构造函数接收dbContext以便在其方法中使用。

    我想让静态类中的静态方法知道在哪里将dbContext的实例激活到当前HttpRequest。

    NInject和OnePerRequestHttpModule可以帮助吗? 有人尝试过吗?

    好。


    我认为对于EF / DbContexts或任何其他问题,没有"圣杯"或魔术子弹的答案。因此,我也不相信您的问题会有一个明确的答案,并且所有答案都将主要基于观点。但是,我个人发现,在处理EF语义和怪癖时,使用CQRS模式而不是存储库模式可以实现更多的控制和更少的问题。以下是一些您可能会(或可能不会)发现有用的链接:

    https://stackoverflow.com/a/21352268/304832

    https://stackoverflow.com/a/21584605/304832

    https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=91

    https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=92

    http://github.com/danludwig/tripod

    一些更直接的答案:

    ...This makes the retrieved objects"partially available" to the upper layers in the app (using the trick .ToList(), limited because we can access collections and attributes but not later navigation into the object child tables, and so on). Also using 2 contexts from different repositories, we got an exception telling that 2 contexts are trying to register changes to the same object.

    这些问题的解决方案是:1)渴望加载最初执行查询时所需的所有子级和导航属性,而不是延迟加载; 2)每个HTTP请求仅使用1个DbContext实例(控制容器的反转可以帮助)。

    Due to timed commitments to deliver prototypes, we created a single static dbContext shared for the whole application, which is called from everywhere when needed (Controllers, Models, Logic, DataAccess, database initializers). We are aware that is a very dirty workaround but it has been working better than the previous approach.

    这实际上比"肮脏的解决方法"要糟糕得多,因为当您拥有static DbContext实例时,您将开始看到非常奇怪且难以调试的错误。听到此方法比您以前的方法效果更好的消息,我感到非常惊讶,但它仅指出如果此方法更好,您的先前方法还会有更多问题。

    We were thinking about creating the context as the very first step when an action is called in the controller, then to carry this object as"relay race" to every other layer or function called. In this way the connection will live from the"click in the browser" until the response is loaded back on it. But we don’t like the idea that every single function must have an extra parameter"context" just to share the connection through the layers for the entire operation route

    这就是Inversion of Control容器可以为您做的事情,因此您不必不断传递实例。如果在每个HTTP请求中注册一个DbContext实例,则可以使用容器(和构造函数注入)获取该实例,而不必在方法参数中传递它(或更糟)。

    ViewModels: The data container to be exchanged between Controllers and Views. Classes only define attributes, plus functions to convert to and from Domain entities only (Translators).

    一点建议:不要在ViewModels上声明这样的函数。 ViewModels应该是哑数据容器,没有行为,甚至没有转换行为。在您的控制器或另一层(例如查询层)中进行翻译。 ViewModels可以具有一些功能,以公开基于其他数据属性但没有行为的派生数据属性。

    Logic Classes: A Domain entity has an EntityLogic class with all the operations. These are the core where all the rules that are common and abstracted from specific consumer clients (ViewModels are unknown).

    这可能是您当前设计中的错误。将您所有的业务规则和逻辑组合到特定于实体的类中可能会变得混乱,尤其是在处理存储库时。跨实体甚至聚合的业务规则和逻辑又如何呢?它们属于哪个实体逻辑类?

    CQRS方法使您摆脱了关于规则和逻辑的这种思考方式,而更多地进入了关于用例的思考范式。每次"浏览器单击"都可能归结为用户想要调用或使用的某些用例。您可以找出该用例的参数是什么(例如,急切加载的子/导航数据),然后编写1(一个)查询处理程序或命令处理程序以包装整个用例。当您发现属于多个查询或命令的公共子例程时,可以将其分解为扩展方法,内部方法甚至其他命令和查询处理程序。

    如果您正在寻找一个不错的起点,我认为您将通过首先学习如何正确使用良好的Inversion of Control容器(例如Ninject或SimpleInjector)来注册EF DbContext来获得最大的收获,以便仅每个HTTP请求都会创建1个实例。这将帮助您至少避免处理和多上下文异常。


    我总是使用一个包含dbContext的BaseController并将其传递给逻辑函数(我称之为扩展)。 这样,每个调用只使用一个上下文,如果失败,将进行回滚。

    例:

    继承BaseController的Controller1

    Controller1现在可以访问作为上下文的属性db

    Controller1包含一个动作" Action1"

    Action1将调用函数" LogicFunctionX(db,value1,Membership.CurrentUserId,true)"

    在Action1中,您可以调用其他逻辑函数,甚至可以在" LogicFunctionX"内部调用它们。 始终通过函数传递属性db。

    为了保存上下文,我会在调用所有逻辑函数后在控制器内部(大多数情况下)进行处理。

    注意:我在LogicFunctionX中传递的true参数是将上下文保存在内部还是不保存。 喜欢:

    1
    2
    if(Save)
       db.SaveChanges();

    在执行此操作之前,我遇到了几个问题。