5

我首先使用 EF 代码来开发我的 3 层 WinForm 应用程序,我使用 disconnected POCOs 作为我的模型实体。我所有的实体都继承自BaseEntity类。

我使用了 disconnected POCO,所以我State在客户端处理实体,并且在ApplyChanges()方法中,我将我的实体图(例如 AnOrder与它的OrderLinesProducts)附加到我的DbContext,然后将每个实体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...

如何在我的 graph( 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. 但是 2 个具有相同 ID 的实体EntityState.Added是不允许的,并且会引发异常。

尝试将行更改为_dbContext.Set<TEntity>().Attach(root);. 这会将图表放入上下文中EntityState.Unchanged。已经在上下文中处于其他状态的实体将其状态设置为未更改。

现在你应该可以去修复状态了。

删除这个答案是因为Attach会导致相同的错误 - 根据评论

参考:

何时使用 DbSet<T>.Add() 与 DbSet<T>.Attach()

为什么实体框架将现有对象重新插入到我的数据库中?

使用不存在的外键

为处理断开连接的实体提供更好的支持

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 回答