2013-01-20 25 views
2

我正在嘗試編寫一個將被其他Repositories使用的GenericEFRepository。我有一個Save方法如下。在Entity Framework中保存實體的通用方法

public virtual void Save(T entity) // where T : class, IEntity, new() And IEntity enforces long Id { get; set; } 
{ 
    var entry = _dbContext.Entry(entity); 

    if (entry.State != EntityState.Detached) 
     return; // context already knows about entity, don't do anything 

    if (entity.Id < 1) 
    { 
     _dbSet.Add(entity); 
     return; 
    } 

    var attachedEntity = _dbSet.Local.SingleOrDefault(e => e.Id == entity.Id); 
    if (attachedEntity != null) 
     _dbContext.Entry(attachedEntity).State = EntityState.Detached; 
    entry.State = EntityState.Modified; 
} 

您可以在下面的代碼

using (var uow = ObjectFactory.GetInstance<IUnitOfWork>()) // uow is implemented like EFUnitOfWork which gives the DbContext instance to repositories in GetRepository 
{ 
    var userRepo = uow.GetRepository<IUserRepository>(); 

    var user = userRepo.Get(1); 
    user.Name += " Updated"; 

    userRepo.Save(user); 
    uow.Save(); // OK only the Name of User is Updated 
} 

using (var uow = ObjectFactory.GetInstance<IUnitOfWork>()) 
{ 
    var userRepo = uow.GetRepository<IUserRepository>(); 

    var user = new User 
    { 
     Id = 1, 
     Name = "Brand New Name" 
    }; 

    userRepo.Save(user); 
    uow.Save(); 

    // NOT OK 
    // All fields (Name, Surname, BirthDate etc.) in User are updated 
    // which causes unassigned fields to be cleared on db 
} 

我能想到的唯一的辦法就是通過像userRepo.CreateEntity(id: 1)和庫庫創建實體將返回其連接到的DbContext實體的意見發現問題。但這似乎很容易出錯,但任何開發人員都可能使用new關鍵字創建實體。

你對這個問題有什麼解決方案?

注意:我已經知道使用GenericRepository和IEntity接口的優缺點。因此,「不要使用GenericRepository,不要使用IEntity,不要在每個實體中放置很長的ID,不要做你正在嘗試做的事情」的評論無濟於事。

回答

6

是的,它很容易出錯,但僅僅是與EF和資料庫的問題。您必須先創建實體並將其附加,然後才能設置要更新的任何數據(您的案例中爲Name),或者您必須爲每個要保留的屬性設置修改後的狀態,而不是整個實體(如同您可以想象開發人員可以再次忘記去做)。

第一個解決方案導致了特殊的方法,在你的倉庫正是這樣做的:

public T Create(long id) { 
    T entity = _dbContext.Set<T>().Create(); 
    entity.Id = id; 
    _dbContext.Set<T>().Attach(entity); 
    return entity; 
} 

第二種解決方案需要像

public void Save(T entity, params Expression<Func<T, TProperty>>[] properties) { 

    ... 

    _dbContext.Set<T>().Attach(entity); 
    if (properties.Length > 0) { 
     foreach (var propertyAccessor in properties) { 
      _dbContext.Entry(entity).Property(propertyAccessor).IsModified = true; 
     } 
    } else { 
     _dbContext.Entry(entity).State = EntityState.Modified; 
    } 
} 

,你會調用它:

userRepository(user, u => u.Name); 
+0

第一種方法存在一個小問題,將值類型屬性設置爲其默認值不會作爲更改獲取。例如,如果您保留一個登錄嘗試失敗的計數器,並且您想將其重置爲0,則它​​將不起作用。 – Stijn

+0

在附加之前,您可以將其設置爲非0(例如1),然後在附加後將其更改爲零。 –

1

這是這種方法的一個基本問題,因爲您希望知識庫神奇地知道您更改了哪些字段以及哪些字段沒有更改。使用null作爲「不變」的信號在null是有效值的情況下不起作用。

您需要告訴存儲庫您要寫入哪些字段,例如發送一個帶有字段名稱的string[]。或每個領域的一個布爾。我不認爲這是一個好的解決方案。

也許你可以反轉這樣的控制流程:

var entity = repo.Get(1); 
entity.Name += "x"; 
repo.SaveChanges(); 

這將使更改跟​​蹤工作。這更接近於如何使用EF 想要

備選:

var entity = repo.Get(1); 
entity.Name += "x"; 
repo.Save(entity); 
1

雖然其他兩個答案提供了很好的見解,你也許可以避免這個問題我認爲它值得指出幾件事情。

  • 什麼你正在嘗試做的(即代理實體更新)非常EF-centeric和IMO實際上不會使EF上下文之外的意義,因此它沒有任何意義,一個通用存儲庫可以預期以這種方式行事。
  • 實際上你甚至還沒有對EF進行完全正確的流動,如果你附加了一個已經設置了幾個字段的對象,EF將會把你告訴它的當前數據庫狀態重寫一遍,除非你修改了一個值或者修改了一個值旗。要做你沒有選擇的東西你通常會附加一個對象沒有的名字,然後在附加ID對象後設置名稱
  • 你的方法通常用於性能方面的原因,我建議通過抽象在現有框架的頂部,您幾乎總會遭受邏輯性能下降。如果這是一個大問題,也許你不應該使用存儲庫?爲了滿足性能問題,您添加到存儲庫的信息越多,越複雜,越嚴格,越難實現。

之所以這麼說,我不認爲你可以處理在一個通用的情況下這種特殊情況下。

這是一種可能的方式,你可以做到這一點

public void UpdateProperty(Expression<Func<T,bool>> selector, FunctionToSetAProperty setter/*not quite sure of the correct syntax off the top of my head*/) 
{ 
    // look in local graph for T and see if you have an already attached version 
    // if not attach it with your selector value set 
    // set the property of the setter 
} 

希望這有一定道理,我不會在我的dev的盒子的ATM,所以我不能真正做工作示例。

我認爲這是一個更好的通用知識庫的方法,因爲它允許您以多種不同的方式實現相同的行爲,abovc可能適用於EF,但是如果您有一個內存知識庫,則會有不同的方法例)。這種方法允許您實現滿足意圖的不同實現,而不是像EF那樣限制存儲庫。

相關問題