11

我正在使用存儲庫模式來提供對我的集合的訪問和保存。更新整個集合的通用存儲庫

問題是更新由實體關係組成的聚合體。

例如,取OrderOrderItem的關係。聚合根是Order,它管理自己的OrderItem集合。因此,OrderRepository將負責更新整個聚合(不存在OrderItemRepository)。

數據持久性使用實體框架6.

更新儲存庫方法處理(DbContext.SaveChanges()別處發生):

public void Update(TDataEntity item) 
{ 
    var entry = context.Entry<TDataEntity>(item); 

    if (entry.State == EntityState.Detached) 
    { 
     var set = context.Set<TDataEntity>(); 

     TDataEntity attachedEntity = set.Local.SingleOrDefault(e => e.Id.Equals(item.Id)); 

     if (attachedEntity != null) 
     { 
      // If the identity is already attached, rather set the state values 
      var attachedEntry = context.Entry(attachedEntity); 
      attachedEntry.CurrentValues.SetValues(item); 
     } 
     else 
     { 
      entry.State = EntityState.Modified; 
     } 
    } 
} 

以我上面的例子中,只有Order實體將被更新,而不是它的相關OrderItem集合。

我需要附加所有OrderItem實體嗎?我怎麼能這樣做一般?

+0

還有就是要增加對更改與圖表跟蹤更好的支持的建議 - HTTPS: //entityframework.codeplex.com/workitem/864,並有一個鏈接到graphdiff,可能會幫助 – Colin

回答

15

Julie Lerman給出了一個很好的方法來處理如何更新她的書Programming Entity Framework: DbContext中的整個聚合。

正如她寫道:

當斷開連接的實體圖到達在服務器端,服務器 不知道實體的狀態。您需要提供一種 的方式來發現狀態,以便可以使上下文知道每個實體的狀態。

該技術被稱爲painting the state

主要有兩種方法可以做到這一點:

