【问题标题】:Problems trying to attach a new EF4 entity to ObjectContext while its entity collection entities are already attached尝试将新的 EF4 实体附加到 ObjectContext 而其实体集合实体已附加时出现问题
【发布时间】:2011-09-05 21:56:00
【问题描述】:

解释起来有点复杂,请多多包涵。

我有一个 ASP.NET MVC 2 项目正在慢慢杀死我,我正在尝试获取表单数据并将其转换为实体以创建或更新,具体取决于情况。最相关的部分(伪代码):

Entity Game
    Scalar properties
    EntityCollection<Platform> Platforms

基本的工作流程是:

表单数据 -> 模型绑定到 DTO -> 使用 AutoMapper 将 DTO 映射到 EF4 实体。

这一切都很好,但有一个例外 - 我需要使用 DTO 中包含的原始整数索引数据创建或更新游戏实体的平台 EntityCollection。所以,这是我一直在尝试的,但不起作用:

public AdminController(IArticleRepository articleRepository, IGameRepository gameRepository, INewsRepository newsRepository)
{
    _articleRepository = articleRepository;
    _gameRepository    = gameRepository;
    _newsRepository    = newsRepository;

    Mapper.CreateMap<AdminGameEditModel, Game>()
        .BeforeMap((s, d) =>
        {
            if (d.Platforms.Count > 0)
            {
                Platform[] existing = d.Platforms.ToArray();

                foreach (var plat in existing)
                {
                    d.Platforms.Remove(plat);
                }
            }

            foreach (var platId in s.PlatformIDs)
            {
                var newPlat = _gameRepository.GetPlatform(platId);

                d.Platforms.Add(newPlat); // <-- where it chokes
            }
        })
        .ForMember(dest => dest.BoxArtPath, opt => opt.Ignore())
        .ForMember(dest => dest.IndexImagePath, opt => opt.Ignore())
        .ForMember(dest => dest.Cons, opt => opt.MapFrom(src => String.Join("|", src.Cons)))
        .ForMember(dest => dest.Pros, opt => opt.MapFrom(src => String.Join("|", src.Pros)))
        .ForMember(dest => dest.LastModified, opt => opt.UseValue(DateTime.Now))
        .ForMember(dest => dest.Platforms, opt => opt.Ignore());
}

具体来说,我在上面突出显示的行中遇到了一个异常:

无法定义两个对象之间的关系,因为它们附加到不同的 ObjectContext 对象。

一些研究告诉我这是意料之中的,因为我的新 Game 对象没有与之关联的 ObjectContext,并且 null 上下文被认为是一个单独的上下文。请参阅this brief explanation from Julie Lerman 了解更多信息。

好吧,作为一个勇敢的人,我想我可以简单地使用 ObjectContext 注册我的游戏,一切都会得到解决。所以,我尝试了:

Game game = new Game();
_gameRepository.RegisterGame(game);

RegisterGame 就是这样:

public void RegisterGame(Game game)
{
    _siteDB.Games.AddObject(game);
}

不幸的是,这没有奏效。在同一点抛出相同的异常。

所以,看来我必须将每个平台的 EntityKey 添加到我的 EntityCollection 中。唯一的问题是,我不知道该怎么做。

那么,有什么想法吗?


编辑:某种进展。我尝试只添加平台实体的 EntityKey,如下所示:

Mapper.CreateMap<AdminGameEditModel, Game>()
    .BeforeMap((s, d) =>
    {
        if (d.Platforms.Count > 0)
        {
           Platform[] existing = d.Platforms.ToArray();

           foreach (var plat in existing)
           {
                d.Platforms.Remove(plat);
           }
        }

        foreach (var platId in s.PlatformIDs)
        {
            var newPlat = _gameRepository.GetPlatform(platId);

            d.Platforms.Add(new Platform { EntityKey = newPlat.EntityKey });
        }
    })

并且它消除了“两个不同的 ObjectContexts”异常。问题是,如果我尝试将新游戏实体添加或附加到我的上下文中,我会收到以下异常:

ObjectStateManager 中已存在具有相同键的对象。 ObjectStateManager 无法跟踪具有相同键的多个对象。

不幸的是,该异常没有指定 哪个 对象,但我敢打赌它是平台对象之一,因为它们已经存在,而我只是想建立一种新的关系两者之间。

所以,问题仍然存在:到底是如何创建一个新实体并使用现有实体填充其 EntityCollection 属性的?我不可能是唯一一个想要创建新实体并在它与现有实体之间创建新的多对多关系的人,对吧?

