我正在编写一个WPF应用程序,该应用程序需要访问数据库中的信息(我目前首先使用Entity Framework代码,因此可以通过DbContext进行数据访问)。

我的ViewModels直接实例化我的DbContext派生类,并对其进行查询以获得它们所需的信息。我了解这很糟糕,原因如下:


我的ViewModel现在对DbContext有依赖性。
我的viewModel较难测试。
如果需要,将很难切换到其他数据提供者。用ObjectContext代替DbContext

解决这个问题的常见方法是定义一个存储库,以使我的ViewModel不知道我在使用哪种数据访问技术。

我已定义以下通用存储库类:

public interface IRepository<T>
{
    IEnumerable<T> GetAll();
    IEnumerable<T> Find(Expression<Func<T, bool>> where);

    // other data access methods could also be included.

    void Add(T entity);
    void Attach(T entity);
    void Delete(T entity);
}


我的具体存储库如下所示:

public class Repository<T> : IRepository<T> where T : class
{
    private DbSet<T> _entitySet;

    public Repository(DbContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        _entitySet = context.Set<T>();
    }

    public IEnumerable<T> GetAll()
    {
        return _entitySet;
    }

    public IEnumerable<T> Find(Expression<Func<T, bool>> where)
    {
        return _entitySet.Where(where);
    }

    public void Add(T entity)
    {
        _entitySet.Add(entity);
    }

    public void Attach(T entity)
    {
        _entitySet.Attach(entity);
    }

    public void Delete(T entity)
    {
        _entitySet.Remove(entity);
    }
}


任何现在需要访问数据库的viewModel具有构造函数参数,以便可以注入存储库。据我所知,它解决了上面列出的所有问题-我的viewModels不再依赖于'DbContext',可以使用模拟的IRepository接口实现对它们进行测试,并且可以通过创建一个不同的API来切换到ObjectContext API IRepository接口的实现。

这很棒,除了在某些情况下我可能需要访问包含不同实体EG的其他存储库客户和产品。有时我有时还需要一次性添加,更新并保存对多个实体的更改。我可以将viewModel构造函数更改为仅接受它们所需的实体,但这对我来说并不正确。

一种解决方案是使用以下接口:

public interface IUnitOfWork : IDisposable
{
    IRepository<T> RepositoryFor<T>() where T : class;
    void SaveChanges();
}


这里的想法是,工作单元允许我的viewModels从数据源请求实体,任何更改都可以一次性保存。此接口的含义是,所请求的任何实体都属于相同的“上下文”。

一个具体实现的示例如下所示:

public class UnitOfWork : IUnitOfWork
{
    private DbContext _context;

    public UnitOfWork(DbContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        _context = context;
    }

    public IRepository<T> RepositoryFor<T>() where T : class
    {
        return new Repository<T>(_context);
    }

    public void SaveChanges()
    {
        _context.SaveChanges();
    }

    public void Dispose()
    {
        _context.Dispose();
    }
}


我的ViewModel构造函数现在注入了IUnitOfWork而不是存储库,并且IUnitOfWork实例允许我为每个实体请求通用存储库,然后使用SaveChanges()方法将所有更改保存到同一上下文中。

这仍然解决了前面列出的问题,并且还允许我对viewModels中的多个实体进行更改。我接下来要关心的是IUnitOfWork实例的生命周期。我的实现使用DbContext,我的理解是,这种类型(通常来说)应该是短命的。

请考虑以下情形:


IUnitOfWork实例被传递到我的ViewModel构造函数中。
我检索一些实体,使用它们,然后将更改保存到数据库中。
我处理IUnitOfWork实例。
我现在需要执行另一个系列操作,因此需要创建一个新的IUnitOfWork实例-我的ViewModel现在取决于接口的具体应用。

替代方法是在ViewModel的整个生命周期内都保留IUnitOfWork,但这意味着基本的DbContext将跟踪我所做的所有更改。

我对此的解决方案是创建另一个接口:

public interface IUnitOfWorkProvider
{
    IUnitOfWork GetUnitOfWork();
}


这是一个具体实例将通过构造函数注入到我的ViewModels中。每当我需要进行任何数据访问时,我都会做这样的事情:

using (IUnitOfWork uow = provider.GetUnitOfWork())
{
    // Carry out all data access here...
    uow.SaveChanges();
}


我想知道的是,我对存储库和单元的实现工作模式可以接受吗?这会给我带来什么重大问题吗?

更新

正如dreza在评论中向我指出的那样,我的viewModels正在从存储库中检索其自己的数据。我不确定如何将数据传递到我的viewModels中,而没有每个viewModel的唯一接口或类来封装此信息。

在当前状态下,存储库还可以(潜在地)返回任何实体存储库那是要求的。例如在我的customerViewModel中,我可能只想使用Customer和Product实体,但是没有什么可以阻止我请求其他不相关的实体。

