【问题标题】:MVC5 + EF + UOW + Services Layer where to call SaveChanges() and avoid multiple trips to DB?MVC5 + EF + UOW + 服务层在哪里调用 SaveChanges() 并避免多次访问 DB?
【发布时间】:2014-08-28 06:33:39
【问题描述】:

我正在使用 MVC5EF Code First 以及 RepositoryUnit of Work 构建一个 Web 应用程序> 图案。到现在我有 3 层:

  • “数据层”,其中包含存储库 UOW。
  • “服务层”参考 UOW 实现业务逻辑和业务验证。
  • “Web 层”负责通过与服务层通信来显示数据。

我的域/业务对象在另一个项目中分离。所以基本上我遵循 John Papa CodeCamper 结构,除了添加“服务层”。

数据/合同/IRepository.cs

public interface IRepository<T> where T : class
{
    IQueryable<T> GetAll();
    T GetById(int id);
    void Add(T entity);
    void Update(T entity);
    void Delete(T entity);
    void Delete(int id);
}

数据/合同/IUnitOfWork.cs

public interface IUnitOfWork
{
    // Save pending changes to the data store.
    void Commit();

    // Repositories
    IRepository<Event> Events { get; }
    IRepository<Candidate> Candidates { get; }
}

数据/EFRepository.cs

/// <summary>
/// The EF-dependent, generic repository for data access
/// </summary>
/// <typeparam name="T">Type of entity for this Repository.</typeparam>
public class EFRepository<T> : IRepository<T> where T : class
{
    public EFRepository(DbContext dbContext)
    {
        if (dbContext == null)
            throw new ArgumentNullException("dbContext");
        DbContext = dbContext;
        DbSet = DbContext.Set<T>();
    }

    protected DbContext DbContext { get; set; }

    protected DbSet<T> DbSet { get; set; }

    public virtual IQueryable<T> GetAll()
    {
        return DbSet;
    }

    public virtual T GetById(int id)
    {
        //return DbSet.FirstOrDefault(PredicateBuilder.GetByIdPredicate<T>(id));
        return DbSet.Find(id);
    }

    public virtual void Add(T entity)
    {
        DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
        if (dbEntityEntry.State != EntityState.Detached)
        {
            dbEntityEntry.State = EntityState.Added;
        }
        else
        {
            DbSet.Add(entity);
        }
    }

    public virtual void Update(T entity)
    {
        DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
        if (dbEntityEntry.State == EntityState.Detached)
        {
            DbSet.Attach(entity);
        }
        dbEntityEntry.State = EntityState.Modified;
    }

    public virtual void Delete(T entity)
    {
        DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
        if (dbEntityEntry.State != EntityState.Deleted)
        {
            dbEntityEntry.State = EntityState.Deleted;
        }
        else
        {
            DbSet.Attach(entity);
            DbSet.Remove(entity);
        }
    }

    public virtual void Delete(int id)
    {
        var entity = GetById(id);
        if (entity == null) return; // not found; assume already deleted.
        Delete(entity);
    }
}

数据/UnitOfWork.cs

/// <summary>
/// The "Unit of Work"
///     1) decouples the repos from the controllers
///     2) decouples the DbContext and EF from the controllers
///     3) manages the UoW
/// </summary>
/// <remarks>
/// This class implements the "Unit of Work" pattern in which
/// the "UoW" serves as a facade for querying and saving to the database.
/// Querying is delegated to "repositories".
/// Each repository serves as a container dedicated to a particular
/// root entity type such as a <see cref="Url"/>.
/// A repository typically exposes "Get" methods for querying and
/// will offer add, update, and delete methods if those features are supported.
/// The repositories rely on their parent UoW to provide the interface to the
/// data layer (which is the EF DbContext in this example).
/// </remarks>
public class UnitOfWork : IUnitOfWork, IDisposable
{
    public UnitOfWork(IRepositoryProvider repositoryProvider)
    {
        CreateDbContext();

        repositoryProvider.DbContext = DbContext;
        RepositoryProvider = repositoryProvider;       
    }

    // Repositories
    public IRepository<Student> Students { get { return GetStandardRepo<Event>(); } }
    public IRepository<Course> Courses { get { return GetStandardRepo<Course>(); } }

    /// <summary>
    /// Save pending changes to the database
    /// </summary>
    public void Commit()
    {
        //System.Diagnostics.Debug.WriteLine("Committed");
        DbContext.SaveChanges();
    }

    protected void CreateDbContext()
    {
        DbContext = new UnicornsContext();

        // Do NOT enable proxied entities, else serialization fails
        DbContext.Configuration.ProxyCreationEnabled = false;

        // Load navigation properties explicitly (avoid serialization trouble)
        DbContext.Configuration.LazyLoadingEnabled = false;

        // Because Web API will perform validation, I don't need/want EF to do so
        DbContext.Configuration.ValidateOnSaveEnabled = false;
    }

    protected IRepositoryProvider RepositoryProvider { get; set; }

    private IRepository<T> GetStandardRepo<T>() where T : class
    {
        return RepositoryProvider.GetRepositoryForEntityType<T>();
    }
    private T GetRepo<T>() where T : class
    {
        return RepositoryProvider.GetRepository<T>();
    }

    private UnicornsContext DbContext { get; set; }

    #region IDisposable

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (DbContext != null)
            {
                DbContext.Dispose();
            }
        }
    }

    #endregion
}

然后最后使用Ninject解决依赖:

kernel.Bind<RepositoryFactories>().To<RepositoryFactories>().InSingletonScope();
kernel.Bind<IRepositoryProvider>().To<RepositoryProvider>();
kernel.Bind<IUnitOfWork>().To<UnitOfWork>();

我应该在哪里调用UOW.Commit(),以便我可以在其他服务中重用特定服务的已实现逻辑,而不是重新编写它?

  1. 控制器是否应该负责提交更改?
  2. 或者服务本身是否应该提交更改?

据我在 Stack Overflow 上阅读的内容,option(1) 更简单,但违反了单一职责原则,或者如果我想与移动/桌面应用程序集成会出现什么情况。

Option(2): 这里我必须在每个服务函数调用中调用 commit,因此我将无法重用函数因为这可能会导致多次访问 DB。

【问题讨论】:

  • 仅供参考,您所做的没有将 EF 与您的 Web 层...或控制器分离。您的存储库返回 IQueryable,无论如何它们都将与实体框架紧密耦合。
  • @ErikFunkenbusch 你能解释一下你的观点或者你想说什么吗?

标签: c# design-patterns asp.net-mvc-5 entity-framework-6 unit-of-work


【解决方案1】:

在我的项目中,我在控制器中调用 Save(),因为我想尽可能地重用我的方法,并且当它们自己调用 Save() 时,将多个方法“粘合”在一起是很困难的。 更糟糕的是,如果控制器级别出现错误,您可能希望避免调用 save(),例如某些方法创建了一堆小对象,这些对象在必须提交的主对象中使用。同时提交似乎比在 createSMallObject() 和 createMasterObject() 中硬编码提交要好,以防在两者之间发生某些事情。

【讨论】:

  • 如果我想通过 API 在移动应用程序中使用我的服务层或在桌面应用程序中使用它怎么办?我这里没有控制器
  • 添加一个必须显式调用的 save() 方法?毕竟,这反映了 ef 设计......
猜你喜欢
  • 2012-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-29
相关资源
最近更新 更多