无法使用AutoMapper解析具有nhibernate的子实体

时间:2012-06-21 03:58:51

标签: c# nhibernate castle-windsor automapper

我在尝试解析模型中的子实体时遇到了一系列问题,我使用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; } 
}

我使用持久性工具来管理我的nhibernate会话:

        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;
    }
}

3 个答案:

答案 0 :(得分:1)

该异常主要来自于您在会话中创建具有现有ID的新对象。在你的情况下,AutoMapper很可能会这样做。

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

因此,您需要自定义Layout属性映射规则,如果它是现有模型,则从Session(Repository)中检索它,并设置它的值(通过AutoMapper或手动),然后会话状态是正确的。

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来检索它。

答案 1 :(得分:0)

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

// 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);
    }
}

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

答案 2 :(得分:0)

以典型的方式来看几个小时之后,并在国际奥委会上学到了一些艰难的教训。

在上面的代码中,您可以看到我已按照以下方式注册我的ISession

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

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

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

一旦我将其更改为.LifestylePerWebRequest()事情变得更好了,但我仍然遇到了一些会话问题。

最终确定通过所有层传递此会话(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)并且问题消失了。

尼斯。希望这有助于其他人看到同样的问题。

相关问题