2013-01-03 12 views
5

Najpierw używam kodu EF do opracowania mojej 3-warstwowej aplikacji WinForm, użyłem rozłączonego POCO s jako mojego modelowe. Wszystkie moje podmioty odziedziczyły po klasie BaseEntity.Znajdowanie encji z tym samym kluczem w grafie obiektów dla zapobiegania "Obiekt z tym samym kluczem już istnieje w ObjectStateManager" Błąd

użyłem rozłączonych POCO s, więc obsługiwać podmiot na State po stronie klienta, aw ApplyChanges() metody, załączam mój wykres jednostki (np Order z jego OrderLines i Products) do mojego DbContext a następnie zsynchronizować każdy podmiot na State z jego strona klienta 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 
    } 
} 

Więc kiedy chcę zapisać wykres podmiotami powiązanymi, użyłem następujących metod:

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); 
     } 
    } 

Ale jeśli mój wykres zawiera 2 lub więcej podmiotów z tego samego klucza daję ten błąd:

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

jaki sposób można wykryć podmioty z tych samych kluczy w moim wykresie (root) i ich wyjątkowy w moim sposobie ApplyChanges()?

+0

Jak "przekazujesz wykres"? I dlaczego twoje podmioty implementują 'INotifyPropertyChanged'? –

+0

głównym obiektem jest wykres TEntities, implementuję INotifyPropertyChanged, aby mógł wiązać obiekty z winUI. – Masoud

+0

@Masoud Czy ta sama wartość 'ID' -1? Być może wystarczy poprawnie oznaczyć elementy do dodania, w przeciwieństwie do aktualizacji lub coś tak prostego. Fakt, że masz kolizje ID, niekoniecznie jest związany z EF, brzmi to tak, jakbyś robił wiele rzeczy poza EF. –

Odpowiedz

0

zmieniłem BaseEntity do

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 
    } 
} 

A także zmienione następujące metody w moim BaseDomainService<T> klasy:

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; 
    } 
} 

Tak, problem rozwiązany.

3

Po wywołaniu _dbContext.Set<TEntity>().Add(root); informuje kontekst, że wszystkie elementy na wykresie mają stan EntityState.Added. Ale 2 podmioty o tym samym identyfikatorze i EntityState.Added są niedozwolone i zostanie zgłoszony wyjątek.

Spróbuj zmienić linię na _dbContext.Set<TEntity>().Attach(root);. To spowoduje, że wykres wejdzie w kontekst z EntityState.Unchanged. Podmioty, które są już w kontekście w innym stanie, będą miały ustawiony stan Bez zmian.

Teraz powinieneś być w stanie naprawić państwa.

Struck tę odpowiedź, bo Attach powoduje ten sam błąd - jak na komentarz

Referencje:

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

Jeśli zmienię _dbContext.Set () .Add (root); do _dbContext.Set () .Attach (root); kiedy kontrola przechodzi do tej linii, pojawia się ten sam błąd: "Obiekt z tym samym kluczem już istnieje ...". – Masoud

4

Jest na to sposób, aby przeszukać bazę danych i sprawdzić, czy zapis z tego samego klucza podstawowego już istnieje, nie wiem, czy to, czego szukasz, ale kod jest poniżej:

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 
} 

I wykorzystanie jest tak:

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

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

Dziękuję, ale szukam sposobu na znalezienie obiektów bez pobierania ich z DB ponownie, ponieważ są one w pamięci. – Masoud

+0

@Masoud, musisz sprawdzić DB nawet po raz pierwszy, jednak jeśli twój obiekt jest odłączony, to nie zapisujesz stanu poprawnie. –

+0

Dlaczego muszę sprawdzić DB po raz pierwszy? – Masoud