22

这是我第一次实施更受领域驱动的设计方法。我决定尝试洋葱架构,因为它专注于域而不是基础设施/平台/等。

在此处输入图像描述

为了从实体框架中抽象出来,我创建了一个具有工作单元实现的通用存储库

IRepository<T>接口IUnitOfWork

public interface IRepository<T>
{
    void Add(T item);

    void Remove(T item);

    IQueryable<T> Query();
}

public interface IUnitOfWork : IDisposable
{
    void SaveChanges();
}

IRepository<T>和的实体框架实现IUnitOfWork

public class EntityFrameworkRepository<T> : IRepository<T> where T : class
{
    private readonly DbSet<T> dbSet;

    public EntityFrameworkRepository(IUnitOfWork unitOfWork)
    {
        var entityFrameworkUnitOfWork = unitOfWork as EntityFrameworkUnitOfWork;

        if (entityFrameworkUnitOfWork == null)
        {
            throw new ArgumentOutOfRangeException("Must be of type EntityFrameworkUnitOfWork");
        }

        dbSet = entityFrameworkUnitOfWork.GetDbSet<T>();
    }

    public void Add(T item)
    {
        dbSet.Add(item);
    }

    public void Remove(T item)
    {
        dbSet.Remove(item);
    }

    public IQueryable<T> Query()
    {
        return dbSet;
    }
}

public class EntityFrameworkUnitOfWork : IUnitOfWork
{
    private readonly DbContext context;

    public EntityFrameworkUnitOfWork()
    {
        this.context = new CustomerContext();;
    }

    internal DbSet<T> GetDbSet<T>()
        where T : class
    {
        return context.Set<T>();
    }

    public void SaveChanges()
    {
        context.SaveChanges();
    }

    public void Dispose()
    {
        context.Dispose();
    }
}

客户存储库:

public interface ICustomerRepository : IRepository<Customer>
{

}

public class CustomerRepository : EntityFrameworkRepository<Customer>, ICustomerRepository 
{
    public CustomerRepository(IUnitOfWork unitOfWork): base(unitOfWork)
    {
    }
}

使用存储库的 ASP.NET MVC 控制器:

public class CustomerController : Controller
{
    UnityContainer container = new UnityContainer();

    public ActionResult List()
    {
        var unitOfWork = container.Resolve<IUnitOfWork>();
        var customerRepository = container.Resolve<ICustomerRepository>();

        return View(customerRepository.Query());
    }

    [HttpPost]
    public ActionResult Create(Customer customer)
    {
        var unitOfWork = container.Resolve<IUnitOfWork>();
        var customerRepository = container.Resolve<ICustomerRepository>();; 

        customerRepository.Add(customer);

        unitOfWork.SaveChanges();

        return RedirectToAction("List");
    }
}

统一的依赖注入:

container.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>();
container.RegisterType<ICustomerRepository, CustomerRepository>();

解决方案:

在此处输入图像描述

问题?

  • 存储库实现(EF 代码)非常通用。这一切都在EntityFrameworkRepository<T>课堂上。具体模型存储库不包含任何此类逻辑。这使我免于编写大量冗余代码,但可能会牺牲灵活性?

  • ICustomerRepositoryCustomerRepository类基本上是空的。它们纯粹是为了提供抽象。据我了解,这符合 Onion 架构的愿景,在这种架构中,基础设施和平台相关代码位于系统外部,但空类和空接口感觉不对?

  • 要使用不同的持久性实现(例如 Azure 表存储),则CustomerRepository需要创建一个新类并继承一个AzureTableStorageRepository<T>. 但这可能会导致冗余代码(多个 CustomerRepositories)?这将如何影响嘲笑?

  • 另一种实现(例如 Azure 表存储)对跨国支持有限制,因此 AzureTableStorageUnitOfWork 类在这种情况下不起作用。

我这样做的方式还有其他问题吗?

