【问题标题】:Minimal Repository implementation using Entity Framework使用实体框架的最小存储库实现
【发布时间】:2016-08-09 14:44:57
【问题描述】:

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

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; }
}

我试图使用 Entity Framework 找到这样一个简单存储库的工作实现,但发现起来非常困难(人们正在使用 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...
}

现在,我的问题是这样的实现是否正确。我之所以问,是因为我是 Entity Framework 的新手,我担心在使用此类存储库时可能出现性能问题或可能出错的事情。

注意:我尝试这样做有两个原因:

  • 出于测试目的,以便我可以在我的单元测试项目中创建存储库的模拟
  • 将来我可能不得不切换到另一个 ORM,并且我希望让这种过渡尽可能简单。

【问题讨论】:

  • 如果您只是轻描淡写并且有性能问题,那么 EF 是否是正确的 ORM 可能值得考虑。从个人经验来看,我没有发现 EF 在我使用过的任何项目中都表现得非常好,而且保持其映射与数据库或代码一致有点笨拙(取决于您选择的路径、代码或数据库优先)。每当有人提到 EF 时,我认识的任何在高吞吐量环境中工作的人都会畏缩不前。
  • 真正的问题是:既然 EF 已经 实现了存储库 (DbSet&lt;T&gt;) 和工作单元 (DbContext) 模式 - 为什么要重新发明轮子呢上面还有一层??
  • @marc_s,我有两个原因 :) 请看我的编辑
  • 原因 #1 在 EF6 中是不必要的 - 您可以使用 Rowan Miller 的 EntityFramework.Testing NuGet 包轻松模拟 DbSet&lt;T&gt;DbContext ....
  • 我仍在等待理由 2 的真实示例。YAGNI

标签: c# entity-framework repository-pattern


【解决方案1】:

首先,存储库是有争议的。由于各种原因,有很多人强烈反对并使用它(或习惯它?)。互联网上有很多文章,无休止地讨论利弊。由您决定您的项目中是否真的需要存储库模式 - 当您问“如何在 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();
    }
}

【讨论】:

  • 您无法有效地创建跨越多个存储库的事务 - 这并不完全正确。我可以创建一个TransactionScope 并将其包装在所有必要的存储库调用中。默认情况下(没有事务范围),我的存储库实现将在每次调用 Save 时提交所有更改,而这种默认行为是我在大多数情况下所需要的。
  • 您的Save() 将使用相同的DbContext cammit 来自所有存储库的所有修改实体,无论您将调用哪个存储库。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多