2016-12-21 29 views
3

我在寫一個合理的'純粹'的DDD應用程序。我沒有使用CQRS。持久性是使用EF6的基礎架構服務。如何在使用實體框架和DDD時實例化新對象?

現在,假設我有一些方法需要創建一個新的A型實體並將其添加到其他實體(B)的導航屬性集合中。該方法將位於域程序集中的某處,或者可能位於應用程序服務程序集中。

如果我正在編寫一個小應用程序,我只需調用DBSet.Create方法,以確保我得到了代理對象的引用(使用延遲加載),然後我可以將它添加到B的導航屬性中。

但是,考慮到我的應用程序和域程序集並沒有意識到我正在使用EF來實現持久性,我如何確保不會中斷延遲加載?如果我只是調用A的構造函數,那麼我沒有代理對象。我應該在應用服務中處理這個事實(感覺全部錯誤),或者讓構造函數受到保護,然後將工廠傳遞給有問題的域/應用服務?

編輯:我這樣做都錯了嗎?也許我可以降低/消除我的問題如下:

  • 我已經習慣了能夠添加到導航集合EF,保存,然後填充有我收集的另一端。但是,在創建DDD時,我想我應該填充新實體的任何關係的兩端?這將消除我的導航問題。
  • 我還需要一個代理對象嗎?在大多數情況下,我不會猜測。在持續存在新實體的事務發生之前,無需從數據庫中獲取任何內容。
  • 如果我試圖保持DBContext的生命期不僅僅是創建(插入)事務,那麼我需要讓我的持久層將新實體添加到DBContext(這意味着跟蹤哪些已創建),或者傳入DBContext的DBSet.Create方法作爲工廠使用的構造函數。
+2

爲什麼你需要一個新實體的代理對象?它即將被插入。在我看來,它可以在應用程序服務或域中,這取決於。例如,如果新對象是聚合的一部分,那麼聚合負責創建。 – jannagy02

+0

嗨,謝謝。您需要一個代理對象來避免集合上的空引用異常。它爲什麼幾乎沒有關係,它更多的是關於如何防止這些持久性問題泄漏到域。我認爲答案必須是一個傳入的工廠,這被應用服務所使用。 – Chalky

+2

當你想創建一個新的A型實體並將其添加到其他實體(B)的導航屬性集合中時,只需執行它。該域仍然是持久性無知的,你不需要延遲加載,因爲它不在數據庫中,當你調用SaveChanges時,EF會插入它。 – jannagy02

回答

0

,我與你

合理的 '純' DDD應用

看到的最大的問題是,你的持久先於一切。如果您還記得藍皮書及其模式(如Factory創建聚合),那麼創建聚合的理由是創建處於有效狀態的域對象。它與持久性無關。

持久性邏輯更適合放置在您的存儲庫中。域應該做它認爲要做的事 - 域邏輯。持久性應該注意您的域模型以某種方式持續存在。很好,如果你選擇EF - 只要做適當的映射,並知道阻抗不匹配。但是,在您致電repository.Add(myNewAggregate)之前,對EF內部結構進行思考是沒有意義的。

如果您對使用EF的一些DDD戰術模式的簡化,定型實現的示例感興趣,您可以在下面找到一個。

免責聲明:我剛寫過它,沒有用實際數據庫測試過。沒有身份處理,沒有配置IoC/DI,沒有交易。但我希望你明白這一點。 EF只是實現這些接口的一種方式,只需幾行代碼即可,可以是NHibernate,Marten,RavenDb或其他。

using System; 
using System.Collections.Generic; 
using System.Data.Entity; 
using System.Linq; 
using System.Linq.Expressions; 

namespace SomeStuff 
{ 
    public interface IAggregateRoot 
    { 
     int Id { get; } 
    } 

    public interface IRepository<T> where T : IAggregateRoot 
    { 
     IUnitOfWork UnitOfWork { get; } 
     void Add(T entity); 
     T Load(int id); 
    } 

