2013-01-03 39 views
5

我首先使用EF代碼來開發我的3層WinForm應用程序,我使用斷開的POCO s作爲我的模型實體。我的所有實體都從BaseEntity類繼承而來。查找對象圖中具有相同鍵的實體,以防止「ObjectStateManager中已存在具有相同鍵的對象」錯誤

我用斷開POCO S,所以處理實體的State在客戶端側,並且在ApplyChanges()方法,附上我的實體圖形(例如,與它的OrderOrderLinesProducts)到我DbContext然後SYNCH每個實體的State與它的客戶端State

public class BaseEntity 
{ 

    int _dataBaseId = -1; 

    public virtual int DataBaseId // DataBaseId override in each entity to return it's key 
    { 
     get { return _dataBaseId; } 
    } 

    public States State { get; set; } 

    public enum States 
    { 
     Unchanged, 
     Added, 
     Modified, 
     Deleted 
    } 
} 

所以,當我要保存相關實體的圖形,我用下面的方法:

public static EntityState ConvertState(BaseEntity.States state) 
    { 
     switch (state) 
     { 
      case BaseEntity.States.Added: 
       return EntityState.Added; 
      case BaseEntity.States.Modified: 
       return EntityState.Modified; 
      case BaseEntity.States.Deleted: 
       return EntityState.Deleted; 
      default: 
       return EntityState.Unchanged; 
     } 
    } 

    public void ApplyChanges<TEntity>(TEntity root) where TEntity : BaseEntity 
    { 
     _dbContext.Set<TEntity>().Add(root); 
     foreach (var entry in _dbContext.ChangeTracker 
     .Entries<BaseEntity>()) 
     { 
      BaseEntity stateInfo = entry.Entity; 
      entry.State = ConvertState(stateInfo.State); 
     } 
    } 

但如果我的圖形中包含具有相同鍵2個或多個實體我給這個錯誤:

An object with the same key already exists in the ObjectStateManager... 

我怎麼能檢測與相同的密鑰的實體在我的圖(root),使他們獨特在我的ApplyChanges()方法?

+0

你如何「通過圖表」?爲什麼你的實體實現'INotifyPropertyChanged'? –

+0

根對象是TEntities的圖形,我實現INotifyPropertyChanged可以將對象綁定到winUI。 – Masoud

+0

@Masoud是否具有相同的「ID」值-1?也許你只需要正確地標記添加的項目而不是更新或像這樣簡單的事情。事實上,你有ID衝突不一定是一個EF的關注,這聽起來像你在EF以外做了很多東西。 –

回答

0

我改變了我的BaseEntity

public class BaseEntity 
{ 
    public int Id {get; set;} 
    public States State { get; set; } 
    public bool MustDelete {get; set;} 

    public enum States 
    { 
    Unchanged, 
    Added, 
    Modified, 
    Deleted 
    } 
} 

而且也改變了按照我的BaseDomainService<T>類方法:

public class BaseDomainService<T> where T : class 
{ 
    protected readonly DbContext _dbContext; 

    public BaseDomainService(IUnitOfWork uow) 
    { 
     _dbContext = (DbContext)uow; 
    } 
    ..... 


    public static EntityState ConvertState(BaseEntity.States state) 
    { 
     switch (state) 
     { 
      case BaseEntity.States.Added: 
       return EntityState.Added; 
      case BaseEntity.States.Modified: 
       return EntityState.Modified; 
      case BaseEntity.States.Deleted: 
       return EntityState.Deleted; 
      default: 
       return EntityState.Unchanged; 
     } 
    }  

    public void ApplyChanges<TEntity>(TEntity root) where TEntity : BaseEntity 
    { 
     _dbContext.Set<TEntity>().Add(root); 
     foreach (var entry in _dbContext.ChangeTracker 
     .Entries<BaseEntity>()) 
     { 
      if (FoundAnEntityWithSameKeyInDbContext<TEntity>(entry)) 
       entry.State = EntityState.Detached; 
      else 
      { 
       BaseEntity stateInfo = entry.Entity; 
       if (stateInfo.MustDelete == true) 
        entry.State = EntityState.Detached; 
       else 
        entry.State = ConvertState(stateInfo.State); 
      } 
     } 
    } 

    private bool FoundAnEntityWithSameKeyInDbContext<TEntity>(DbEntityEntry<BaseEntity> entry) where TEntity : BaseEntity 
    { 
     var tmp = _dbContext.ChangeTracker.Entries<BaseEntity>().Count(t => t.Entity.Id == entry.Entity.Id && t.Entity.Id != 0 && t.Entity.GetType() == entry.Entity.GetType()); 
     if (tmp > 1) 
      return true; 
     return false; 
    } 
} 

那麼,問題就迎刃而解了。

3

當您調用_dbContext.Set<TEntity>().Add(root);時,它會告訴上下文,圖中所有實體的狀態爲EntityState.Added。但是不允許有兩個具有相同ID和EntityState.Added的實體,並且拋出異常。

嘗試將行更改爲 _dbContext.Set<TEntity>().Attach(root);。這會將圖形放入 EntityState.Unchanged的上下文中。已經在其他狀態中的上下文中的實體將其狀態設置爲「未更改」。

現在你應該可以去修復狀態。

震撼了這個答案,因爲Attach導致相同的錯誤 - 按評論

