【问题标题】:Unable to use AutoMapper to resolver child entities with nhibernate无法使用 AutoMapper 解析具有 nhibernate 的子实体
【发布时间】:2012-06-23 06:47:38
【问题描述】:

我在尝试解决模型中的子实体时遇到了一系列问题,我使用 nhibernate 进行持久化,使用 windsor 进行 ioc 和 automapper 进行映射。

我已经以多种方式对此进行了攻击,并且几乎总是在此过程中被阻止,非常感谢任何帮助。

我对以下代码的问题是,当我尝试通过以下方式更新页面布局时。 (假设只有 layout-id 发生变化)

        var page = _pageRepository.Get(model.Id);
        Mapper.Map(model, page);

        using (ITransaction tran = _sessionFactory.BeginTransaction())
        {
            _pageRepository.Update(page);
            tran.Commit();
        }

我得到一个很好的错误提示,

具有相同标识符值的不同对象已经存在 与会话关联:用于布局模型。

现在我尝试过: - 将设施更改为 perwebrequest(然后说会话已关闭) - 获取后尝试从缓存中删除布局(错误如上) - 我尝试在解析器中获取现有会话(上下文错误)

我应该如何进一步解决这个问题?肯定不能这么难!我哪里错了?非常感谢。

这里是所有重要的部分。

我有一个这样的模型:

public class ContentPage : Page
{ 
    public virtual Layout Layout { get; set; } 
}

我使用持久化工具来管理我的休眠会话,如下所示:

        Kernel.Register(
            Component.For<ISessionFactory>()
                .UsingFactoryMethod(_ => config.BuildSessionFactory()),

            Component.For<ISession>()
                .UsingFactoryMethod(k => k.Resolve<ISessionFactory>().OpenSession())
                .LifestylePerThread() <-- IMPORTANT FOR LATER.
            );

我的映射是这样的:

        CreateMap<BlaViewModel, ContentPage>()
            .ForMember(dest => dest.DateModified, src => src.MapFrom(x => DateTime.UtcNow)) 
            .ForMember(x => x.Layout, x => x.ResolveUsing<EntityResolver<Layout>>().FromMember(y => y.Layout_Id));

最后我的解析器是这样的:

public class EntityResolver<T> : ValueResolver<Guid, T> where T : EntityBase
{
    private readonly ISession _session;

    public EntityResolver(ISession session)
    {
        _session = session;
    }

    protected override T ResolveCore(Guid id)
    {
        var entity = _session.Get<T>(id); 
        return entity;
    }
}

【问题讨论】:

    标签: c# nhibernate castle-windsor automapper


    【解决方案1】:

    异常主要来自于您在 Session 中创建了一个具有现有 Id 的新对象。在您的情况下,AutoMapper 很可能会这样做。

    如何在 ContentPage 地图中配置 Layout 属性?如果你默认使用它,那么 AutoMapper 会创建一个新的 Layout 对象,并给它设置 Id,它不是从 Session 加载的。然后保存这个对象会导致异常。

    所以需要自定义 Layout 属性映射规则,如果是已经存在的模型,则从 Session(Repository) 中检索,并设置其值(通过 AutoMapper 或手动),则 session 的状态是正确的。

    ContentPage 的 AutoMapper 配置可能如下:

    Mapper.CreateMap<VPage, ContentPage>()
        ....
        .ForMember(des=>des.Layout, opt=>opt.MapFrom(src=>GetLayout(src))) //customize Layout
        ....;
    

    在你的 GetLayout 函数中是这样的:

    private Layout GetLayout(VPage page)
    {
        var layout = page.LayoutId == 0? new Layout() : _layoutRepository.Get(page.LayoutId); //avoid new Layout object with existed Id
        .......
    
        return layout;
    }
    

    另外,你最好不要使用 AutoMapper 从 DTO 转换域模型,请参阅this explanation

    更新:很抱歉没有看到您的 EntityResolver,请尝试使用您的 LayoutRepository 来检索它。

    【讨论】:

      【解决方案2】:

      我的猜测是解析器正在使用另一个会话来获取布局。

      // sample code
      var layout1 = Resolve(1);
      
      session.Attach(layout1);  // now contains layout 1
      
      var layout2 = Resolve(1);
      
      session.Attach(layout2);  // error: already contains layout with id 1
      
      
      public Layout Resolve(int id)
      {
          using (var session = OpenSession())
          {
              return GetNewSession.Get<Layout>(1);
          }
      }
      

      使用相同的会话来解析连接的实体

      【讨论】:

        【解决方案3】:

        几个小时后以典型的方式解决这个问题,并在 IOC 上学到了一些深刻的教训。

        在上面的代码中,你可以看到我注册了我的 ISession 来解析如下

         Kernel.Register(
                Component.For<ISessionFactory>()
                    .UsingFactoryMethod(_ => config.BuildSessionFactory()),
        
                Component.For<ISession>()
                    .UsingFactoryMethod(k => k.Resolve<ISessionFactory>().OpenSession())
                    .LifestylePerThread() <-- IMPORTANT FOR LATER.
                );
        

        这是我的问题的开始,主要是因为这是每个线程,几乎所有不同的点都会解决一个新的会话。以会话的多个实例结束。 (因此错误)

        将其更改为 .LifestylePerWebRequest() 后,情况有所好转,但我仍然遇到一些会话问题。

        最终弄清楚这个 Session 正在通过所有层(通过构造器的 IOC)所以我的 Manager 层、Repository 层以及它正在使用的任何地方都需要更改为作为 PerWebRequest 安装。

        喜欢:

        container.Register(Classes.FromAssemblyContaining<Repository>()
                                       .Where(Component.IsInSameNamespaceAs<Repository>())
                                       .WithService
                                       .DefaultInterfaces()
                                       .LifestylePerWebRequest());
        

        还有:

                container.Register(Component.For<EntityResolver<Layout>>().ImplementedBy<EntityResolver<Layout>>().LifestylePerWebRequest());
        

        通过正确使用我的 IOC,最终达到了只有一个会话被假脱机 (PerWebRequest) 并且问题消失的地步。

        很好。希望对其他关注相同问题的人有所帮助。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-09-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多