0

I am having a series of issues trying to resolve child entities in my models, I am using nhibernate for persistence, windsor for ioc and automapper for the mapping.

I've attacked this in several ways and almost always get blocked along the way, any help would be greatly appreciated.

My problem for the code below, is that when I try to update the pages layout through the following. (assume only layout-id is changing)

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

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

I get a nice error saying,

a different object with the same identifier value was already associated with the session: for the layout model.

Now I have tried: - changing facility to perwebrequest (then says session is closed) - tried remove layout from the cache after getting it (error as above) - i've tried getting the existing session in the resolver (context error)

How should I approach this further? Surely it cant be this hard! Where am I going wrong? Thanks so much.

Here are all the important bits.

I have a model like this:

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

I use a persistent facility to manage my nhibernate sessions like this:

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

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

And my mapping like this:

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

And finally my resolver like this:

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;
    }
}
shenku
  • 11,969
  • 12
  • 64
  • 118

3 Answers3

1

The exception mostly comes from that you create a new object with a existed Id in the Session. In your case it is likely the AutoMapper does this.

How do you config the Layout property in your ContentPage map? If you use it by default, then AutoMapper will create a new Layout object and set Id to it, which is not loaded from Session. Then save this object can cause the exception.

So you need to customize the Layout property mapping rule, retrieve it from the Session(Repository) if it is a existed model, and set it's value (via AutoMapper or manually), then the state of session is correct.

You AutoMapper config for ContentPage may be like:

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

And in your GetLayout function it's like:

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

What's more, you better not use AutoMapper to convert domain models from DTO, see this explanation.

Updated: Sorry not seeing your EntityResolver, try to use your LayoutRepository to retrieve it instead.

Community
  • 1
  • 1
Chris Li
  • 3,715
  • 3
  • 29
  • 31
0

My guess is that the resolver is using another session to get the layout.

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

use the same session to resolve connected entities

Firo
  • 30,626
  • 4
  • 55
  • 94
0

In typical fashion figure this out a few hours later, and learned some tough lessons on IOC.

In the code above, you can see I registered my ISession to be resolved as follows

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

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

This was the start of my problem, essentially because this was per thread, a new Session would be resolved for almost all the different points. Ending up with multiple instances of the session. (Hence the error)

Once I changed this to be .LifestylePerWebRequest() things got a bit better, but I was still getting some Session troubles.

Eventually figuring out that through all the layers this Session was being passed (IOC via contructor) so the my Manager layers, Repository layers and everywhere it was being used, all need to be changed to be Installed as PerWebRequest.

Like:

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

And:

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

With proper usage of my IOC, eventually got to the point where only ONE session was ever spooled up (PerWebRequest) and the problem went away.

Nice. Hope that helps for anyone else looking at the same issue.

shenku
  • 11,969
  • 12
  • 64
  • 118