【问题标题】:Instantiating DbContext within ViewModel VS passing controller's instance在 ViewModel 中实例化 DbContext VS 传递控制器的实例
【发布时间】:2015-12-06 20:59:52
【问题描述】:

我有一个编辑 ViewModel 可以完成一些数据库工作。

我采用哪种方法有什么不同吗?

  1. 将控制器的 DBContext 实例传递给 ViewModel(下面的示例 1)
  2. 在 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);
    }
}

【问题讨论】:

  • 视图模型不应该对您的 DBContext 有任何了解。它是一个包含视图中使用的属性的“哑”类(请参阅What is ViewModel in MVC?)。您的方法意味着您不能进行单元测试。
  • 为什么Model中有SaveChanges方法?你是从视图中调用它吗?
  • 可能是我命名错误。这是一个大型数据模型,我只是在编辑几个属性。因此,我想要一个模型将这两个属性与主数据库支持的模型隔离开来,并将它们显示在编辑页面上。然后在发布时,我使用正在编辑的属性绑定到模型并更新数据库,

标签: asp.net-mvc entity-framework viewmodel dbcontext


【解决方案1】:

视图模型应该是简单的 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 的类,它是一个表示客户实体的数据结构,并且可供 Web 代码和数据访问代码访问(您可以保留在 Common Project 中并在两者中添加对它的引用项目)

在您的控制器中,您将使用 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 等依赖注入框架在您的应用程序中注入接口的具体实现。

【讨论】:

  • 感谢您提供详细的示例和解释 - 非常感谢!
【解决方案2】:

ViewModel 是 MVVM,而不是 MVC。 MVC 中的 Model 与 MVVM 中的 Model 是一回事。该模式称为 Model-View-Viewmodel

在 MVC 中,控制器负责页面流。没有其他的。 DbContext 与页面流无关

模型负责业务逻辑。 DbContext 很多与业务逻辑有关。如果你想在靠近表现层的地方使用数据库,Model 应该创建 DbContext。

您的控制器示例 2 比 Shyju 提出的更好。现在您无需使用 DbContext 即可测试页面流。而且由于 DbContext 与页面流无关,所以它更有意义。

一些吹毛求疵:POCO 不是具有公共属性且没有逻辑的对象。它是一个不依赖于任何特定框架的类(例如要实现的特定接口)。

【讨论】:

    猜你喜欢
    • 2015-08-29
    • 2018-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-25
    相关资源
    最近更新 更多