views:

132

answers:

3

Hi,

we have an old, big asp.net application with nhibernate, which we are extending and upgrading some parts of it. NHibernate that was used was pretty old ( 1.0.2.0), so we decided to upgrade to ( 2.1.2) for the new features. HBM files are generated through custom template with MyGeneration. Everything went quite smoothly, except for one thing.

Lets say we have to objects Blog and Post. Blog can have many posts, so Post will have many-to-one relationship. Due to the way that this application operates, relationship is done not through primary keys, but through Blog.Reference column.

Sample mapings and .cs files:

<?xml version="1.0" encoding="utf-8" ?>

    <id name="Id" column="Id" type="Guid">
        <generator class="assigned"/>
    </id>
    <property column="Reference" type="Int32" name="Reference" not-null="true" />
    <property column="Name" type="String" name="Name" length="250" />
</class>

<?xml version="1.0" encoding="utf-8" ?>

    <id name="Id" column="Id" type="Guid">
        <generator class="assigned"/>
    </id>

    <property column="Reference" type="Int32" name="Reference" not-null="true" />
    <property column="Name" type="String" name="Name" length="250" />
    <many-to-one name="Blog" column="BlogId" class="SampleNamespace.BlogEntity,SampleNamespace" property-ref="Reference" />
</class>

And class files

class BlogEntity
{
    public Guid Id { get; set; }
    public int Reference { get; set; }
    public string Name { get; set; }
}

class PostEntity
{
    public Guid Id { get; set; }
    public int Reference { get; set; }
    public string Name { get; set; }
    public BlogEntity Blog { get; set; }
}

Now lets say that i have a Blog with Id 1D270C7B-090D-47E2-8CC5-A3D145838D9C and with Reference 1

In old nhibernate such thing was possible:

        //this Blog already exists in database
        BlogEntity blog = new BlogEntity();
        blog.Id = Guid.Empty;
        blog.Reference = 1; //Reference is unique, so we can distinguish Blog by this field
        blog.Name = "My blog";

        //this is new Post, that we are trying to insert
        PostEntity post = new PostEntity();
        post.Id = Guid.NewGuid();
        post.Name = "New post";
        post.Reference = 1234;
        post.Blog = blog; 

        session.Save(post);

However, in new version, i get an exception that cannot insert NULL into Post.BlogId. As i understand, in old version, for nhibernate it was enough to have Blog.Reference field, and it could retrieve entity by that field, and attach it to PostEntity, and when saving PostEntity, everything would work correctly. And as i understand, new NHibernate tries only to retrieve by Blog.Id.

How to solve this? I cannot change DB design, nor can i assign an Id to BlogEntity, as objects are out of my control (they come prefilled as generic "ojbects" like this from external source)

A: 

It seems very strange to me that the code worked in NH 1. But, since it is not working at the moment anyway, I think you have to look for the blog entity in a query first:

var criteria = DetachedCriteria.For<Blog>();
criteria.Add(Expression.Eq("Reference", 1));
var blog = criteria.GetExecutableCriteria(session).List<Blog>().FirstOrDefault();

post.Blog = blog;
session.Save(post);
Jan Willem B
Unfortunately i cannot do like this. Lets say i have 50+ different objects, and they come in through old-fashioned "generic" method - lets say InsertEntity(object entity) . And entity could be Blog, could be Post, could be User, could be anything else... And the worst part - i cannot change that method, because its out of my control
Meska
I still don't understand why you can't do it. You have a session.Save(post) in your original code, that is no different in my code. And the code I added is only doing a query to lookup the Blog entity by its reference. So what exactly is the problem? If the problem is that you can't change the code, I would suggest you don't introduce a new NHibernate library.
Jan Willem B
This Blog/Post scenario is just an proof-of-concept what is happening. Its much more complicated, and the save method is mor like:InserEntity (object entity){ //do some processing. entity is really generic session.save}and i cannot change signature of InserEntity (object entity). Anyhow, thanks for help :), and it seems that we will skip introduction of new nhibernate, if there is no feasible solution within few days
Meska
A: 

this

blog.Id = Guid.Empty

is translated as a null in the DB. So when you change it (as the sample code implies) you are explicitly setting a null value on the BlogEntity Id.

This is the error you are receiving and is irrelevant of the "Reference" column/property.

As for the question of what you can do... well you don't have to make the ORM joins on the Guids! You can make the joins on the Reference column...

Jaguar
But how come it did work in 1.0.2.0 ? Could you explain more on ORM making joins on Reference ? Any keywords so i could google up more info ? Thanks
Meska
I have found many differences from 1.2 to 2++ with most being that 2++ versions are quite stricter on the rules. Now as to the joins, just because you have a DB PK it doesn't mean you actually have to map it as such. You can map the Reference column as the Id of the entity but be prepared to make a lot of testing and ensure that the actual Guid PK is always inserted etc.The problem derives from a bad db schema so don't expect NHibernate to auto-magically resolve that for you.
Jaguar
A: 

Answering my own question.

The problem was that nhibernate was hiting DB to retrieve BlogEntity with id 00000000-0000-0000-0000-000000000000. Of course in DB it got nothing, so it tried to insert null

And it was clearly visible in logs why it was happening

Unable to determine if BlogEntity with assigned identifier 00000000-0000-0000-0000-000000000000 is transient or detached; querying the database. Use explicit Save() or Update() in session to prevent this.

Solved it my implementing IInterceptor, passing it to Session and especially its method bool? IsTransient(object entity)

And problem solved.

Meska