实体框架6 - 使用我的getHashCode()

Buz*_*zby 16 c# wpf entity-framework n-tier-architecture dbcontext

有一定的背景可以通过这个 - 请忍受我!

我们有一个使用EF的n层WPF应用程序 - 我们通过dbContext将数据从数据库加载到POCO类中.销毁dbContext,然后用户就可以编辑数据.我们使用Julie Lerman在她的书"Programming Entity Framework:DBContext"中建议的"状态绘画",这样当我们将根实体添加到新的dbContext进行保存时,我们可以设置是否添加,修改或保持每个子实体等等. .

当我们第一次这样做时(早在2012年11月!)我们遇到的问题是,如果我们添加到dbContext的根实体具有相同子实体的多个实例(即,链接到用户的"任务"记录, "状态历史记录"也链接到同一用户)进程将失败,因为即使子实体是相同的(来自相同的数据库行),它们也被赋予不同的哈希码,因此EF将它们识别为不同的对象.

我们修复了这个问题(早在2012年12月!),通过在我们的实体上覆盖GetHashCode,如果实体来自数据库,则返回数据库ID,如果实体尚未保存,则返回唯一的负数.现在,当我们将根实体添加到dbContext时,它足够聪明,可以实现多次添加相同的子实体并正确处理它.自2012年12月以来我们一直运作良好,直到我们上周升级到EF6 ...

EF6的一个新"功能"是它现在使用它自己的Equals和GetHashCode方法来执行更改跟踪任务,忽略任何自定义覆盖.请参阅:http://msdn.microsoft.com/en-us/magazine/dn532202.aspx(搜索"减少对编码风格的干扰").如果您希望EF管理变更跟踪,那么这很好,但在断开连接的n层应用程序中,我们不希望这样做,实际上这会破坏我们的代码,这些代码已经运行了一年多.

希望这是有道理的.

现在 - 问题 - 有没有人知道我们可以告诉EF6如何在EF5中使用我们的 GetHashCode和Equals方法,或者是否有人有更好的方法来处理将根实体添加到具有重复子实体的dbContext在它,以便EF6会满意吗?

谢谢你的帮助.对不起,很长的帖子.

更新 已经在EF代码中探讨了它看起来像是通过获取实体的哈希码来设置InternalEntityEntry(dbEntityEntry)的哈希码,但现在在EF6中使用RuntimeHelpers.GetHashCode(_entity)检索,这意味着我们被覆盖实体上的哈希码被忽略.所以我想让EF6使用我们的哈希码是不可能的,所以我可能需要专注于如何将实体添加到可能具有重复子实体的上下文而不会扰乱EF.有什么建议?

更新2 最烦人的事情是,这种功能上的变化被报告为一件好事,而不是,正如我所看到的那样,这是一个突破性的变化!当然,如果你有断开连接的实体,并且你已经为它们加载.AsNoTracking()以获得性能(并且因为我们知道我们将断开它们,所以为什么还要跟踪它们),那么dbContext没有理由覆盖我们的getHashcode方法!

更新3 感谢所有的意见和建议 - 非常感谢!经过一些实验,它似乎与.AsNoTracking()有关.如果使用.AsNoTracking()加载数据,则重复的子实体是内存中的单独对象(具有不同的哈希码),因此存在状态绘制问题并在以后保存它们.我们之前通过覆盖哈希码修复了这个问题,因此当实体被添加回保存上下文时,重复的实体被识别为同一个对象,并且只添加一次,但我们不能再使用EF6执行此操作.所以现在我需要进一步研究为什么我们首先使用.AsNoTracking().另外一个想法是,EF6的更改跟踪器应该只对其主动跟踪的条目使用自己的哈希码生成方法 - 如果实体已经加载了.AsNoTracking(),它可能应该使用来自底层实体的哈希码吗?

更新4 现在我们已经确定我们不能继续在EF6中使用我们的方法(重写的哈希码和.AsNoTracking),我们应该如何管理对断开连接的实体的更新?我用blogposts/comments/authors创建了这个简单的例子: 图表和数据

在此示例中,我想打开blogpost 1,更改内容和作者,然后再次保存.我已经尝试了3种方法与EF6,我无法让它工作:

