我在尝试解析模型中的子实体时遇到了一系列问题,我使用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;
}
}
答案 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)并且问题消失了。
尼斯。希望这有助于其他人看到同样的问题。