评论

这些实现对我来说似乎很标准。但是我不确定这些是否在视图模型中使用。我总是倾向于跟踪其他对象负责填充视图模型本身,而不是负责获取所需数据的视图模型。这种方法有什么特殊原因吗?

这就是我根据最近学到的信息得出的结论。我当然愿意学习一种不同的方法。您是否有实现该方法的示例?我看到的所有MVVM示例通常都非常简单,并且直接创建数据即可。

#1 楼

注意:Ben正确地评论了他是从WPF而不是MVC方式执行此操作之后,我的回答还是偏向于MVC,因此尽管我将其保留在此处,但这可能与他实际寻找的内容没有太大关系。


通常,我的假设是,我的viewModels尽可能是DTO的
,也许具有验证属性功能,或者我已经看到它们在某些情况下使用消息通知来提供对更新的支持。视图。我认为理论是视图模型本身可以通过多种方法填充,或者用于填充视图模型的数据可以来自多种地方。

从我收集的观点模型来看,本质上是一种提供一组数据的方法,这是有问题的调用者/观点特别需要的。此数据可能来自各种地方,并且这些地方可能会有所不同。 viewmodel本身并不在乎,它只是将数据传输到知道如何处理的视图。

此外,在我看来,这样做可以使单元测试更容易编写。当然,有很多方法可以给猫剥皮。本月我最喜欢的方式是使用类之类的服务来进行从数据模型到视图模型的任何映射。我还听说过诸如Auto mapper之类的模块也是为您执行此映射的出色模块。

注意:这些只是如何使用存储库和IOW而不使它们成为一部分的示例。您的视图模型。

因此,按照我目前的工作方式,我会执行以下操作:

public class TopLevelClass
{
    private readonly IUnitOfWork _unitOfWork;

    public TopLevelClass(IUnitOfWork iow)
    {
       // injected by a DI framework such as Ninject, Unity ??
       _unitOfWork = iow;
    }

    // just an example of doing something with not using IUnitOfWork in viewmodel
    public void DoMySpecialSomething(string mySpecialId)
    {
       var repository = _unitOfWork.RepositoryFor<MySpecialModel>();
       var mySpecialModel = repository.SingleOrDefault(p => p.Id = mySpecialId);

       var viewModel = new MySpecialService().GetViewModel(mySpecialModel);

       // do something with my view model ???
    }

    public MySpecialViewModel GetSomethingEvenMoreSpecial(string mySpecialName, string goldMedal)
    {
       var mySpecialModel = new MySpecialModel
                               {
                                  Name = mySpecialName,
                                  OlympicMedal = goldMedal
                               };

       return new MySpecialService().GetViewModel(mySpecialModel);
    }
}

// Maybe this might also work from an IMySpecialService interfaces ????
public class MySpecialService
{
    public MySpecialViewModel GetViewModel(MySpecialModel model)
    {
       return new MySpecialViewModel
                {
                   Id = model.Id,
                   IsEnabled = !string.isNullOrEmpty(model.Name),
                   Name = model.Name,
                   OlympicMedal = model.Medal
                };
    }
}

public class MySpecialViewModel
{
    [DisplayName("Your Id")]
    public int Id { get; set; }
    // other properties etc
}

public class MySpecialModel
{
   public int Id { get; set; }
   // other properties here
}


我想我是否正在做TDD我本来会创建视图模型并首先对MySpecialService进行存根,并可能创建了类似

// Using standard microsoft testing project
[TestMethod]
public void MySpecialModel_IsValidTest()
{
   var model = new MySpecialModel
                 {
                    Id = 45
                 };

   var service = new MySpecialService();
   var viewModel = service.GetViewModel(model);

   Assert.AreEqual(model.Id, 45);
}  

I hope that might give you some ideas in what you might want to (or not) do differently.

的测试

评论


\ $ \ begingroup \ $
感谢您的反馈。明确地说,当我谈论viewModel时,我是说它们是在WPF而不是ASP.NET MVC的上下文中使用的。您的回复还不太清楚,因此我认为值得一提。
\ $ \ endgroup \ $
–本杰明
2012年8月3日在12:02

\ $ \ begingroup \ $
@Benjamin啊,好,我不好。您确实是在说这个。对此感到抱歉,除非它给您一些新的想法,否则您可能会忽略我的回答:)
\ $ \ endgroup \ $
– dreza
2012年8月3日20:35



\ $ \ begingroup \ $
没问题。无论如何+1仍然是正确的,因为您关于viewModels不直接查询数据源(直接通过DbContext或通过接口)的观点仍然有效,这使我重新考虑了我的方法。
\ $ \ endgroup \ $
–本杰明
2012年8月4日14:39