BlogPost blogpost;

using (TestEntities te = new TestEntities())
{
    te.Configuration.ProxyCreationEnabled = false;
    te.Configuration.LazyLoadingEnabled = false;

    //retrieve blog post 1, with all comments and authors
    //(so we can display the entire record on the UI while we are disconnected)
    blogpost = te.BlogPosts
        .Include(i => i.Comments.Select(j => j.Author)) 
        .SingleOrDefault(i => i.ID == 1);
}

//change the content
blogpost.Content = "New content " + DateTime.Now.ToString("HH:mm:ss");

//also want to change the author from Fred (2) to John (1)

//attempt 1 - try changing ID? - doesn't work (change is ignored)
//blogpost.AuthorID = 1;

//attempt 2 - try loading the author from the database? - doesn't work (Multiplicity constraint violated error on Author)
//using (TestEntities te = new TestEntities())
//{
//    te.Configuration.ProxyCreationEnabled = false;
//    te.Configuration.LazyLoadingEnabled = false;
//    blogpost.AuthorID = 1;
//    blogpost.Author = te.Authors.SingleOrDefault(i => i.ID == 1);
//}

//attempt 3 - try selecting the author already linked to the blogpost comment? - doesn't work (key values conflict during state painting)
//blogpost.Author = blogpost.Comments.First(i => i.AuthorID == 1).Author;
//blogpost.AuthorID = 1;


//attempt to save
using (TestEntities te = new TestEntities())
{
    te.Configuration.ProxyCreationEnabled = false;
    te.Configuration.LazyLoadingEnabled = false;
    te.Set<BlogPost>().Add(blogpost); // <-- (2) multiplicity error thrown here

    //paint the state ("unchanged" for everything except the blogpost which should be "modified")
    foreach (var entry in te.ChangeTracker.Entries())
    {
        if (entry.Entity is BlogPost)
            entry.State = EntityState.Modified;
        else
            entry.State = EntityState.Unchanged;  // <-- (3) key conflict error thrown here
    }

    //finished state painting, save changes
    te.SaveChanges();

}
Run Code Online (Sandbox Code Playgroud)

如果您在EF5中使用此代码,请使用我们现有的方法将.AsNoTracking()添加到原始查询中.

        blogpost = te.BlogPosts
        .AsNoTracking()
        .Include(i => i.Comments.Select(j => j.Author)) 
        .SingleOrDefault(i => i.ID == 1);
Run Code Online (Sandbox Code Playgroud)

..并覆盖实体上的GetHashCode和Equals :(例如,在BlogPost实体中)..

    public override int GetHashCode()
    {
        return this.ID;
    }

    public override bool Equals(object obj)
    {
        BlogPost tmp = obj as BlogPost;
        if (tmp == null) return false;
        return this.GetHashCode() == tmp.GetHashCode();
    }
Run Code Online (Sandbox Code Playgroud)

..代码中的所有三种方法现在都可以正常工作.

请问您能告诉我如何在EF6中实现这一目标吗?谢谢

Art*_*ers 3

让您的应用程序在 EF5 中以这种方式运行,这既有趣又令人惊讶。EF 始终只需要任何实体的单个实例。如果添加对象图并且 EF 错误地假设它已经在跟踪某个对象,而实际上它正在跟踪不同的实例,则 EF 跟踪的内部状态将不一致。例如,该图仅使用 .NET 引用和集合,因此该图仍将具有多个实例,但 EF 将仅跟踪一个实例。这意味着可能无法正确检测到实体属性的更改,并且实例之间的修复也可能导致意外行为。了解您的代码是否以某种方式解决了这些问题,或者您的应用程序是否碰巧没有遇到这些问题,因此无效的状态跟踪并不重要,这将是很有趣的为您的应用程序。

\n\n

我们对 EF6 所做的更改使得应用程序不太可能将 EF 状态跟踪置于无效状态,从而导致意外行为。如果您有一个巧妙的模式来确保我们使用 EF6 破坏的跟踪状态有效,那么如果您可以在http://entityframework.codeplex.com/上提交带有完整重现的错误,那就太好了

\n