    public interface IUnitOfWork 
    { 
     void Commit(); 
    } 

    public class EntityFrameworkUnitOfWork : IUnitOfWork 
    { 
     public EntityFrameworkUnitOfWork(DbContext context) 
     { 
      Context = context; 
     } 

     public DbContext Context { get; } 

     public void Commit() => Context.SaveChanges(); 
    } 

    public class EntityFrameworkRepository<T> : IRepository<T> where T : class, IAggregateRoot 
    { 
     private readonly DbSet<T> _set; 
     public IUnitOfWork UnitOfWork { get; } 

     public EntityFrameworkRepository(EntityFrameworkUnitOfWork unitOfWork) 
     { 
      UnitOfWork = unitOfWork; 
      _set = unitOfWork.Context.Set<T>(); 
     } 

     public void Add(T entity) => _set.Add(entity); 

     public T Load(int id) => _set.Find(id); 

     protected IQueryable<T> Query(Expression<Func<T, bool>> filter) => _set.Where(filter); 
    } 

    public class Employee : IAggregateRoot 
    { 
     public int Id { get; set; } 
     public string Name { get; private set; } 
     public int DepartmentId { get; private set; } 

     private Employee(string name) 
     { 
      Name = name; 
     } 

     public static Employee Create(string name) => 
      new Employee(name); 

     public void AssignToDepartment(int departmentId) => DepartmentId = departmentId; 
    } 

    public interface IEmployeeRepository : IRepository<Employee> 
    { 
     IEnumerable<Employee> AllEmployeesForDepartment(int departmentId); 
    } 

    public class EmployeeRepository : EntityFrameworkRepository<Employee>, IEmployeeRepository 
    { 
     public EmployeeRepository(EntityFrameworkUnitOfWork unitOfWork) : base(unitOfWork) 
     { 
     } 

     public IEnumerable<Employee> AllEmployeesForDepartment(int departmentId) => 
      Query(e => e.DepartmentId == departmentId); 
    } 

    public class EmployeeService 
    { 
     private readonly IEmployeeRepository _repository; 

     public EmployeeService(IEmployeeRepository repository) 
     { 
      _repository = repository; 
     } 

     public void RegisterEmployee(string name) 
     { 
      var employee = Employee.Create(name); 
      _repository.Add(employee); 
      _repository.UnitOfWork.Commit(); 
     } 

     public void AssignEmployeeToDepartment(int employeeId, int departmentId) 
     { 
      var employee = _repository.Load(employeeId); 
      if (employee == null) 
       throw new InvalidOperationException("Employee not found"); 

      employee.AssignToDepartment(departmentId); 
      _repository.UnitOfWork.Commit(); 
     } 
    } 
} 
+0

感謝您的回答,但它並沒有真正解決我的問題。 – Chalky

+0

好吧,如果你刪除了對域驅動設計的引用,你可能會以你想要的方式得到適用於你的問題的答案...... –

+0

嗯,好的,謝謝你的時間。 – Chalky

0

我認爲在這種情況下,您根本不需要處理導航。

從持久性帶來一個聚合根來處理創建的項目,只是堅持新的項目。像沃恩弗農said

public void planProductBacklogItem(String aProductId,String aSummary, String aCategory, String aBacklogItemType) { 

//aggregate root with enough info to create the item and check rules 
Product product = productRepository.productOfId(aProductId); 

//Create the item. Assign procutID to new backlogitem. Also check rules (max planed items? wrong itemType for this product?, etc...) 
BacklogItem plannedBacklogItem = product.planBacklogItem(aSummary,aBacklogItemType,aCategory); 

//just persist the new item. As it has a reference to productID everything goes OK. 
backlogItemRepository.add(plannedBacklogItem); 

} 

現在,EF弗農實現這個選擇使用domain object backed by a state object