參考文獻:

When to use DbSet<T>.Add() vs DbSet<T>.Attach()

Why does Entity Framework Reinsert Existing Objects into My Database?

Making Do with Absent Foreign Keys

Provide better support for working with disconnected entities

DbSet.Attach method

+0

如果我更改_dbContext.Set ().Add(root); to _dbContext.Set ().Attach(root);當控制權轉到這一行時,我得到了同樣的錯誤,「已經存在具有相同密鑰的對象...」。 – Masoud

4

還有就是搜索數據庫並檢查是否與相同主鍵的記錄已經存在,我不知道這是你在找什麼,但代碼如下方式:

public static class ObjectSetExtensions 
{ 
    #region Constants 

    private const BindingFlags KeyPropertyBindingFlags = 
     BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; 

    #endregion 

    #region Public Methods and Operators 

     public static bool RecordExists<TEntity>(
     this ObjectSet<TEntity> set, 
     TEntity entity) where TEntity : class 
    { 
     Contract.Requires(set != null); 
     Contract.Requires(entity != null); 

     var expressionParameter = Expression.Parameter(typeof(TEntity)); 
     var keyProperties = set.GetKeyProperties(); 

     var matchExpression = 
      keyProperties.Select(
       pi => 
       Expression.Equal(
        Expression.Property(expressionParameter, pi.Last()), 
        Expression.Constant(pi.Last().GetValue(entity, null)))) 
       .Aggregate<BinaryExpression, Expression>(
        null, 
        (current, predicate) => (current == null) ? predicate : 
         Expression.AndAlso(current, predicate)); 

     var existing = 
      set.SingleOrDefault(Expression.Lambda<Func<TEntity, bool>>(
      matchExpression, 
      new[] { expressionParameter })); 

     return existing != null; 
    } 

    #endregion 

    #region Methods 

    private static IEnumerable<PropertyPathCollection> GetKeyProperties<TEntity>(this ObjectSet<TEntity> objectSet) 
     where TEntity : class 
    { 
     Contract.Requires(objectSet != null); 

     var entityType = typeof(TEntity); 

     return 
      objectSet.EntitySet.ElementType.KeyMembers.Select(
       c => new PropertyPathCollection(entityType.GetProperty(c.Name, KeyPropertyBindingFlags))); 
    } 

    #endregion 
} 

public sealed class PropertyPathCollection : IEnumerable<PropertyInfo> 
{ 
    // Fields 
    #region Static Fields 

    public static readonly PropertyPathCollection Empty = new PropertyPathCollection(); 

    #endregion 

    #region Fields 

    private readonly List<PropertyInfo> components; 

    #endregion 

    // Methods 
    #region Constructors and Destructors 

    public PropertyPathCollection(IEnumerable<PropertyInfo> components) 
    { 
     this.components = new List<PropertyInfo>(); 
     this.components.AddRange(components); 
    } 

    public PropertyPathCollection(PropertyInfo component) 
    { 
     this.components = new List<PropertyInfo> { component }; 
    } 

    private PropertyPathCollection() 
    { 
     this.components = new List<PropertyInfo>(); 
    } 

    #endregion 

    #region Public Properties 

    public int Count 
    { 
     get 
     { 
      return this.components.Count; 
     } 
    } 

    #endregion 

    #region Public Indexers 

    public PropertyInfo this[int index] 
    { 
     get 
     { 
      return this.components[index]; 
     } 
    } 

    #endregion 

    #region Public Methods and Operators 

    public static bool Equals(PropertyPathCollection other) 
    { 
     if (ReferenceEquals(null, other)) 
     { 
      return false; 
     } 

     return true; 
    } 

    public static bool operator ==(PropertyPathCollection left, PropertyPathCollection right) 
    { 
     return Equals(left, right); 
    } 

    public static bool operator !=(PropertyPathCollection left, PropertyPathCollection right) 
    { 
     return !Equals(left, right); 
    } 

    public override bool Equals(object obj) 
    { 
     if (ReferenceEquals(null, obj)) 
     { 
      return false; 
     } 

     if (ReferenceEquals(this, obj)) 
     { 
      return true; 
     } 

     if (obj.GetType() != typeof(PropertyPathCollection)) 
     { 
      return false; 
     } 

     return Equals((PropertyPathCollection)obj); 
    } 

    public override int GetHashCode() 
    { 
     return this.components.Aggregate(0, (t, n) => (t + n.GetHashCode())); 
    } 

    #endregion 

    #region Explicit Interface Methods 

    IEnumerator<PropertyInfo> IEnumerable<PropertyInfo>.GetEnumerator() 
    { 
     return this.components.GetEnumerator(); 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return this.components.GetEnumerator(); 
    } 

    #endregion 
} 

而且用法是這樣的:

var context = this.DbContext; 
var adapter = context as IObjectContextAdapter; 
var objectContext = adapter.ObjectContext; 

objectContext.CreateObjectSet<TEntity>().RecordExists(instance); 
+0

謝謝,但我正在尋找一種方法來找到實體,而無需再次從數據庫中獲取它們,因爲它們在內存中。 – Masoud

+0

@Masoud,你必須首次檢查DB,但是如果你的實體斷開連接,那麼你不能正確地存儲狀態。 –

+0

爲什麼我必須首次檢查DB? – Masoud

相關問題