(我的大部分灵感来自这篇文章

4

3 回答 3

24

我可以说这段代码第一次尝试就足够了,但它确实有一些地方需要改进。

让我们来看看其中的一些。

1.依赖注入(DI)和IoC的使用。

您使用最简单的服务定位器模式版本——container实例本身。

我建议您使用“构造函数注入”。您可以在此处找到更多信息(ASP.NET MVC 4 依赖注入)

public class CustomerController : Controller
{
    private readonly IUnitOfWork unitOfWork;
    private readonly ICustomerRepository customerRepository;

    public CustomerController(
        IUnitOfWork unitOfWork, 
        ICustomerRepository customerRepository)
    {
        this.unitOfWork = unitOfWork;
        this.customerRepository = customerRepository;
    }

    public ActionResult List()
    {
        return View(customerRepository.Query());
    }

    [HttpPost]
    public ActionResult Create(Customer customer)
    {
        customerRepository.Add(customer);
        unitOfWork.SaveChanges();
        return RedirectToAction("List");
    }
}

2. 工作单元 (UoW) 范围。

我找不到IUnitOfWorkand的生活方式ICustomerRepository。我对 Unity 不熟悉,但msdn 说默认使用 TransientLifetimeManager。这意味着每次解析类型时都会获得一个新实例。

因此,以下测试失败:

[Test]
public void MyTest()
{
    var target = new UnityContainer();
    target.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>();
    target.RegisterType<ICustomerRepository, CustomerRepository>();

    //act
    var unitOfWork1 = target.Resolve<IUnitOfWork>();
    var unitOfWork2 = target.Resolve<IUnitOfWork>();

    // assert
    // This Assert fails!
    unitOfWork1.Should().Be(unitOfWork2);
} 

而且我希望UnitOfWork您的控制器中的实例与UnitOfWork您的存储库中的实例不同。有时它可能会导致错误。但它没有在ASP.NET MVC 4 依赖注入中作为 Unity 的问题突出显示。

Castle Windsor PerWebRequest生活方式用于在单个 http 请求中共享相同的类型实例。

PerWebRequest组件UnitOfWork是常用的方法。自定义可用于在方法调用期间调用。ActionFilterCommit()OnActionExecuted()

我还将重命名该SaveChanges()方法并简单地调用它,Commit因为它在示例PoEAA中被调用。

public interface IUnitOfWork : IDisposable
{
    void Commit();
}

3.1。对存储库的依赖。

如果您的存储库将是“空的”,则不需要为它们创建特定的接口。可以在您的控制器中解决IRepository<Customer>并拥有以下代码

public CustomerController(
    IUnitOfWork unitOfWork, 
    IRepository<Customer> customerRepository)
{
    this.unitOfWork = unitOfWork;
    this.customerRepository = customerRepository;
}

有一个测试可以测试它。

[Test]
public void MyTest()
{
    var target = new UnityContainer();
    target.RegisterType<IRepository<Customer>, CustomerRepository>();

    //act
    var repository = target.Resolve<IRepository<Customer>>();

    // assert
    repository.Should().NotBeNull();
    repository.Should().BeOfType<CustomerRepository>();
}

但是,如果您希望拥有“在查询构造代码集中的映射层之上的抽象层”的存储库。(PoEAA,存储库

存储库在域和数据映射层之间进行调解,就像内存中的域对象集合一样。客户端对象以声明方式构造查询规范,并将它们提交给 Repository 以获得满意。

3.2. 继承 EntityFrameworkRepository。

在这种情况下,我将创建一个简单的IRepository

public interface IRepository
{
    void Add(object item);

    void Remove(object item);

    IQueryable<T> Query<T>() where T : class;
}

以及它的实现,它知道如何使用 EntityFramework 基础架构并且可以很容易地被另一个(例如AzureTableStorageRepository)替换。

public class EntityFrameworkRepository : IRepository
{
    public readonly EntityFrameworkUnitOfWork unitOfWork;

    public EntityFrameworkRepository(IUnitOfWork unitOfWork)
    {
        var entityFrameworkUnitOfWork = unitOfWork as EntityFrameworkUnitOfWork;

        if (entityFrameworkUnitOfWork == null)
        {
            throw new ArgumentOutOfRangeException("Must be of type EntityFrameworkUnitOfWork");
        }

        this.unitOfWork = entityFrameworkUnitOfWork;
    }

    public void Add(object item)
    {
        unitOfWork.GetDbSet(item.GetType()).Add(item);
    }

    public void Remove(object item)
    {
        unitOfWork.GetDbSet(item.GetType()).Remove(item);
    }

    public IQueryable<T> Query<T>() where T : class
    {
        return unitOfWork.GetDbSet<T>();
    }
}

public interface IUnitOfWork : IDisposable
{
    void Commit();
}

public class EntityFrameworkUnitOfWork : IUnitOfWork
{
    private readonly DbContext context;

    public EntityFrameworkUnitOfWork()
    {
        this.context = new CustomerContext();
    }

    internal DbSet<T> GetDbSet<T>()
        where T : class
    {
        return context.Set<T>();
    }

    internal DbSet GetDbSet(Type type)
    {
        return context.Set(type);
    }

    public void Commit()
    {
        context.SaveChanges();
    }

    public void Dispose()
    {
        context.Dispose();
    }
}

现在CustomerRepository可以成为代理并引用它。

public interface IRepository<T> where T : class
{
    void Add(T item);

    void Remove(T item);
}

public abstract class RepositoryBase<T> : IRepository<T> where T : class
{
    protected readonly IRepository Repository;

    protected RepositoryBase(IRepository repository)
    {
        Repository = repository;
    }

    public void Add(T item)
    {
        Repository.Add(item);
    }

    public void Remove(T item)
    {
        Repository.Remove(item);
    }
}

public interface ICustomerRepository : IRepository<Customer>
{
    IList<Customer> All();

    IList<Customer> FindByCriteria(Func<Customer, bool> criteria);
}

public class CustomerRepository : RepositoryBase<Customer>, ICustomerRepository
{
    public CustomerRepository(IRepository repository)
        : base(repository)
    { }

    public IList<Customer> All()
    {
        return Repository.Query<Customer>().ToList();
    }

    public IList<Customer> FindByCriteria(Func<Customer, bool> criteria)
    {
        return Repository.Query<Customer>().Where(criteria).ToList();
    }
}
于 2013-12-10T19:46:40.083 回答
2

我看到的唯一缺点是您高度依赖您的 IOC 工具,因此请确保您的实施是可靠的。然而,这并不是洋葱设计所独有的。我在多个项目中使用过 Onion,并没有遇到任何真正的“陷阱”。

于 2013-12-09T14:46:09.267 回答
0

我在代码中看到了几个严重的问题。

第一个问题是存储库和 UoW 之间的关系。

    var unitOfWork = container.Resolve<IUnitOfWork>();
    var customerRepository = container.Resolve<ICustomerRepository>();

这是隐式依赖。没有 UoW,存储库将无法正常工作!并非所有存储库都需要与 UoW 连接。例如存储过程呢?您有存储过程,并将其隐藏在存储库后面。存储过程调用使用单独的事务!至少不是在所有情况下。因此,如果我解析唯一的存储库并添加项目,那么它将无法正常工作。此外,如果我设置瞬态生命许可证,此代码将不起作用,因为存储库将有另一个 UoW 实例。所以我们有紧密的隐式耦合。

第二个问题是在 DI 容器引擎之间创建紧密耦合并将其用作服务定位器!服务定位器不是实现 IoC 和聚合的好方法。在某些情况下,它是反模式。应使用 DI 容器

于 2016-11-02T15:31:42.700 回答