2013-01-03 6 views
5

Sto usando il codice EF prima per sviluppare la mia applicazione WinForm a 3 strati, ho usato disconnesso POCO s come entità modello. Tutte le mie entità ereditate dalla classe BaseEntity.Trovare le entità con la stessa chiave in un oggetto grafico per impedire "Un oggetto con la stessa chiave esiste già nell'ObjectStateManager" Errore

ho usato scollegati POCO s, così ho gestire entità di State sul lato client, e in ApplyChanges() metodo, allegare il mio grafico entità (ad esempio un Order con la sua OrderLines e Products) al mio DbContext e poi sincronizzare di State con la sua ogni entità lato client 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 
    } 
} 

Così, quando voglio salvare un grafico degli enti collegati, ho usato metodi seguenti:

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

Ma se il mio grafico contiene 2 o più entità con la stessa chiave do questo errore:

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

Come posso rilevare le entità con le stesse chiavi nel mio grafico (root) e li rendono unici nel mio metodo ApplyChanges()?

+0

Come si "passa un grafico"? E perché le tue entità implementano 'INotifyPropertyChanged'? –

+0

l'oggetto radice è il grafico di TEntities, io implemento INotifyPropertyChanged per poter legare oggetti a winUI. – Masoud

+0

@Masoud È lo stesso valore di 'ID' -1? Forse hai solo bisogno di segnare correttamente gli elementi da aggiungere invece di aggiornare o qualcosa di così semplice. Il fatto che tu abbia una collisione di ID non è necessariamente una preoccupazione di EF, sembra che tu stia facendo un sacco di cose al di fuori di EF. –

risposta

0

ho cambiato il mio BaseEntity a

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

E anche cambiato seguendo i metodi della mia classe 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; 
    } 
} 

Quindi, il problema risolto.

3

Quando si chiama _dbContext.Set<TEntity>().Add(root); indica al contesto che tutte le entità nel grafico hanno uno stato di EntityState.Added. Ma 2 entità con lo stesso ID e EntityState.Added non sono consentite e viene generata un'eccezione.

Provare a cambiare la linea su _dbContext.Set<TEntity>().Attach(root);. Questo metterà il grafico nel contesto con EntityState.Unchanged. Le entità che sono già nel contesto in qualche altro stato avranno il loro stato impostato su Invariato.

Ora dovresti essere in grado di risolvere gli stati.

Struck questa risposta fuori perché Attach provoca lo stesso errore - come da commento

Riferimenti:

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

Se cambio _dbContext.Set () .Add (root); a _dbContext.Set () .Attach (root); quando il controllo va su questa linea, ottengo lo stesso errore, "Esiste già un oggetto con la stessa chiave ...". – Masoud

4

C'è un modo per cercare nel database e verificare se un record con la stessa chiave primaria esiste già, non so se è quello che state cercando, ma il codice è qui sotto:

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 
} 

E l'utilizzo è simile a questo:

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

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

Grazie, ma sto cercando un modo per trovare le entità senza recuperarle dal DB, perché sono in memoria. – Masoud

+0

@Masoud, devi controllare DB anche per la prima volta, tuttavia se la tua entità è disconnessa allora non stai memorizzando lo stato correttamente. –

+0

Perché devo controllare DB per la prima volta? – Masoud

Problemi correlati