实体框架告诉我一个对象是附加的,当它不是 - 为什么?

时间:2011-10-25 16:13:25

标签: asp.net entity-framework objectcontext

我想在数据库中更新一个对象。我是EF的新手,但已经做了很多阅读。显然我的方法是错的,但我不明白为什么。 FYI整个引用的Context是一个ObjectContext,它在此代码开始时被新实例化,并在之后立即处理。这是我的Update方法 - View是我想在数据库中更新的对象,它有4个ICollection属性,我希望将这些属性保存到数据库中:

    public void Update(View view)
    {
        var original = Read(view.Username, view.ViewId);

        original.ViewName = view.ViewName;

        ProcessChanges<CostCentre, short>(Context.CostCentres, original.CostCentres, view.CostCentres, "iFinanceEntities.CostCentres", "CostCentreId");

        ProcessChanges<LedgerGroup, byte>(Context.LedgerGroups, original.LedgerGroups, view.LedgerGroups, "iFinanceEntities.LedgerGroups", "LedgerGroupId");

        ProcessChanges<Division, byte>(Context.Divisions, original.Divisions, view.Divisions, "iFinanceEntities.Divisions", "DivisionId");

        ProcessChanges<AnalysisCode, short>(Context.AnalysisCodes, original.AnalysisCodes, view.AnalysisCodes, "iFinanceEntities.AnalysisCodes", "AnalysisCodeId");

        int test = Context.SaveChanges();
    }

首先,我从数据库中获取原始数据,因为我想将其集合与新的集合进行比较。这应该确保添加和删除正确的子对象。我使用这个ProcessChanges方法依次比较每个集合:

    private void ProcessChanges<TEntity, TKey>(ObjectSet<TEntity> contextObjects, ICollection<TEntity> originalCollection, ICollection<TEntity> changedCollection, string entitySetName, string pkColumnName) 
        where TEntity : class, ILookupEntity<TKey>
    {
        List<TKey> toAdd = changedCollection
            .Select(c => c.LookupKey)
            .Except(originalCollection.Select(o => o.LookupKey))
            .ToList();

        List<TKey> toRemove = originalCollection
            .Select(o => o.LookupKey)
            .Except(changedCollection.Select(c => c.LookupKey))
            .ToList();

        toAdd.ForEach(a =>
        {
            var o = changedCollection.Single(c => c.LookupKey.Equals(a));

            AttachToOrGet<TEntity, TKey>(entitySetName, pkColumnName, ref o);
            originalCollection.Add(o);
        });

        toRemove.ForEach(r =>
        {
            var o = originalCollection.Single(c => c.LookupKey.Equals(r));
            originalCollection.Remove(o);
        });
    }

这会将新集合与旧集合进行比较,并确定要添加的对象和要删除的对象。请注意,集合都包含实现ILookupEntity的对象。

我的问题出现在我调用AttachToOrGet的行上。我从stackoverflow上的其他地方获得的这个方法。我正在使用它,因为我经常收到一条消息,说“附加一个新的子对象时,ObjectStateManager中已经存在一个具有相同键的对象”。当我发布下面这个方法的代码时,希望你能理解我对此的困惑:

    public void AttachToOrGet<TEntity, TKey>(string entitySetName, string pkColumnName, ref TEntity entity)
        where TEntity : class, ILookupEntity<TKey>
    {
        ObjectStateEntry entry;
        // Track whether we need to perform an attach
        bool attach = false;
        if (Context.ObjectStateManager.TryGetObjectStateEntry(new EntityKey(entitySetName, pkColumnName, entity.LookupKey), out entry))
        //if (Context.ObjectStateManager.TryGetObjectStateEntry(Context.CreateEntityKey(entitySetName, entity), out entry))
        {
            // Re-attach if necessary
            attach = entry.State == EntityState.Detached;
            // Get the discovered entity to the ref
            entity = (TEntity)entry.Entity;
        }
        else
        {
            // Attach for the first time
            attach = true;
        }
        if (attach)
            Context.AttachTo(entitySetName, entity);
    }

基本上这是说如果实体尚未附加,则附加它。 但是我的代码在Context.ObjectStateManager.TryGetObjectStateEntry行返回false,但在最后一行抛出异常,并在“ObjectStateManager中已存在具有相同键的对象”。对我而言是矛盾的。

就我而言,我正在努力实现一些非常简单的事情。编写存储过程需要20分钟的东西。简单的数据库更新。坦率地说,我不关心什么是附加的,什么不是,因为我不希望跟踪更改或创建代理或延迟加载或做任何其他EF提供给我。我只想采用一个非常简单的对象,并使用服务器之间的最小行程更新数据库。这怎么这么复杂?请有人帮帮我 - 我已经花了一整天的时间!

更新

这是我的ILookupEntity类:

public interface ILookupEntity<TKey>
{
    TKey LookupKey { get; }
    string DisplayText { get; }
}

以下是它在CostCentre中的实现方式:

public partial class CostCentre : IFinancialCode, ILookupEntity<short>
{
    #region IFinancialCode Members

    public short ID { get { return CostCentreId; } }

    public string DisplayText { get { return string.Format("{0} - {1}", Code, Description); } }

    #endregion

    #region ILookupEntity Members

    public short LookupKey
    {
        get { return ID; }
    }

    #endregion ILookupEntity Members
}

1 个答案:

答案 0 :(得分:1)

嗯,我已经完成了这个并找到了解决方案,但我不能说我理解它。当我在@Slauma发表评论后进行检查时,关键因素出现了。我想检查我是否使用了正确的实体集名称等,所以我在AttachToOrGet方法的顶部附近包含以下行:

        var key = new EntityKey(entitySetName, pkColumnName, entity.LookupKey);

        object temp;
        if (!Context.TryGetObjectByKey(key, out temp))
            throw new Exception(string.Format("No entity was found in {0} with key {1}", entitySetName, entity.LookupKey));

奇怪的是,这一点解决了这个问题。出于某种原因,一旦我调用TryGetObjectByKey,ObjectStateManager.TryGetObjectStateEntry调用实际上开始定位附加的实体。神奇。如果有人能解释清楚,我会喜欢它。

顺便说一句,我还需要包含以下代码,但这只是因为在我的情况下,建模实体位于与上下文本身不同的程序集中。

        Assembly assembly = typeof(CostCentre).Assembly;
        Context.MetadataWorkspace.LoadFromAssembly(assembly);