c# – 使用dbContext的正确方法

摘要

这个问题是一种方法论.答案应该是与所述情景的背景一起处理圣杯的链接.

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

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

是否有任何文章,演示,使用“The Way”在更复杂的应用程序中执行此操作,而不仅仅是使用MVVC-presentation / Domain Entities / Logic / DataAccess层之间的分离的“CRUD”操作,包括身份安全处理用户和角色权限?

描述

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

由于提供原型的时间承诺,我们为整个应用程序创建了一个单独的静态dbContext,在需要时从任何地方调用它(控制器,模型,逻辑,DataAccess,数据库初始化器).我们知道这是一个非常肮脏的解决方法,但它比以前的方法工作得更好.

仍有问题:dbContext一次只能处理1个异步方法调用,我们可以有很多调用(例如userManager.FindByNameAsync – 只有异步方法).例外:“在上一次异步操作完成之前,在此上下文中启动了第二个操作”.

我们考虑创建上下文作为在控制器中调用操作的第一步,然后将此对象作为“中继竞争”传递给所有其他调用的层或函数.通过这种方式,连接将从“在浏览器中单击”生效,直到将响应加载到其上.但我们不喜欢这样的想法,即每个函数必须有一个额外的参数“上下文”,只是为了通过整个操作路径的层共享连接.

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

应用层

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

>视图:HTML Razor JQuery CSS.此处的代码仅限于布局,但某些HTML可能取决于角色.方法调用仅限于控制器,加上utils(如格式化).
> ViewModels:控制器和视图之间要交换的数据容器.类只定义属性,以及仅与域实体进行转换的函数(转换器).
>控制器:从浏览器调用的动作导致调用逻辑层中的函数.此处的身份验证限制对操作内的操作或限制的访问.控制器避免使用Domain实体而是使用ViewModel,以便与Logic层进行通信,调用ViewModels转换函数.
>域实体:用于逻辑层,用于通过Entity Framework创建数据库表.
>逻辑类:Domain实体具有包含所有操作的EntityLogic类.这些是所有常见规则并从特定消费者客户端抽象出来的核心(ViewModel未知).
>存储库:访问数据库.不确定我们是否确实需要这个,因为实体框架已经将域实体映射到数据库中的对象.

典型情况

>浏览器在Products控制器中调用操作(POST)来编辑产品. ProductViewModel用作数据的容器.
>控制器操作仅限于一组角色.在操作内部,根据角色,调用不同的Logic函数,并将ProductViewModel转换为ProductDomainEntity并作为参数传递.
>逻辑EditProduct函数调用不同逻辑类中的其他函数,并使用本地化和安全性来限制或过滤.逻辑可能会也可能不会调用存储库来访问数据,或者为所有人使用全局上下文,并将生成的域实体集合传递给逻辑.
>根据结果,逻辑可能会也可能不会尝试导航结果’子集合.结果作为域实体(或集合)返回到控制器操作,并且根据此结果,控制器可以调用更多逻辑,或重定向到另一个操作或使用View将结果转换为正确的ViewModel进行响应.

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

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

UserLogic.GetCompanyUserRoles(user)

, 要么

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.

这实际上比“脏解决方法”更糟糕,因为当你有一个静态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).

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

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个实例.这应该可以帮助您至少避免处置和多上下文异常.

点赞