使用实体框架的最小存储库实现

本文关键字:存储 实现 实体 框架 | 更新日期: 2023-09-27 18:01:26

我试图在我的应用程序中实现一个最小的通用存储库模式。我有一个非常小的接口来查询和保存数据:

public interface IRepository
{
    IQueryable<TEntity> Query<TEntity>() 
        where TEntity: BaseEntity;
    void Save<TEntity>(TEntity entity) 
        where TEntity : BaseEntity;
}

BaseEntity是我将存储在存储库中的所有对象的基类:

public abstract class BaseEntity
{
    public Guid Id { get; set; }    
    public DateTime CreatedDate { get; set; }
    public DateTime UpdatedDate { get; set; }
}

我试图使用实体框架找到这样一个简单的存储库的工作实现,但令人惊讶的是很难找到(人们正在使用UnitOfWork和其他东西,使实现比我想要的更复杂)。

所以我创建了我能想到的绝对最小的实现:

public class EfRepository : DbContext, IRepository
{
    public IQueryable<TEntity> Query<TEntity>() where TEntity : BaseEntity
    {
        return this.Set<TEntity>();
    }
    public void Save<TEntity>(TEntity entity) where TEntity : BaseEntity
    {
        if (entity.Id == default(Guid))
        {
            entity.Id = Guid.NewGuid();
            this.Set<TEntity>().Add(entity);
        }
        else
        {
            this.Entry(entity).State = EntityState.Modified;
        }       
        this.SaveChanges();
    }
    public DbSet<User> Users { get; set; } // User is a subclass of BaseEntity
    //Other DbSet's...
}

现在,我的问题是这样的实现是否正确。我问,因为我是新的实体框架,我担心可能的性能问题或事情,可能会出错,而使用这样的存储库。

注意:我这么做有两个原因:

  • 用于测试目的,以便我可以在单元测试项目中创建存储库的模拟
  • 这是可能的,我将不得不切换到另一个ORM在未来,我想使这个过渡尽可能容易。

使用实体框架的最小存储库实现

首先,存储库是有争议的。出于各种原因,有很多人强烈反对它,也有很多人在使用它(或习惯它?)在互联网上有许多关于利弊的无休止的讨论的文章。这是由你来决定你是否真的需要在你的项目中存储库模式-让我们不要专注于你问"如何在c#中做到这一点?"而不是"我应该这样做吗?"。

您的存储库实现扩展了DbContext。这意味着您不能有效地创建一个跨多个存储库(多个实体类型)的事务,因为每个存储库都有自己的DbContext(因为它是上下文)。在底层,DbContext跟踪对实体所做的更改。如果你有一个以上的上下文,并试图同时保存两个上下文,它们就不会知道彼此的存在。这给我们留下了一个问题——如果SaveChanges()的第一次调用成功,第二次调用失败,如何回滚第一个?它已经被保存了?这就是工作单元出现的地方。

所以首先你需要一个存储库接口——作为实体的集合:

public interface IRepository<TEntity>
{
    TEntity Get(Expression<Func<TEntity, bool>> predicate);
    IEnumerable<TEntity> GetAll();
    IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> predicate);
    void Add(TEntity entity);
    void AddAll(IEnumerable<TEntity> entities);
    void Remove(TEntity entity);
    void RemoveAll(IEnumerable<TEntity> entities);
}

和工作单元:

public interface IUnitOfWork : IDisposable
{
    // Commit all the changes 
    void Complete();
    // Concrete implementation -> IRepository<Foo>
    // Add all your repositories here:
    IFooRepository Foos {get;}
}

基类如下所示:

public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class
{
    protected DbContext Context { get; private set; }
    public BaseRepository(DbContext dbContext)
    {
        Context = dbContext;
    }
    public virtual TEntity Get(Expression<Func<TEntity, bool>> predicate)
    {
        return Context.Set<TEntity>().Where(predicate).FirstOrDefault();
    }
    public virtual IEnumerable<TEntity> GetAll()
    {
        return Context.Set<TEntity>().ToList();
    }
    public virtual IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> predicate)
    {
        return Context.Set<TEntity>().Where(predicate).ToList();
    }
    public void Add(TEntity entity)
    {
        var entry = Context.Entry(entity);
        if(entry.State == EntityState.Detached)
        {
            Context.Set<TEntity>().Add(entity);
        }
        else
        {
            entry.State = EntityState.Modified;
        }
    }
    public void AddAll(IEnumerable<TEntity> entities)
    {
        foreach(var entity in entities)
        {
            Add(entity);
        }
    }
    public void Remove(TEntity entity)
    {
        var entry = Context.Entry(entity);
        if (entry.State == EntityState.Detached)
        {
            Context.Set<TEntity>().Attach(entity);
        }
        Context.Entry<TEntity>(entity).State = EntityState.Deleted;
    }
    public void RemoveAll(IEnumerable<TEntity> entities)
    {
        foreach (var entity in entities)
        {
            Remove(entity);
        }
    }
}

和工作实现单元:

public class UnitOfWork : IUnitOfWork
{
    private readonly ApplicationDbContext _dbContext;
    private IFooRepository _fooRepo;
    public UnitOfWork(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext;
        // Each repo will share the db context:
        _fooRepo = new FooRepository(_dbContext);
    }

    public IFooRepository Foos
    {
        get
        {
            return _fooRepo;
        }
    }
    public void Complete()
    {
        _dbContext.SaveChanges();
    }
    public void Dispose()
    {
        _dbContext.Dispose();
    }
}