5

最初にEFコードを使用して3層POCOのWinFormアプリケーションを開発し、モデルエンティティとして切断されたを使用しました。BaseEntityクラスから継承されたすべてのエンティティ。

切断されたを使用したので、クライアント側でPOCOエンティティを処理し、メソッドでは、エンティティグラフ(たとえば、Anとそれと)をに添付してから、各エンティティをそのクライアント側と同期します。StateApplyChanges()OrderOrderLinesProductsDbContextStateState

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()

4

3 に答える 3

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);
于 2013-12-12T18:41:26.053 に答える
3

呼び出す_dbContext.Set<TEntity>().Add(root);と、グラフ内のすべてのエンティティの状態が。であることがコンテキストに通知されますEntityState.Added。ただし、同じIDを持つ2つのエンティティEntityState.Addedは許可されず、例外がスローされます。

行をに変更してみてください_dbContext.Set<TEntity>().Attach(root);。これにより、グラフが。のコンテキストに配置されますEntityState.Unchanged。他の状態のコンテキストにすでに存在するエンティティの状態は、未変更に設定されます。

これで、状態を修正できるはずです。

Attachコメントごとに同じエラーが発生するため、この回答を削除しました

参照:

DbSet <T> .Add()とDbSet <T> .Attach()を使用する場合

Entity Frameworkが既存のオブジェクトをデータベースに再挿入するのはなぜですか?

存在しない外部キーを処理する

切断されたエンティティの操作をより適切にサポートする

DbSet.Attachメソッド

于 2013-12-11T09:53:40.963 に答える
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;
    }
}

それで、問題は解決しました。

于 2014-02-02T04:36:21.230 に答える