使用模型的知識和設置狀態爲每個實體
  • 建立一個通用的方法來跟蹤狀態
    • 迭代通過該圖

      第二個選項非常好,包括創建一個模型中的每個實體都將實現的接口。朱莉使用的IObjectWithState接口,講述了實體的當前狀態:

      public interface IObjectWithState 
      { 
          State State { get; set; } 
      } 
      public enum State 
      { 
          Added, 
          Unchanged, 
          Modified, 
          Deleted 
      } 
      

      你要做的第一件事是自動設置爲Unchanged通過在Context掛鉤的事件添加一個構造函數從數據庫中檢索到的每一個實體類:

      public YourContext() 
      { 
      ((IObjectContextAdapter)this).ObjectContext 
          .ObjectMaterialized += (sender, args) => 
      { 
          var entity = args.Entity as IObjectWithState; 
          if (entity != null) 
          { 
          entity.State = State.Unchanged; 
          } 
      }; 
      } 
      

      然後,修改和OrderOrderItem實現IObjectWithState接口並調用ApplyChanges方法接受根實體作爲參數:

      private static void ApplyChanges<TEntity>(TEntity root) 
      where TEntity : class, IObjectWithState 
      { 
      using (var context = new YourContext()) 
      { 
          context.Set<TEntity>().Add(root); 
      
          CheckForEntitiesWithoutStateInterface(context); 
      
          foreach (var entry in context.ChangeTracker 
          .Entries<IObjectWithState>()) 
          { 
          IObjectWithState stateInfo = entry.Entity; 
          entry.State = ConvertState(stateInfo.State); 
          } 
          context.SaveChanges(); 
      } 
      } 
      
      private static void CheckForEntitiesWithoutStateInterface(YourContext context) 
      { 
      var entitiesWithoutState = 
      from e in context.ChangeTracker.Entries() 
      where !(e.Entity is IObjectWithState) 
      select e; 
      
      if (entitiesWithoutState.Any()) 
      { 
          throw new NotSupportedException("All entities must implement IObjectWithState"); 
      } 
      } 
      

      最後但並非最不重要的,不要忘了打電話ApplyChanges前將圖形entites的右側狀態;-)(你甚至可以在同一張圖中混合ModifiedDeleted州)

      朱莉建議在他的書中更進一步:

      你可能會發現自己希望更加精細的方式 修改後的屬性被跟蹤。而不是將整個實體 標記爲已修改,您可能只想將實際上已更改爲 的屬性標記爲已修改。 除了將實體標記爲修改之外,客戶端還負責記錄哪些屬性已被修改。 要做到這一點的一種方法是將修改後的屬性名稱列表添加到 狀態跟蹤界面。

      但正如我的答案已經太長了,就去看了她book如果您想了解更多;-)

    +1

    這幾乎就是我現在正在做的一個項目,並且它工作得很好。我有一個附加到DbContext的擴展方法,我在SaveChanges中調用它檢查具有該接口並設置EntityState的所有實體。奇蹟般有效。 – VeteranCoder

    +0

    @MaxS我有這種方法的問題。如果我創建了一個對象然後嘗試並使用它,因爲物化事件不會在該對象上觸發,所以下次ApplyChanges被另一個對象添加時,會出現重複鍵異常。也許我的做法是有缺陷的,因爲我一直堅持一兩件事情,而不是一個大交易。第一個操作與交易分開。所以我不得不做一個重新加載和數據庫命中來解決這個問題。不理想。 – onefootswill

    +0

    @onefootswill我不確定我是否正確理解了你描述的場景。你能一步一步告訴我你想做什麼嗎?您是否嘗試將新添加的實體掛鉤到現有的實體?無論如何,看看[這裏](http://msdn.microsoft.com/en-us/data/jj592676.aspx)來看EF如何處理實體狀態。 – MaxSC

    6

    我自以爲是(DDD專用)的答案是:

    1. 在數據層切斷EF實體。

    2. 確保您的數據層僅返回域實體(而非EF實體)。

    3. 忘記EF的延遲加載和IQueryable()善良(閱讀:噩夢)。

    4. 考慮使用文檔數據庫。

    5. 請勿使用通用存儲庫。

    我發現你在EF問什麼是先刪除或禁用數據庫中的所有訂單項是順序的孩子,然後添加或激活的所有訂單項的唯一途徑數據庫,現在是您最近更新的訂單的一部分。

    +0

    我實際上是返回域實體(在我上面的示例中未顯示)。我只是刪除了映射和工廠邏輯來簡化問題。無論如何,我仍然有更新EF庫實現中的數據實體圖的問題。 – davenewza

    +0

    我曾經有一個約5層深的對象圖類似的問題,從來沒有找到一個很好/乾淨的答案。你會認爲EF會有更新複雜對象圖的方法。 –

    +0

    5.不要使用通用儲存庫(1.和2.已經暗示)。 –

    0

    所以你有更新的方法做得很好你總根,看看這個領域模型:

    public class ProductCategory : EntityBase<Guid> 
        { 
         public virtual string Name { get; set; } 
        } 
    
        public class Product : EntityBase<Guid>, IAggregateRoot 
        { 
        private readonly IList<ProductCategory> _productCategories = new List<ProductCategory>(); 
    
        public void AddProductCategory(ProductCategory productCategory) 
         { 
          _productCategories.Add(productCategory); 
         } 
        } 
    

    這只是其中有一個產品類別的產品,我剛剛創建的ProductRepository作爲我aggregateroot是產品(不是產品類別),但我想,當我創建添加的產品類別或更新產品在服務層:

    public CreateProductResponse CreateProduct(CreateProductRequest request) 
         { 
          var response = new CreateProductResponse(); 
         try 
          { 
           var productModel = request.ProductViewModel.ConvertToProductModel(); 
           Product product=new Product(); 
           product.AddProductCategory(productModel.ProductCategory); 
           _productRepository.Add(productModel); 
           _unitOfWork.Commit(); 
          } 
          catch (Exception exception) 
          { 
           response.Success = false; 
          } 
          return response; 
         } 
    

    我只是想告訴你如何爲域中的實體創建域方法並將其用於服務或應用層。你可以看到下面的代碼通過productRepository增加了產品分類類別的數據庫:

    product.AddProductCategory(productModel.ProductCategory); 
    

    現在更新同一實體,你可以要求ProductRepository並獲取實體並對其進行更改。 注意,檢索實體和值對象,並分別彙總,你可以編寫查詢服務或readOnlyRepository:

    public class BlogTagReadOnlyRepository : ReadOnlyRepository<BlogTag, string>, IBlogTagReadOnlyRepository 
        { 
         public IEnumerable<BlogTag> GetAllBlogTagsQuery(string tagName) 
         { 
          throw new NotImplementedException(); 
         } 
        } 
    

    希望它有助於