在 ViewModel VS 传递控制器的实例中实例化 DbContext
Instantiating DbContext within ViewModel VS passing controller's instance
我有一个编辑 ViewModel 可以完成一些数据库工作。
我采用这些方法中的哪一种有什么不同吗?
- 将控制器的 DBContext 实例传递给 ViewModel(下面的示例 1)
- 在 ViewModel 本身中创建一个新的 DBContext 实例(下面的示例 2)
如果我传入控制器的 DBContext 实例,这意味着 .Dispose()
将在某个时候调用它(假设控制器中有 .Dispose()
方法)。通过在 ViewModel 方法本身中创建 DBContext 实例,这永远不会发生。这重要吗?
示例 1:
public class ModelOne
{
public int ID { get; set; }
public string PropA { get; set; }
public string PropB { get; set; }
public string PropC { get; set; }
// etc...
}
public class EditModelOnePropAViewModel
{
public EditModelOnePropAViewModel(ApplicationDbContext db, int id)
{
ID = id;
PropA = db.ModelOneDbSet
.Where(i => i.ID == id)
.Select(i => i.PropA)
.FirstOrDefault();
}
public void SaveChanges(ApplicationDbContext db)
{
var modelOne = db.ModelOneDbSet.FirstOrDefault(i => i.ID == ID);
modelOne.PropA = PropA;
db.SaveChanges();
}
public string PropA { get; set; }
public int ID { get; set; }
}
public class ControllerOne : Controller
{
private ApplicationDbContext DB = new ApplicationDbContext() { };
[HttpGet]
public ActionResult Edit(int id)
{
var viewModel = new EditModelOnePropAViewModel(DB, id);
return View(viewModel);
}
[HttpPost]
public ActionResult Edit(EditModelOnePropAViewModel postedModel)
{
if (ModelState.IsValid)
{
postedModel.SaveChanges(DB);
return RedirectToAction("index");
}
return View(postedModel);
}
}
示例 2:
public class ModelTwo
{
public int ID { get; set; }
public string PropA { get; set; }
public string PropB { get; set; }
public string PropC { get; set; }
// etc...
}
public class EditModelTwoPropAViewModel
{
public EditModelTwoPropAViewModel(int id)
{
using (var db = new ApplicationDbContext())
{
ID = id;
PropA = db.ModelTwoDbSet
.Where(i => i.ID == id)
.Select(i => i.PropA)
.FirstOrDefault();
}
}
public void SaveChanges()
{
using (var db = new ApplicationDbContext())
{
var modelTwo = db.ModelTwoDbSet.FirstOrDefault(i => i.ID == ID);
modelTwo.PropA = PropA;
db.SaveChanges();
}
}
public string PropA { get; set; }
public int ID { get; set; }
}
public class ControllerTwo : Controller
{
[HttpGet]
public ActionResult Edit(int id)
{
var viewModel = new EditModelTwoPropAViewModel(id);
return View(viewModel);
}
[HttpPost]
public ActionResult Edit(EditModelTwoPropAViewModel postedModel)
{
if (ModelState.IsValid)
{
postedModel.SaveChanges();
return RedirectToAction("index");
}
return View(postedModel);
}
}
视图模型应该是简单的 POCO。 类 具有您的视图所需的属性。没有其他的。
public class CustomerViewModel
{
public int Id {set;get;}
public string FirstName {set;get;}
public string LastName {set;get;}
}
在你的控制器中,
public ActionResult Edit(int id)
{
using(var db=new YourDbContext())
{
var c= db.Customers.FirstOrDefault(s=>s.Id==id);
if(c!=null)
{
var vm= new CustomerViewModel { Id=id,
FirstName=c.FirstName,
LastName=c.LastName
};
return View(vm);
}
}
return View("NotFound");
}
更好的方法是在您的数据访问层上创建一个抽象,这样您的控制器代码就不知道您正在使用什么数据访问技术。这将帮助您对控制器操作进行单元测试。
所以基本上你会像这样创建一个抽象
public interface ICustomerRepository
{
CustomerDto GetCustomer(ind id);
}
public class EFCustomerRepository : ICustomerRepository
{
public CustomerDto GetCustomer(int id)
{
using(var db=new YourDbContext())
{
var c= db.Customers.FirstOrDefault(s=>s.Id==id);
if(c!=null)
{
return new CustomerDto { Id=id, FirstName = c.FirstName };
}
}
return null;
}
}
假设您有一个名为 CustomerDto
的 class,它是一种表示客户实体的数据结构,Web 代码和数据访问代码都可以访问它(您可以将其保存在一个 Common 中项目并在两个项目中添加对该项目的引用)
并且在您的控制器中,您将使用 ICustomerRepository
的实现
public CustomerController : Controller
{
ICustomerRepository repo;
public CustomerController(ICustomerRepository repo)
{
this.repo =repo;
}
// You will use this.repo in your action methods now.
}
这将帮助您在单元测试中使用伪造的 ICustomerRepository 实现。您可以使用像 Moq/FakeItEasy 这样的模拟库来做到这一点。
您可以使用 Unity、StructureMap 或 Ninject 等依赖注入框架在您的应用程序中注入接口的具体实现。
ViewModel 是 MVVM,而不是 MVC。 MVC 中的模型与 MVVM 中的模型是一回事。该模式称为 Model-View-Viewmodel
在 MVC 中,控制器负责页面流。没有其他的。 DbContext 与页面流没有任何关系。
模型负责业务逻辑。 DbContext 很多 与业务逻辑有关。如果你想在离表现层这么近的地方使用数据库,模型应该创建 DbContext。
你的控制器示例 2 比 Shyju 提出的那个要好。现在您无需使用 DbContext 即可测试页面流。由于 DbContext 与页面流无关,因此更有意义。
有些挑剔:POCO 不是具有 public 属性且没有逻辑的对象。它 class 不依赖于任何特定框架(例如要实现的特定接口)。
我有一个编辑 ViewModel 可以完成一些数据库工作。
我采用这些方法中的哪一种有什么不同吗?
- 将控制器的 DBContext 实例传递给 ViewModel(下面的示例 1)
- 在 ViewModel 本身中创建一个新的 DBContext 实例(下面的示例 2)
如果我传入控制器的 DBContext 实例,这意味着 .Dispose()
将在某个时候调用它(假设控制器中有 .Dispose()
方法)。通过在 ViewModel 方法本身中创建 DBContext 实例,这永远不会发生。这重要吗?
示例 1:
public class ModelOne
{
public int ID { get; set; }
public string PropA { get; set; }
public string PropB { get; set; }
public string PropC { get; set; }
// etc...
}
public class EditModelOnePropAViewModel
{
public EditModelOnePropAViewModel(ApplicationDbContext db, int id)
{
ID = id;
PropA = db.ModelOneDbSet
.Where(i => i.ID == id)
.Select(i => i.PropA)
.FirstOrDefault();
}
public void SaveChanges(ApplicationDbContext db)
{
var modelOne = db.ModelOneDbSet.FirstOrDefault(i => i.ID == ID);
modelOne.PropA = PropA;
db.SaveChanges();
}
public string PropA { get; set; }
public int ID { get; set; }
}
public class ControllerOne : Controller
{
private ApplicationDbContext DB = new ApplicationDbContext() { };
[HttpGet]
public ActionResult Edit(int id)
{
var viewModel = new EditModelOnePropAViewModel(DB, id);
return View(viewModel);
}
[HttpPost]
public ActionResult Edit(EditModelOnePropAViewModel postedModel)
{
if (ModelState.IsValid)
{
postedModel.SaveChanges(DB);
return RedirectToAction("index");
}
return View(postedModel);
}
}
示例 2:
public class ModelTwo
{
public int ID { get; set; }
public string PropA { get; set; }
public string PropB { get; set; }
public string PropC { get; set; }
// etc...
}
public class EditModelTwoPropAViewModel
{
public EditModelTwoPropAViewModel(int id)
{
using (var db = new ApplicationDbContext())
{
ID = id;
PropA = db.ModelTwoDbSet
.Where(i => i.ID == id)
.Select(i => i.PropA)
.FirstOrDefault();
}
}
public void SaveChanges()
{
using (var db = new ApplicationDbContext())
{
var modelTwo = db.ModelTwoDbSet.FirstOrDefault(i => i.ID == ID);
modelTwo.PropA = PropA;
db.SaveChanges();
}
}
public string PropA { get; set; }
public int ID { get; set; }
}
public class ControllerTwo : Controller
{
[HttpGet]
public ActionResult Edit(int id)
{
var viewModel = new EditModelTwoPropAViewModel(id);
return View(viewModel);
}
[HttpPost]
public ActionResult Edit(EditModelTwoPropAViewModel postedModel)
{
if (ModelState.IsValid)
{
postedModel.SaveChanges();
return RedirectToAction("index");
}
return View(postedModel);
}
}
视图模型应该是简单的 POCO。 类 具有您的视图所需的属性。没有其他的。
public class CustomerViewModel
{
public int Id {set;get;}
public string FirstName {set;get;}
public string LastName {set;get;}
}
在你的控制器中,
public ActionResult Edit(int id)
{
using(var db=new YourDbContext())
{
var c= db.Customers.FirstOrDefault(s=>s.Id==id);
if(c!=null)
{
var vm= new CustomerViewModel { Id=id,
FirstName=c.FirstName,
LastName=c.LastName
};
return View(vm);
}
}
return View("NotFound");
}
更好的方法是在您的数据访问层上创建一个抽象,这样您的控制器代码就不知道您正在使用什么数据访问技术。这将帮助您对控制器操作进行单元测试。
所以基本上你会像这样创建一个抽象
public interface ICustomerRepository
{
CustomerDto GetCustomer(ind id);
}
public class EFCustomerRepository : ICustomerRepository
{
public CustomerDto GetCustomer(int id)
{
using(var db=new YourDbContext())
{
var c= db.Customers.FirstOrDefault(s=>s.Id==id);
if(c!=null)
{
return new CustomerDto { Id=id, FirstName = c.FirstName };
}
}
return null;
}
}
假设您有一个名为 CustomerDto
的 class,它是一种表示客户实体的数据结构,Web 代码和数据访问代码都可以访问它(您可以将其保存在一个 Common 中项目并在两个项目中添加对该项目的引用)
并且在您的控制器中,您将使用 ICustomerRepository
的实现public CustomerController : Controller
{
ICustomerRepository repo;
public CustomerController(ICustomerRepository repo)
{
this.repo =repo;
}
// You will use this.repo in your action methods now.
}
这将帮助您在单元测试中使用伪造的 ICustomerRepository 实现。您可以使用像 Moq/FakeItEasy 这样的模拟库来做到这一点。
您可以使用 Unity、StructureMap 或 Ninject 等依赖注入框架在您的应用程序中注入接口的具体实现。
ViewModel 是 MVVM,而不是 MVC。 MVC 中的模型与 MVVM 中的模型是一回事。该模式称为 Model-View-Viewmodel
在 MVC 中,控制器负责页面流。没有其他的。 DbContext 与页面流没有任何关系。
模型负责业务逻辑。 DbContext 很多 与业务逻辑有关。如果你想在离表现层这么近的地方使用数据库,模型应该创建 DbContext。
你的控制器示例 2 比 Shyju 提出的那个要好。现在您无需使用 DbContext 即可测试页面流。由于 DbContext 与页面流无关,因此更有意义。
有些挑剔:POCO 不是具有 public 属性且没有逻辑的对象。它 class 不依赖于任何特定框架(例如要实现的特定接口)。