【问题标题】:.net core - unit of work generic repository pattern.net core - 工作单元通用存储库模式
【发布时间】:2019-02-25 14:21:00
【问题描述】:

我希望迁移一个收集产品信息的大型项目。当前应用程序使用 CSLA 作为其框架(传统的 aspx 形式)。

我想将应用程序迁移/开发到 .net core 2.1。我有 MVC 开发经验(4 年以上),最近接触过 .net 核心。

我想使用 EF Core 来调用现有的存储过程。我正在考虑使用工作单元通用存储库设计模式。我以前使用过存储库模式,发现它非常有用。

作为当前流程中存在的功能的一部分,它包含一个主表和一个编辑表结构格式。当有人编辑产品时,会在 Edits 表中插入一个新行。一旦得到管理员的批准,它就会在主表中插入/更新当前行。

在 .net core 2.1 中使用“工作单元/通用存储库模式”对其他开发人员是否有效?

您遇到过什么问题? 我的目标是开发一个高性能、高效的驱动应用程序。

欢迎任何其他想法和建议。谢谢。

【问题讨论】:

  • 如果您使用的是实体框架,它已经使用了存储库模式。 DbContext 是您的工作单元,DbSet<Entity> 是您的存储库。实体框架的设计方式强制与应用程序耦合,这对于某些场景来说很好。如果您使用实体框架,请不要使用存储库模式。
  • 是的,我倾向于使用 EF。我已经读过 Unity of Work 模式已经存在。在模型/服务层中提供另一个层将允许我实现“工作单元”/通用存储库模式以与 DBContext 交互。我正在考虑可能调用存储过程来操作 CRUD,因为存储过程中有一些逻辑。另一种方法是使用上下文/实体与使用存储过程相对应。拥有 sp 有其好处,例如分离以编辑/调整 SQL,而不是为任何 SQL 更改重新编译应用程序等

标签: .net asp.net-core .net-core entity-framework-core asp.net-core-2.1


【解决方案1】:

就个人而言,我使用工作单元来减少大量的依赖注入。我可以有一个数据库工作单元,一旦我使用依赖注入在该工作单元中注入数据库上下文,我就不需要在我想使用它们的地方注入每个模型存储库,而只需从工作单位。这也有助于我仅在需要时以特定方法实例化存储库。

public interface IDatabaseUnitOfWork
{
    DbContext DatabaseContext { get; }
    Task<bool> Save();

    IBaseRepository<UserAccount> UserAccountRepository { get; }
}

public class DatabaseUnitOfWork : IDatabaseUnitOfWork
{
    private IBaseRepository<UserAccount> _userAccountRepository;

    public DatabaseUnitOfWork(DbContext databaseContext)
    {
        DatabaseContext = databaseContext;
    }

    public DbContext DatabaseContext { get; private set; }

    public async Task<bool> Save()
    {
        try
        {
            int _save = await DatabaseContext.SaveChangesAsync();
            return await Task.FromResult(true);
        }
        catch (System.Exception e)
        {
            return await Task.FromResult(false);
        }
    }

    public IBaseRepository<UserAccount> UserAccountRepository
    {
        get
        {
            if (_userAccountRepository == null)
            {
                _userAccountRepository = new BaseRepository<UserAccount>(DatabaseContext);
            }
            return _userAccountRepository;
        }
    }
}

然后

services.AddScoped<IDatabaseUnitOfWork, DatabaseUnitOfWork>();
services.AddScoped<IServiceUnitOfWork, ServiceUnitOfWork>();

终于

public class DemoClass
    {
        private IServiceUnitOfWork _serviceUnitOfWork;
        public DemoClass(IServiceUnitOfWork serviceUnitOfWork)
        {
            _serviceUnitOfWork = serviceUnitOfWork;
        }

        Public bool CreateUserAccount(UserAccount userAccount){
            await _serviceUnitOfWork.UserAccountRepository.Add(userAccount);
            return await _serviceUnitOfWork.Save();
        }

       ----
   }

更新

通用基础存储库

public interface IBaseRepository<T> where T : class
{
    Task<bool> Add(T entity);

    Task<List<T>> GetAll();

    Task<List<T>> GetAll(params Expression<Func<T, object>>[] includes);

    Task<List<T>> SearchBy(Expression<Func<T, bool>> searchBy, params Expression<Func<T, object>>[] includes);

    Task<T> FindBy(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includes);

    Task<bool> Update(T entity);

    Task<bool> Delete(Expression<Func<T, bool>> identity, params Expression<Func<T, object>>[] includes);

    Task<bool> Delete(T entity);

}

public class BaseRepository<T> : IBaseRepository<T> where T : class
{
    private DbContext _ctx;

    public BaseRepository(DbContext context)
    {
        _ctx = context;
    }

    public virtual async Task<bool> Add(T entity)
    {
        try
        {
            _ctx.Set<T>().Add(entity);
            return  await Task.FromResult(true);
        }
        catch (Exception e)
        {
            return  await Task.FromResult(false);
        }
    }

    public virtual async Task<List<T>> GetAll()
    {
        return _ctx.Set<T>().ToList();
    }

    public virtual async Task<List<T>> GetAll(params Expression<Func<T, object>>[] includes)
    {
        var result = _ctx.Set<T>().Where(i => true);

        foreach (var includeExpression in includes)
            result = result.Include(includeExpression);

        return await result.ToListAsync();
    }


    public virtual async Task<List<T>> SearchBy(Expression<Func<T, bool>> searchBy, params Expression<Func<T, object>>[] includes)
    {
        var result = _ctx.Set<T>().Where(searchBy);

        foreach (var includeExpression in includes)
            result = result.Include(includeExpression);

        return await result.ToListAsync();
    }