【问题讨论】:

  • 嘿,这里是 VT ;) 如果您先检查一下,我将引用我在线程中所说的话:您的“GetPlatform”方法是否使用与您的“RegisterGame”方法使用相同的上下文?仔细检查这些方法,它们正在访问 exact 相同的 Context 实例(不是克隆,不是单独的实例,而是完全相同的实例)。哦,并确保您将新的 Game 对象添加到上下文中(即不要丢弃您为将其添加到上下文中所做的更改),您肯定希望它与上下文相关联。
  • 是的,这是下一步。看起来它是相同的上下文,因为这两种方法都在访问同一个 repo,但这个例外让我感到奇怪。我正在学习如何创建一个上下文并将其与 Ninject 的所有存储库共享。
  • 啊,但是特定方法的内部是否也使用相同的上下文实例?我可以看到一个场景,其中一个人可能不小心 - 例如 - 在“GetPlatform”方法中创建了一个新上下文。绝对检查它只是为了确定。要检查的另一件事是他们没有使用存储库本身的单独实例。两者都绝对值得一试!
  • 当我有机会的时候肯定会检查它,虽然我有一个偷偷摸摸的怀疑,如果我确实有重复的上下文,它发生在 AutoMapper 本身。理想情况下,我会创建一个上下文,然后将其注入到我的存储库中,它们本身将被注入到我的控制器中。如果你想看一看,我也在想办法:stackoverflow.com/questions/6231851/…

标签: c# asp.net-mvc-2 entity-framework-4 automapper


【解决方案1】:

我最初通过简单地创建我自己的杂乱无章的静态映射器解决了这个问题(以及我在使用 EF4 多对多图与 AutoMapper 时遇到的其他问题)。它很丑陋,一点也不抽象,但它确实有效。随着 AutoMapper 2.0 的发布,我决定看看我能否最终让一切都按照我想要的方式工作。

令人惊讶的是,我在 AutoMapper 代码中得到了与最初相同的“不同 ObjectContexts”异常。它在我自己的方法中毫无例外地工作,但在 AutoMapper 中却没有。奇怪,看代码本质上是一样的。

AutoMapper 地图:

Mapper.CreateMap<AdminGameViewModel, Game>()
    .BeforeMap((s, d) =>
    {
        if (d.Platforms != null && d.Platforms.Count > 0)
        {
            var oldPlats = d.Platforms.ToArray();

            foreach (var oldPlat in oldPlats)
            {
                d.Platforms.Remove(oldPlat);
            }
        }

        foreach (var platId in s.PlatformIDs)
        {
            var plat = _gameRepository.GetPlatform(platId);
            d.Platforms.Add(plat);
        }
    })
    .ForMember(dest => dest.Platforms, opt => opt.Ignore())
    .ForMember(dest => dest.BoxArtPath, opt => opt.Ignore())
    .ForMember(dest => dest.IndexImagePath, opt => opt.Ignore())
    .ForMember(dest => dest.Cons, opt => opt.MapFrom(src => string.Join("|", src.Cons)))
    .ForMember(dest => dest.Pros, opt => opt.MapFrom(src => string.Join("|", src.Pros)))
    .ForMember(dest => dest.LastModified, opt => opt.UseValue(DateTime.Now));

我自己的映射器:

public static Game MapFromEditModelToGame(IGameRepository repo, AdminGameViewModel formData, Game newGame)
{
    newGame.GameID = formData.GameID;
    newGame.GameTitle = formData.GameTitle;
    newGame.GenreID = formData.GenreID;
    newGame.LastModified = DateTime.Now;
    newGame.ReviewScore = (short)formData.ReviewScore;
    newGame.ReviewText = formData.ReviewText;
    newGame.Cons = String.Join("|", formData.Cons);
    newGame.Pros = String.Join("|", formData.Pros);
    newGame.Slug = formData.Slug;

    if (newGame.Platforms != null && newGame.Platforms.Count > 0)
    {
        var oldPlats = newGame.Platforms.ToArray();

        foreach (var oldPlat in oldPlats)
        {
            newGame.Platforms.Remove(oldPlat);
        }
    }

    foreach (var platId in formData.PlatformIDs)
    {
        var plat = repo.GetPlatform(platId);
        newGame.Platforms.Add(plat);
    }

    return newGame;
}

我只能猜测存在一些奇怪的范围问题,但我认为这是一个有趣的(阅读:令人沮丧的)问题。不知道是因为 EF4 本身,还是我尝试将它与 AutoMapper 一起使用。

【讨论】:

    【解决方案2】:

    我使用Julie Lerman's original solution 让它工作。我不必使用我原来的预 DTO 解决方案分离/重新连接我的平台,所以我认为我不需要在这里。无论如何,看起来我需要对如何处理 ObjectContext 做更多的研究。

    【讨论】:

      【解决方案3】:

      试试这个:

      foreach (var platId in s.PlatformIDs)
      {
          Platfrom p = new Platform { Id = platId };
          context.Attach(p) 
          d.Platforms.Add(p);
      }
      

      您不必加载实体来建立关系。您只需要一个具有正确 ID 的假人(您已经拥有)。虚拟对象必须附加到上下文中。

      【讨论】:

      • 在尝试您的解决方案时遇到异常。如果我使用 _siteDB.Attach(plat),我会收到一个异常,告诉我它无法附加具有空 entitykey 的实体。如果我使用 _siteDB.Platforms.Attach(plat),我会收到一个异常,告诉我上下文中已经存在具有相同键的对象。
      猜你喜欢
      • 2011-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-31
      • 1970-01-01
      相关资源
      最近更新 更多