ral*_*nso 8 repository-pattern entity-framework-4 asp.net-mvc-3 dbcontext
我对ASP.NET MVC 3的大部分知识来自阅读Adam Freeman和Steven Senderson撰写的Pro ASP.NET MVC 3 Framework一书.对于我的测试应用程序,我试图非常密切地坚持他们的例子.我正在使用存储库模式加上Ninject和Moq,这意味着单元测试工作得很好(即无需从数据库中提取数据).
在本书中,存储库的使用方式如下:
public class EFDbTestChildRepository
{
private EFDbContext context = new EFDbContext();
public IQueryable<TestChild> TestChildren
{
get { return context.TestChildren; }
}
public void SaveTestChild(TestChild testChild)
{
if (testChild.TestChildID == 0)
{
context.TestChildren.Add(testChild);
}
else
{
context.Entry(testChild).State = EntityState.Modified;
}
context.SaveChanges();
}
}
Run Code Online (Sandbox Code Playgroud)
这是与之相关的DbContext:
public class EFDbContext : DbContext
{
public DbSet<TestParent> TestParents { get; set; }
public DbSet<TestChild> TestChildren { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
请注意:为了简化这个提取的例子,我在这里省略了接口ITestChildRepository,然后Ninject会使用它.
在其他来源中,我看到了一个更通用的存储库方法,其中一个存储库足以满足整个应用程序的需要.显然在我的情况下,我最终在我的应用程序中列出了相当多的存储库列表 - 基本上我的域模型中的每个实体都有一个.不确定这两种方法的优缺点 - 我只是按照这本书的安全性来做.
最后得到我的问题:每个存储库都有自己的DbContext - private EFDbContext context = new EFDbContext();.我是否有冒险在一个请求中使用多个DbContexts的风险?这会导致任何重大的性能开销吗?如何在上下文之间发生冲突以及对数据完整性的任何后果?
这是一个示例,我最终在控制器中有多个存储库.
我的两个数据库表与外键关系链接.我的域模型类:
public class TestParent
{
public int TestParentID { get; set; }
public string Name { get; set; }
public string Comment { get; set; }
public virtual ICollection<TestChild> TestChildren { get; set; }
}
public class TestChild
{
public int TestChildID { get; set; }
public int TestParentID { get; set; }
public string Name { get; set; }
public string Comment { get; set; }
public virtual TestParent TestParent { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
Web应用程序包含一个允许用户创建新TestChild的页面.在它上面有一个选择框,其中包含可供选择的TestParents列表.这就是我的控制器的样子:
public class ChildController : Controller
{
private EFDbTestParentRepository testParentRepository = new EFDbTestParentRepository();
private EFDbTestChildRepository testChildRepository = new EFDbTestChildRepository();
public ActionResult List()
{
return View(testChildRepository.TestChildren);
}
public ViewResult Edit(int testChildID)
{
ChildViewModel cvm = new ChildViewModel();
cvm.TestChild = testChildRepository.TestChildren.First(tc => tc.TestChildID == testChildID);
cvm.TestParents = testParentRepository.TestParents;
return View(cvm);
}
public ViewResult Create()
{
ChildViewModel cvm = new ChildViewModel();
cvm.TestChild = new TestChild();
cvm.TestParents = testParentRepository.TestParents;
return View("Edit", cvm);
}
[HttpPost]
public ActionResult Edit(TestChild testChild)
{
try
{
if (ModelState.IsValid)
{
testChildRepository.SaveTestChild(testChild);
TempData["message"] = string.Format("Changes to test child have been saved: {0} (ID = {1})",
testChild.Name,
testChild.TestChildID);
return RedirectToAction("List");
}
}
catch (DataException)
{
//Log the error (add a variable name after DataException)
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
}
// something wrong with the data values
return View(testChild);
}
}
Run Code Online (Sandbox Code Playgroud)
有一个EFDbTestChildRepository是不够的,但我还需要一个EFDbTestParentRepository.它们都分配给控制器的私有变量 - 瞧,在我看来,已经创建了两个DbContexts.或者这是不正确的?
为了避免这个问题,我尝试使用EFDbTestChildRepository来访问TestParents.但这显然只会带来那些已经连接到至少一个TestChild的人 - 所以不是我想要的.
以下是视图模型的代码:
public class ChildViewModel
{
public TestChild TestChild { get; set; }
public IQueryable<TestParent> TestParents { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
如果我忘记包含一些相关代码,请告诉我.非常感谢您的建议!
不存在性能问题(除非我们讨论纳秒,实例化上下文非常便宜)并且您不会损坏您的数据完整性(在此之前您将获得异常).
但这种方法非常有限,只能在非常简单的情况下使用.在许多情况下,多个上下文将导致问题.例如:假设您要为现有父级创建一个新子级,并使用以下代码尝试:
var parent = parentRepo.TestParents.Single(p => p.Id == 1);
var child = new Child { TestParent = parent };
childrenRepo.SaveTestChild(child);
Run Code Online (Sandbox Code Playgroud)
这个简单的代码不起作用,因为parent它已经附加到内部的上下文中,parentRepo但childrenRepo.SaveTestChild会尝试将其附加到childrenRepo其中导致异常的上下文中,因为实体不能附加到另一个上下文.(这实际上是一种解决方法,因为您可以设置FK属性而不是加载parent:child.TestParentID = 1.但是如果没有FK属性,那将是一个问题.)
如何解决这样的问题?
一种方法可能是扩展EFDbTestChildRepository一个新的属性:
public IQueryable<TestParent> TestParents
{
get { return context.TestParents; }
}
Run Code Online (Sandbox Code Playgroud)
在上面的示例代码中,您可以只使用一个存储库,代码可以工作.但正如您所看到的,名称"EFDbTest Child Repository"不再适合新存储库的用途.它现在应该是"EFDbTest ParentAndChild Repository".
我称之为聚合根方法,这意味着您创建一个存储库不仅仅针对一个实体,而是针对少数几个彼此密切相关且在它们之间具有导航属性的实体.
另一种解决方案是将上下文注入存储库(而不是在存储库中创建它),以确保每个存储库使用相同的上下文.(上下文通常被抽象为一个IUnitOfWork接口.)示例:
public class MyController : Controller
{
private readonly MyContext _context;
public MyController()
{
_context = new MyContext();
}
public ActionResult SomeAction(...)
{
var parentRepo = new EFDbTestParentRepository(_context);
var childRepo = new EFDbTestChildRepository(_context);
//...
}
protected override void Dispose(bool disposing)
{
_context.Dispose();
base.Dispose(disposing);
}
}
Run Code Online (Sandbox Code Playgroud)
这为您提供了可在多个存储库中使用的每个控制器的单个上下文.
下一步可能是通过依赖注入为每个请求创建一个上下文,例如......
private readonly MyContext _context;
public MyController(MyContext context)
{
_context = context;
}
Run Code Online (Sandbox Code Playgroud)
...然后配置IOC容器以创建单个上下文实例,该实例可能被注入到多个控制器中.
我是否有可能在一个请求中面临多个 DbContext 的风险?
是的。存储库的每个实例都将实例化其自己的 DbContexts 实例。根据应用程序的大小和使用情况,这可能不是问题,尽管它不是一种可扩展性很强的方法。不过,有几种方法可以处理这个问题。在我的 Web 项目中,我将 DbContext 添加到 Request 的 Context.Item 集合中,这样所有需要它的类都可以使用它。我使用 Autofac(类似于 Ninject)来控制在特定场景中创建的 DbContext 以及它们的存储方式,例如,我对 WCF 上下文有一个与 Http 上下文不同的“会话管理器”。
这会导致任何显着的性能开销吗?
是的,但如果应用程序相对较小,则同样不会大规模。但随着它的增长,您可能会注意到开销。
上下文之间潜在的冲突以及对数据完整性的任何后果如何?
使用这样的 ORM 的原因之一是可以在 DbContext 中维护更改。如果您为每个请求实例化多个上下文实例,您将失去此优势。除非您异步处理大量更新,否则您不会注意到冲突或完整性本身的任何影响。
| 归档时间: |
|
| 查看次数: |
6721 次 |
| 最近记录: |