    /// <summary>
    /// Finds by predicate.
    /// http://appetere.com/post/passing-include-statements-into-a-repository
    /// </summary>
    /// <param name="predicate">The predicate.</param>
    /// <param name="includes">The includes.</param>
    /// <returns></returns>
    public virtual async Task<T> FindBy(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includes)
    {
        var result = _ctx.Set<T>().Where(predicate);

        foreach (var includeExpression in includes)
            result = result.Include(includeExpression);

        return await result.FirstOrDefaultAsync();
    }

    public virtual async Task<bool> Update(T entity)
    {
        try
        {
            _ctx.Set<T>().Attach(entity);
            _ctx.Entry(entity).State = EntityState.Modified;

            return  await Task.FromResult(true);
        }
        catch (Exception e)
        {
            return  await Task.FromResult(false);
        }
    }

    public virtual async Task<bool> Delete(Expression<Func<T, bool>> identity, params Expression<Func<T, object>>[] includes)
    {
        var results = _ctx.Set<T>().Where(identity);

        foreach (var includeExpression in includes)
            results = results.Include(includeExpression);
        try
        {
            _ctx.Set<T>().RemoveRange(results);
            return  await Task.FromResult(true);
        }
        catch (Exception e)
        {
            return  await Task.FromResult(false);
        }
    }

    public virtual async Task<bool> Delete(T entity)
    {
        _ctx.Set<T>().Remove(entity);
        return await Task.FromResult(true);
    }

}

扩展基本存储库(例如。DeleteAllAccounts

public interface IUserAccountRepository : IBaseRepository<UserAccount>
    {
        Task DeleteAllAccounts();
    }

    public class UserAccountRepository : BaseRepository<UserAccount>, IUserAccountRepository
    {
        private DbContext _databaseContext;
        public UserAccountRepository(DbContext databaseContext) : base(databaseContext)
        {
            _databaseContext = databaseContext;
        }

        public async Task DeleteAllAccounts()
        {
            ......
        }
    }

所以不要使用 _userAccountRepository = new BaseRepository&lt;UserAccount&gt;(DatabaseContext); 你会使用 _userAccountRepository = new UserAccountRepository(DatabaseContext);

【讨论】:

  • 很好的例子。我假设在您的 UserAccountRepository 中,您拥有对 EF 的 BL 和数据访问权限,例如访问方法 serviceUnitOfWork.UserAccountRepository.Add(userAccount)。?
  • 是的,我愿意。但我通常做的是使用AddUpdateDelete 等基本方法创建一个通用存储库 (BaseRepository)。然后,如果我想要一些超出基本 CRUD 操作的东西,我会为特定模型扩展基本存储库。
  • 您的存储库是泄漏的抽象。比如说我不想再使用 EF 并且想用 LiteDb 数据库替换这个实现。 LiteDb 不支持Expression&lt;Func&lt;&gt;&gt;,因此无法用其他任何东西替换 EF。还有你是怎么做交易的?查看您的存储库,您不能。
  • 您所要做的就是为 LiteDb 编写另一个存储库或手动解释 Expression&lt;Func&lt;&gt;&gt; 是什么,然后将其转换为 LiteDb 可以理解的内容。这样,您可以利用 ORM 或直接数据库连接提供的全部功能,并为您提供极大的灵活性。我曾经按照您建议的方式进行,结果不是很令人满意。
  • 用你的DemoClass例子,你会从你的控制器动作中调用它吗?我总是喜欢让控制器尽可能亮。例子; public ActionResult index(){ DemoClass demo = new DemoClass(); var model = demo.CreateUserAccount(UserAccount userAccount) return View(model); }
【解决方案2】:

我将使用经典的“视情况而定”。本质上,DbSet&lt;&gt; 是一个存储库,DbContext 是一个工作单元。但这并不意味着您不能在更高的抽象级别上使用存储库模式或工作单元模式。

我的建议是在你需要它之​​前不要使用它。我在 EF Core 中使用了一个单元或工作模式,发现它在您使用多个 DbContexts 或多个数据库提供程序的情况下非常有用。我还没有找到任何其他实例可以使用它。

我倾向于从不使用通用存储库,因为我更喜欢封装我的查询,并将它们放在我的ControllerPages 之外。


已更新以回答以下评论:

这是一个很好的问题,但不是那么容易回答,而且肯定超出了 StackOverflow.com 的范围,因为工艺通常被认为是意见。与代码工艺直接相关的问题有一个堆栈交换https://softwareengineering.stackexchange.com

不过,我会说,你至少提出了这个问题,我为你感到非常兴奋。

我个人的意见是推荐“领域驱动设计”。关于这个主题有很多免费资源,但要参考 Eric Evans 的书。

本质上,您的业务逻辑是核心,所有依赖项都指向内部。因此,即使是“服务层”也不会包含您的业务逻辑。

【讨论】:

  • @adminvincet 打开这篇文章后,我开始质疑自己的开发风格。特别是知道我的 BL 的最佳位置在哪里。在过去的项目中,我将我的 BL 放在存储库中,同一个存储库将针对启动的 DBContext 进行保存。在开发工作单元模式时,您通常将 BL 放在哪里?我一直在阅读,服务层最好将 BL 放入其中,但似乎它增加了不必要的抽象层。你的想法是什么?谢谢
  • @Paul 查看更新后的答案。我会说这是一个意见,其他答案也没有错。
猜你喜欢
  • 1970-01-01
  • 2016-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-27
  • 1970-01-01
相关资源
最近更新 更多