摘要
这个问题是一种方法论。答案应该是与所述情景的背景一起处理圣杯的链接。
我们在MVC Web应用程序项目中遇到了与使用dbContext相关的不同问题。
在阅读了许多问答博客,文章......包括有存储库和注入模式的提案,Owin,Entity Framework,Ninject之后,我们仍然不清楚使用dbContext的正确方法。
是否有任何文章,演示,使用“The Way”在更复杂的应用程序中执行此操作,而不仅仅是使用MVVC-presentation / Domain Entities / Logic / DataAccess层之间的分离的“CRUD”操作,包括Identity安全处理用户和角色权限?
描述
以前,我们的方法是在每个存储库中需要时创建dbContext对象。 很快我们发现了“dbContext被丢弃”之类的错误,因为连接与存储库功能一起消失了。这使得检索到的对象“部分可用”到应用程序的上层(使用技巧.ToList(),因为我们可以访问集合和属性,但以后不能导航到对象子表,等等)。同样使用来自不同存储库的2个上下文,我们得到一个异常,告知2个上下文正在尝试将更改注册到同一个对象。
由于提供原型的时间承诺,我们为整个应用程序创建了一个单独的静态dbContext,在需要时从任何地方调用它(控制器,模型,逻辑,DataAccess,数据库初始化器)。我们知道这是一个非常肮脏的解决方法,但它比以前的方法工作得更好。
仍有问题:dbContext一次只能处理1个异步方法调用,我们可以有很多调用(例如userManager.FindByNameAsync - 只有异步方法)。例外:“在上一次异步操作完成之前,在此上下文中启动了第二个操作”。
我们考虑创建上下文作为在控制器中调用操作的第一步,然后将此对象作为“中继竞赛”传递给所有其他调用的层或函数。通过这种方式,连接将从“在浏览器中单击”生效,直到将响应加载到其上。但我们不喜欢这样的想法,即每个函数都必须有一个额外的参数“context”,只是为了通过整个操作路径的层共享连接。
我们确信我们不是第一个想知道使用上下文的正确方法的人。
应用层
我们有这些(逻辑)图层,不同的工作区,但是同样的webapp MVC项目,从上到下:
观点: HTML + Razor + JQuery + CSS。此处的代码仅限于布局,但某些HTML可能取决于角色。方法调用仅限于控制器,加上utils(如格式化)。
ViewModels: 要在控制器和视图之间交换的数据容器。类只定义属性,以及仅与域实体进行转换的函数(转换器)。
控制器: 从浏览器调用的操作会导致调用逻辑层中的函数。此处的身份验证限制对操作内的操作或限制的访问。控制器避免使用Domain实体而是使用ViewModel,以便与Logic层进行通信ViewModels的转换函数被调用。
域实体: 用于逻辑层,用于按实体框架创建数据库表。
逻辑类: 域实体具有包含所有操作的EntityLogic类。这些是所有常见规则并从特定消费者客户端抽象出来的核心(ViewModel未知)。
存储库: 访问数据库。不确定我们是否确实需要这个,因为实体框架已经将域实体映射到数据库中的对象。
典型情况
浏览器调用Products控制器中的操作(POST)来编辑产品。 ProductViewModel用作数据的容器。
控制器操作仅限于一组角色。在操作内部,根据角色,调用不同的Logic函数,并将ProductViewModel转换为ProductDomainEntity并作为参数传递。
逻辑EditProduct函数调用不同逻辑类中的其他函数,并使用本地化和安全性来限制或过滤。逻辑可能会也可能不会调用存储库来访问数据,或者为所有人使用全局上下文,并将生成的域实体集合传递给逻辑。
根据结果,逻辑可能会也可能不会尝试导航结果'子集合。结果作为域实体(或集合)返回到控制器操作,并且根据此结果,控制器可以调用更多逻辑,或重定向到另一个操作或使用View将结果转换为正确的ViewModel。 / p>
在哪里,何时以及如何创建dbContext以最佳方式支持整个操作?
UPDATE:逻辑层中的所有类都是静态的。从控制器调用这些方法就像这样:
UserLogic.GetCompanyUserRoles(user)
,或
user.GetCompanyRoles()
其中 GetCompanyRoles()是 UserLogic 中实施的用户的扩展方法。因此,Logic类的实例不代表没有构造函数接收在其方法中使用的dbContext。
我想在静态类中使用静态方法来了解将dbContext的实例激活到当前HttpRequest的位置。
NInject 和 OnePerRequestHttpModule 可以帮助解决这个问题吗?有人试过吗?
答案 0 :(得分:2)
我不相信有一个圣杯"或者魔术子弹回答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
更直接的答案:
...这使得检索到的对象“部分可用”到应用程序的上层(使用技巧.ToList(),因为我们可以访问集合和属性,但以后不能导航到对象子表中,并且等等)。同样使用来自不同存储库的2个上下文,我们得到一个异常,告知2个上下文正在尝试将更改注册到同一个对象。
这些问题的解决方案是:1)急切加载最初执行查询而不是延迟加载时所需的所有子属性和导航属性,以及2)每个HTTP请求仅使用1个DbContext实例(反转为控制容器可以帮助解决这个问题。)
由于提供原型的时间承诺,我们为整个应用程序创建了一个单独的静态dbContext,在需要时从任何地方调用它(控制器,模型,逻辑,DataAccess,数据库初始化器)。我们知道这是一个非常肮脏的解决方法,但它比以前的方法工作得更好。
这实际上比一个肮脏的解决方法更糟糕,因为当你有一个static
DbContext实例时,你将开始看到非常奇怪且难以调试的错误。我很惊讶地发现这比以前的方法效果更好,但它只是指出如果这个方法效果更好,你之前的方法会遇到更多问题。
我们考虑创建上下文作为在控制器中调用操作的第一步,然后将此对象作为“中继竞赛”传递给所有其他调用的层或函数。通过这种方式,连接将从“在浏览器中单击”生效,直到将响应加载到其上。但我们不喜欢这样的想法,即每个函数都必须有一个额外的参数“上下文”,只是为了通过整个操作路径的层共享连接
这就是Inversion of Control容器可以为您做的事情,因此您不必继续绕过实例。如果您为每个HTTP请求注册一个DbContext实例,则可以使用容器(和构造函数注入)来获取该实例,而不必在方法参数中传递它(或者更糟糕)。
ViewModels:要在控制器和视图之间交换的数据容器。类只定义属性,以及仅与域实体进行转换的函数(转换器)。
小建议:不要在ViewModels上声明这样的函数。 ViewModels应该是哑数据容器,没有行为,甚至是翻译行为。在控制器或其他层(如查询层)中进行转换。 ViewModels可以使用函数来公开基于其他数据属性的派生数据属性,但没有行为。
逻辑类:Domain实体具有包含所有操作的EntityLogic类。这些是所有常见规则并从特定消费者客户端抽象出来的核心(ViewModel未知)。
这可能是您当前设计中的错误。将所有业务规则和逻辑分解为特定于实体的类可能会变得混乱,尤其是在处理存储库时。那些跨越实体甚至聚合的业务规则和逻辑呢?它们属于哪个实体逻辑类?
CQRS方法将您从这种思考规则和逻辑的模式中推出,更多地成为思考用例的范例。每个"浏览器点击"可能会归结为用户想要调用或使用的一些用例。您可以找出该用例的参数(例如,哪个子/导航数据到急切加载),然后编写1(一)个查询处理程序或命令处理程序来包装整个用例。当您找到属于多个查询或命令的公共子例程时,您可以将这些子例程分解为扩展方法,内部方法甚至其他命令和查询处理程序。
如果您正在寻找一个好的起点,我认为通过首先学习如何正确使用良好的Inversion of Control容器(如Ninject或SimpleInjector)来注册您的EF DbContext,您将获得最大的收益。这样每个HTTP请求只能创建一个实例。这应该可以帮助您至少避免处置和多上下文异常。
答案 1 :(得分:0)
我总是使用包含dbContext的BaseController并将其传递给逻辑函数(我调用的扩展)。这样,每次调用只使用一个上下文,如果出现故障则会进行回滚。
示例:
继承BaseController的Controller1
Controller1现在可以访问作为上下文的属性db
Controller1包含一个动作“Action1”
Action1将调用函数“LogicFunctionX(db,value1,Membership.CurrentUserId,true)”
在Action1中,您可以调用其他逻辑函数,甚至可以在“LogicFunctionX”中调用它们。始终通过函数传递属性db。
要保存上下文,我在调用所有逻辑函数后(主要是)在控制器内部执行此操作。
注意:我在LogicFunctionX中传递的参数是将内容保存在内部或不在内部。喜欢:
if(Save)
db.SaveChanges();
在这之前我遇到了几个问题。