使用Entity Framework的UnitOfWork和Repository模式

3

我将在我的数据访问层中使用仓储和工作单元来完成这个任务,请看一个联系人聚合根。

 public interface IAggregateRoot
    {
    }

这是我的通用存储库接口:

 public interface IRepository<T>
        {
            IEnumerable<T> GetAll();
            T FindBy(params Object[] keyValues);
            void Add(T entity);
            void Update(T entity);
            void Delete(T entity);

        }

和我的POCO Contact类在Model中

 public class Contact :IAggregateRoot
        {
            public Guid Id { get; set; }
            public string Name { get; set; }
            public string Email { get; set; }
            public string Title { get; set; }
            public string Body { get; set; }
            public DateTime CreationTime { get; set; }
        }

这是我的IContactRepository,它继承自IRepository,可能还有它自己的方法。
 public interface IContactRepository : IRepository<Contact>
        {
        }

现在我已经在IUitOfWork和UnitOfwork中完成了这样的操作

public interface IUnitOfWork 
    {
        IRepository<Contact> ContactRepository { get; }
    }

public class UnitOfWork : IUnitOfWork
    {
        private readonly StatosContext _statosContext = new StatosContext();
        private IRepository<Contact> _contactUsRepository;

 public IRepository<Contact> ContactRepository
        {
            get { return _contactUsRepository ?? (_contactUsRepository = new Repository<Contact>(_statosContext)); }
        }
}

还有关于我的代码库

public class Repository<T> : IRepository<T> where T : class, IAggregateRoot
    {
       //implementing methods 
    }

我可以使用Service中的UnitOfwork访问Repositories来执行所有的CRUD操作,例如:

_unitOfWork.ContactRepository.Add(contact);
            _unitOfWork.SaveChanges();

但是我想要像这样做

_

ContactRepository.Add(contact);
            _unitOfWork.SaveChanges();

通过_ContactRepository而不是_unitOfWork.ContactRepository来获取CRUD和通用方法 因为我想要获得ContactRepository的一些特定查询方法, 有人可以帮忙吗?

也许这有点离题,但我放弃了完整的存储库,转而采用更简单的解决方案:引入一个精简的 IDbContext 接口,并通过该接口使用 DbContext。由于 DbContext 在开箱即用时就是 UoW 和存储库的结合体,所以这种方法非常简单和有效... - Patryk Ćwiek
抱歉,我没明白!你能否举个例子呢?我只发现http://www.primaryobjects.com/CMS/Article122.aspx有用,但它与此不同。 - Eric Nielsen
3个回答

11

这并不是你问题的一个直接答案,但它可能会简化事情并减少重复。

当你使用例如EntityFramework Power Tools来反向工程Code First(或者仅仅使用Code First),你会得到一个DbContext类,它同时作为UoW和repository,例如:

public partial class YourDbContext : DbContext
{
    public DbSet<Contact> Contacts {get; set;}
}

现在,如果您希望使事情易于测试,有一个简单的方法:引入一个非常薄的接口:
public interface IDbContext
{
    IDbSet<T> EntitySet<T>() where T : class;
    int SaveChanges();
    //you can reveal more methods from the original DbContext, like `GetValidationErrors` method or whatever you really need.
}

然后创建另一个文件,包含部分类的第二部分:

public partial class YourDbContext : IDbContext
{
     public IDbSet<T> EntitySet<T>() where T : class
     {
         return Set<T>();
     }
}

Ta-da! 现在你可以注入IDbContext,并以支持它的YourDbContext作为其后备:
//context is an injected IDbContext:
var contact = context.EntitySet<Contact>().Single(x => x.Id == 2);    
contact.Name = "Updated name";
context.EntitySet<Contact>().Add(new Contact { Name = "Brand new" });
context.SaveChanges();

现在,如果你想对上下文的处理有更多的控制权,那么你就需要编写自己的(吸气IDbContextFactory(泛型或非泛型,具体取决于你的需求),并注入该工厂。
现在不需要编写自己的FindAddUpdate方法了,DbContext会适当地处理它们,更容易引入显式事务,并且所有内容都被很好地隐藏在接口(IDbContextIDbSet)后面。
顺便说一句,IDbContextFactory相当于NHibernate的ISessionFactory,而IDbContext则相当于ISession。我希望EF也可以直接提供这个功能。

5
注意,当使用模拟或伪造对象进行单元测试时,使用真实的 DbContext 时可能会得到不同的结果。因为 EntityFramework 表达式与 LinqToObjects 表达式工作方式不同(由于转换为 SQL)。例如,LinqToObjects 将在创建包含可空列且实际为空的表达式时抛出异常,而 LinqToEntities 不会。此外,L2O 允许在表达式中调用方法,但 L2E 不允许(除了一些有限的特殊函数)。 - Erik Funkenbusch
@MystereMan 如果你正在存根或切换存储库到虚拟的,那么应该考虑这一点。如果你只是在模拟它(并将数据测试留给集成测试),那么这是最简单的解决方案。 :) - Patryk Ćwiek
这帮助我简化了与/关于DbContext(本质上是UofW)的抽象。我总是很苦恼,无法很好地将DbContext和我的抽象结合在一起...这就是我一直在寻找的东西。虽然如此,我确实有一个存储库层,而不是直接为它们使用DbSet。 - bcr

2

我同意医生的观点,DbContext已经是一个UnitOfWork,在其上再添加另一个UoW抽象通常是多余的,除非您认为未来很可能会切换数据库技术。

然而,我不同意将DbSet视为存储库,因为这样会将查询与使用它们的方法紧密耦合。如果您需要更改查询,则必须在使用它的每个地方进行更改。

我更喜欢使用独立的存储库(或服务接口,它们具有相似的功能)或者使用更多的CQRS系统进行命令/查询分离,并使用查询对象。


谢谢您的回答,您的意思是我可以删除所有UoW相关内容,因为Entity Framework支持它?但是我的参考资料是Scott Millett的《Pro ASP.NET设计模式》,他通过UOW模式涵盖了Entity Framework!也许在早期版本的Entity Framework中不支持? - Eric Nielsen
1
@EricNielsen - 嗯,它一直具有某种程度的UnitOfWork,但DbContext是在EF 4.1中添加的,它更简单和直接。无论如何,很多人仍然建议使用UoW,在大多数应用程序中,我只是不认为需要进一步抽象。 - Erik Funkenbusch

0

在UnitOfWork类中,您需要实现DBContext或ObjectContext。

无论系统如何,UnitOfWork都将隔离所有事务。EF仅用于数据库连接。即使您的系统只使用数据库,为了未来的扩展,最好还是保持一个单独的UnitOfWork类。

而在工作单元Commit()内部,您可以调用内部实现的DBContext.SaveChanges()。

这个DBContext将对在UnitOfWork中声明的所有存储库可访问。因此,存储库向DBContext添加或删除内容,而UnitOfWork则进行提交。

当您的场景涉及不同的存储(如云块、表存储等)时,您可以像实现EF上下文一样,在UnitofWork中实现它们。有些存储库可以访问Table Storage,有些可以访问EF上下文。

提示:实现ObjectContext而不是DBContext在缓存场景中具有优势,并且在扩展框架方面有更多选项。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接