views:

2275

answers:

4

How do I let NHibernate ignore extra properties of a subclass of my model?

class SuperModel { // hot I know
{
    public Guid Id { get; private set; }
    public string FirstName { get; set; }
}

class SubModel : SuperModel {
    public string FavoriteColor { get; set; }
}

I really only want to store the SuperModel data using my repository and use the FavoriteColor elsewhere, but I get

No persister for: SubModel

even though I save it with my repository as

void Store(SuperModel model) {
   using (var session = Session){
       session.SaveOrUpdate(model); // <<<< The exception is thrown here
   }
}

and some where else I use

void WhatToDo(SubModel model) {
   doSomething(model.FavoriteColor);
}

And I use it as such

var model = new SubModel { FirstName = "Miranda", FavoriteColor = "Green" };
modelRepository.Store(model);
someService.WhatToDo(model);

Any one know how I can fluently configure this? Thanks.

FYI- implicit and explicit casting has no effect.

Edit

My mappings are like this

class SuperModelMap : ClassMap<SuperModel>
{
    public SuperModelMap()
    {
        WithTable("SuperModels");
        Id(x => x.Id);
        Map(x => x.FirstName);
    }
}

Edit 2

I figure/found out that I could do this, but in my database, I have to have a dummy table, which would just be inefficient. It works but there has to be a better way...

In my SuperModelMap...

JoinedSubClass<SubModel>("SubModel", MapSubModel);

private void MapSubModel(JoinedSubClassPart<SubModel> part)
{
    // Leave this empty
}

Edit 3 I'm closer, but I still get a different error on selection.

I tried this.

DiscriminateSubClassesOnColumn("Id")
    .SubClass<SubModel>(m => { });

InnerException {"Object with id: 5586b075-47f1-49c8-871c-9c4d013f7220 was not of the specified subclass: SuperUser (Discriminator was: '1000')"} System.Exception {NHibernate.WrongClassException}

+1  A: 

NHibernate assumes that you'd like to retrieve exactly the same object as you persist. So, even though you don't care about additional properties, you might care about the type of object. If you don't, the simplest solution would be to make a shallow copy of SubModel object, but instead of creating SubModel object, create SuperModel object.

I assume you thought about this and didn't like it. If you'd like to avoid dummy table, but can live with dummy column, I'd suggest you call:

DiscriminateSubClassesOnColumn("dummycolumn")
.SubClass<SubModel>(m => { });

This column would be used by NHibernate to store information about persisted object's type. When you load object from the db, it will be SubModel or SuperModel, depending on what it was when you persisted it.

Your solution with calling DiscriminateSubClassesOnColumn didn't work, because NHibernate couldn't determine which class to use based on id column.

Another idea: I'm not sure if it will work, but you could add another mapping, for SubModel, exactly the same as for SuperModel. Then, NHibernate should persist SubModel to the same table as SuperModel, and when you request your object it should fetch SuperModel object. Unfortunately I can't test this solution right now, maybe you can get it to work. No SubClass in this solution - two "parallel" mappings.

maciejkow
It didn't like it when I tried to retrieve.
Daniel A. White
Alright I got it to work, but now it returns a SubModel. Any way to stop that?
Daniel A. White
You idea is valid, but it wouldn't be reusable.
Daniel A. White
+3  A: 

You can refine this solution to make it more reusable. As I understand, you don't like mapping duplication. This can be avoided:

I have created a SuperModelMapHelper class that contains an extension method:

public static class SuperModelMapHelper
{
    public static void MapSuperModel<T>(this ClassMap<T> classMap)
        where T : SuperModel
    {
        classMap.WithTable("SuperModels");
        classMap.Id(x => x.Id);
        classMap.Map(x => x.FirstName);
    }
}

As you can see - it's generic and will accept any of SuperModel's subclasses. Then, there are two mappings:

public class SuperModelMap : ClassMap<SuperModel>
{
    public SuperModelMap()
    {
        MapSuperModel();
    }
}

public class SubModelMap : ClassMap<SubModel>
{
    public SubModelMap()
    {
        MapSuperModel();
    }
}

I've used extension method to preserve convention of FluentNHibernate, you can make it simple static method and pass class map as a parameter.

And this code:

Guid id;
using (var session = sf.OpenSession())
using (var transaction = session.BeginTransaction())
{
    var subModel = new SubModel()
                        {FavoriteColor = "blue", FirstName = "Jane"};
    session.Save(subModel);
    id = subModel.Id;
    transaction.Commit();
}

using (var session = sf.OpenSession())
using (var transaction = session.BeginTransaction())
{
    var superModel = session.Get<SuperModel>(id);
    Console.WriteLine(superModel.GetType().Name);
    Console.WriteLine(superModel.FirstName);
    transaction.Commit();
}

Works as intended - type is SuperClass. Note that I've created second session. You'd have to flush your session before trying to load entity in the same session you saved it, because NHibernate defers query execution.

Using this solution there is very little duplication. You can investigate AutoMapping feature of FluentNHibernate to reduce it even more - perhaps creating own convention would let you to automatically map such classes.

maciejkow
A: 

I just had to make my class public in Fluent NHibernate Mapping. I spent 2 freaking hours before finding the solution.

ViBi