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}
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.
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.
Related
TLDR version: I'm having trouble getting my DDD domain model to work with NHibernate. If my value object itself contains a collection of value objects, I can't assign a new value without getting an NHibernate exception, and want to know what the best practice is in this situation.
Longer version:
Say I have an entity which contains a value object as a property, ValueObjectA, which itself contains a set of a different value objects of type ValueObjectB.
ValueObjectB only exists meaningfully as a property of ValueObjectA, i.e. if myEntity.ValueObjectA == null, it doesn't make sense for ValueObjectB to exist either.
I've written some example code to illustrate what I mean, with simplifications for brevity.
public class Entity
{
public int Id { get; private set; }
public ValueObjectA ValueObjectA { get; set; }
// Constructor: public Entity(ValueObjectA valueObjectA)
}
public class ValueObjectA : IEquatable<ValueObjectA>
{
public string X { get; private set; }
public ISet<ValueObjectB> ValueObjectBs { get; private set; }
// Constructor: public ValueObjectA(string x, ISet<ValueObjectB> valueObjectBs)
// Implementation of Equals/GetHahcode
}
public class ValueObjectB : IEquatable<ValueObjectB>
{
public int Y { get; private set; }
public int Z { get; private set; }
// Constructor: public ValueObjectB(int y, int z)
// Implementation of Equals/GetHahcode
}
I have a corresponding mapping class using mapping by code:
public class EntityMap : ClassMapping<Entity>
{
public EntityMap()
{
Table("Entity");
Id(x => x.Id, map => map.Generator(Generators.Identity));
Component(x => x.ValueObjectA, c =>
{
c.Property(x => x.X);
// Component relation is equilavent to <composite-element> in xml mappings
c.Set(x => x.ValueObjectBs, map =>
{
map.Table("ValueObjectB");
map.Inverse(true);
map.Cascade(Cascade.All | Cascade.DeleteOrphans);
map.Key(k => k.Column("Id"));
}, r => r.Component(ce =>
{
ce.Property(x => x.Y);
ce.Property(x => x.Z);
}));
});
}
}
The properties of ValueObjectA are mapped to the Entity table, but the properties of ValueObjectA.ValueObjectB are mapped to another table, since it is a one to many relationship. When a ValueObjectB is removed, I want that row to be deleted in the ValueObjectB table.
Since value objects are immutable, when I change the properties of entity.ValueObjectA, I should create a new instance of ValueObjectA. The problem is that the set of ValueObjectBs is a reference type, so when I try to save the entity with a different ValueObjectA, NHibernate will throw an exception because the original set that NHibernate is tracking is no longer referenced:
A collection with cascade="all-delete-orphan" was no longer referenced
by the owning entity instance.
Consider the following code:
var valueObjectBs_1 = new HashSet<ValueObjectB>
{
new ValueObjectB(1, 2),
new ValueObjectB(3, 4)
};
var valueObjectA_1 = new ValueObjectA("first", valueObjectBs_1);
var entity = new Entity(valueObjectA_1);
// Save entity, reload entity
var valueObjectBs_2 = new HashSet<ValueObjectB>
{
new ValueObjectB(1, 2)
};
var valueObjectA_2 = new ValueObjectA("second", valueObjectBs_2);
entity.ValueObjectA = valueObjectA_2;
// Save entity again
// NHIBERNATE EXCEPTION
I've managed to get around this by creating another ValueObjectA in order to preserve the reference to the set, e.g.
valueObjectA_1.ValueObjectBs.Remove(new ValueObjectB(3, 4));
entity.ValueObjectA = new ValueObjectA(valueObjectA_2.X, valueObjectA_1.ValueObjectBs);
However... that feels like a code smell - even if I wrote a custom setter for Entity.ValueObjectA, the implementation is starting to get complicated where the design is supposed to be simple.
public class Entity
{
// ...
private ValueObjectA valueObjectA;
public ValueObjectA ValueObjectA
{
// get
set
{
// Add/Remove relevant values from ValueObjectA.ValueObjectBs
valueObjectA = new ValueObjectA(value.X, ValueObjectA.ValueObjectBs);
}
}
}
What is the best practice in this type of situation? Or is this a sign that I'm trying to do something which violates the principles of DDD?
What you have is an anemic domain model.
You should replace public setters of the entity with methods that have meaningful names from the Ubiquitous language, that check the invariants and that do all the necessary cleanup in case of value objects replacements.
Although it may seem that things are more complicated this is payed back by the fact the now the entity is in full control about what happens with its internals. You now have full encapsulation.
Using
NHibernate : 3.3.2
Fluent NHibernate: 1.3.0
.NET 4.0
Hi all, I'm trying to put together a (very) simple reference project for Fluent NHibernate using automapping, in particular setting up table-per-hierarchy inheritance. I've tried copying the config from an existing (working) project and I've run through the example on the Fluent Wiki page on AutoMapping and inheritance and both give me the same result; the base class that I've set up with table-per-hirearchy gets treated like a regular class.
The domain model looks like so:
namespace Tests
{
public abstract class Animal
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual int Legs { get; set; }
}
public class Cat : Animal {}
public class Budgie : Animal {}
}
Like I said, simple, just to illustrate inheritance. I'm aware that 'legs' should probably be overridden on the base classes :)
The AutoMapping configuration looks like so:
public class AutoMappingConfig : DefaultAutomappingConfiguration
{
public override bool ShouldMap(Type type)
{
var include = type.IsSubclassOf(typeof(Animal));
Debug.WriteLineIf(include, string.Format("Included {0} in NHibernate mapping.", type));
return include;
}
public override bool IsDiscriminated(Type type)
{
var result = type.In(
(typeof(Cat)),
(typeof(Budgie))
);
return result;
}
}
And finally, the configuration/session creation looks like so:
public static ISession NewSession()
{
var cfg = new AutoMappingConfig();
var map = AutoMap.AssemblyOf<Animal>(cfg)
.IgnoreBase<Animal>();
var dbConfiguration = MsSqlConfiguration.MsSql2008
.ConnectionString(ConfigurationManager.ConnectionStrings["testdb"].ConnectionString);
return Fluently.Configure()
.Mappings(m =>m.AutoMappings.Add(map))
.Database(dbConfiguration)
.BuildSessionFactory()
.OpenSession();
}
Putting that together, I try to create a couple of new records:
[Test]
public void CreateData()
{
var tiddles = new Cat {Name = "Tiddles", Legs = 4};
var kylie = new Budgie {Name = "Kylie", Legs = 2};
using (var transaction = _session.BeginTransaction())
{
_session.Save(tiddles); // exception!
_session.Save(kylie);
transaction.Commit();
}
}
}
The error is:
NHibernate.Exceptions.GenericADOException : could not insert:
[Tests.Cat][SQL: INSERT INTO [Cat] (Name, Legs) VALUES (?, ?); select
SCOPE_IDENTITY()] ----> System.Data.SqlClient.SqlException : Invalid
object name 'Cat'.
A few things to note:
The '?' are being filled out if I check with SQL Profiler.
When I put a breakpoint in the IsDiscriminated(Type type) method I can see that
its being called with the two expected types (Cat & Budgie) and is returning true each time. However, the SQL is writing to the wrong table, and its NOT writing a discriminator column. i.e. even though its been told that these classes are discriminated, they're not being treated as such.
In the table, the Id column is an auto-increment identity column.
I've tried adding other properties to the two sub classes in case they needed something other than just the base properties to trigger the correct behavior (no difference).
Any help would be greatly appreciated. I'm now convinced its something obvious, but no one here knows much about NHibernate (LightSpeed is another matter) so I've no idea what.
Ok, so the final working code looks like this:
public class AutoMappingConfig : DefaultAutomappingConfiguration
{
public override bool ShouldMap(Type type)
{
var include = type.IsSubclassOf(typeof(Animal)) || type == typeof (Animal);
Debug.WriteLineIf(include, string.Format("Included {0} in NHibernate mapping.", type));
return include;
}
public override bool IsDiscriminated(Type type)
{
return typeof(Animal).IsAssignableFrom(type);
}
}
public static ISession NewSession()
{
var cfg = new AutoMappingConfig();
var map = AutoMap.AssemblyOf<Animal>(cfg)
.IncludeBase<Animal>();
var dbConfiguration = MsSqlConfiguration.MsSql2008
.ConnectionString(ConfigurationManager.ConnectionStrings["testdb"].ConnectionString);
return Fluently.Configure()
.Mappings(m =>m.AutoMappings.Add(map))
.Database(dbConfiguration)
.BuildSessionFactory()
.OpenSession();
}
And all is well with the world :)
(i.e. there were three errors)
The instructions here are a bit confusing, as it first talks about using .IgnoreBase<> so NHibernate wont treat the base as entity in its own right, then later mentions using .Includebase<> when using abstract layer supertypes. I'd tried both, but without Firo's answer got no luck.
you forgot animal
public override bool IsDiscriminated(Type type)
{
return typeof(Animal).IsAssigneableFrom(type);
}
Newbie Alert
I am trying to check if an entity exists in the database, if it does i want to update it else create a new entity. But CreateCriteria use always returns an entity with no id? Any ideas why? I am using fluent nhibernate for manual mapping i.e use of ClassMap;
Base class -hold only the Id public property
public abstract class EntityBase : IEquatable
{
public virtual int Id { get; set; }
public virtual bool Equals(EntityBase obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (GetType() != obj.GetType()) return false;
return obj.Id == Id;
}
}
MAPPING;
public class ProjectNameMap: ClassMap<ProjectName>
{
public ProjectNameMap()
{
Table("dbo.TABLENAME");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.PROJECT_NAME).Not.Nullable();
Map(x => x.PROJECT_DESCRIPTION);
}
}
Getting back the entity;
public static T GetEntityByRestrictions<T>(String propertyName, String propertyValue)
where T:EntityBase
{
using (var session = SessionManager.CreateSessionFactory().OpenSession())
{
using (var transaction = session.BeginTransaction())
{
var entity= session.CreateCriteria<T>(propertyValue)
.Add(Restrictions.Eq(propertyName, propertyValue))
.UniqueResult<T>();
return entity;
}
}
}
}
Two silly steps i tried (dont laugh)
1. Silly step i tried was to manually set the Id =52 matching existing database entry and i keep on getting a primary key violation on project name as the database expects unique project name.
More silly steps (i can hear laughter) modified mapping file to include Map(x=>x.Id).Update().Insert() and this lead to INSERT_IDENTITY set to OFF (or somethin).
So whats the best way to get an entity with Id and update afterwards,is there something wrong with my CreateCriteria?
I believe calling the Merge method on the session will do exactly what you want.
Just put in the entity you want to update/insert as an argument and it will update the properties if the entity already exists or persist the new instance if it does not. This way you don't need the CreateCriteria and your solution will be a lot simpler.
Disclaimer: I'm fairly new to NH & ORM in general.
Disclaimer: I'm working with a build of FNH from here in order to use with NH3.0GA.
The problem in a nutshell is that I would like to use FNH's SubclassMap as a way to map a LEFT JOIN, table-per-subclass scenario to my object hierarchy which is defined as:
public class MyBaseClass {
public virtual int Id { get; set; }
}
public class MySubClass : MyBaseClass {
public virtual string SubClassVal { get; set; }
}
This is mapped via FNH as:
public class MyBaseClassMap : ClassMap<MyBaseClass> {
public MyBaseClassMap() {
Table("BaseClass");
Id(x => x.Id, "Id").GeneratedBy.Assigned();
}
}
public class MySubClassMap : SubclassMap<MySubClass> {
public MySubClassMap() {
Table("SubClass");
KeyColumn("Id");
Map(x => x.SubClassVal);
}
}
And I retrieve via:
public class Repository {
ISession session; //assume properly initialized ISession
public IList<T> GetAll<T>() where T: class {
return session.CreateCriteria<T>().List<T>();
}
}
And in my database, I've got 1 record in my BaseClass table, 0 records in SubClass.
Now, what I would like to do is pull the entity out as a MySubClass instance by doing something like this:
var rep = new Repository();
var subclasses = rep.GetAll<MySubClass>();
And of course, there are no instances in the returned collection as this is presumably performing an INNER JOIN underneath it all. This is where I'm stuck. I've managed to discover that specifying an 'optional' join is what I'm supposed to do. I've attempted to modify MySubClassMap to:
public class MySubClassMap : SubclassMap<MySubClass> {
public MySubClassMap() {
Join("SubClass", j => {
j.KeyColumn("Id");
j.Optional();
j.Map(x => x.SubClassVal); // note that I've tried the map outside the Join() below, to no avail
});
//Map(x => x.SubClassVal);
}
}
Compiling/running this presents me with the following (innermost) exception:
The element 'joined-subclass' in namespace 'urn:nhibernate-mapping-2.2' has invalid child element 'join' in namespace 'urn:nhibernate-mapping-2.2'. List of possible elements expected: 'property, many-to-one, one-to-one, component, dynamic-component, properties, any, map, set, list, bag, idbag, array, primitive-array, joined-subclass, loader, sql-insert, sql-update, sql-delete, resultset, query, sql-query' in namespace 'urn:nhibernate-mapping-2.2'.
I'll save posting the stack trace, but the jist of it is:
MyApp -->
FluentNHibernate.Cfg.FluentConfiguration.BuildSessionFactory() -->
NHibernate.Cfg.FluentConfiguration.BuildConfiguration()
I think that's all the relevant info. I suspect I may be bumping into a breaking change between this very new version of NH and version of FNH that isn't so new. But, as mentioned earlier, I am a rookie, and could well be doing something stupid. If this is the case, I'd very much appreciate somebody smacking me over the head with what probably should be obvious.
Thanks in advance.
Entities have one type, which doesn't change. If you have a record in your BaseClass table only, that entity is and will always be a MyBaseClass.
If entities can change their "type", you shouldn't use inheritance but composition.
I have the following idea:
Business object implemented as interface or abstract class with certain properties as read only to all layers except the DAL layer. I also want my business objects in another assembly than the DAL (for testing purposes), so marking the properties is not an option for me.
Examples could be one to one relationships or other properties.
I have almost solved the issue by doing the following
abstract class User
{
public virtual long UserId {get; protected set;}
public virtual string Password {get; protected set;}
...
}
In the DAL:
public class DbUser : User
{
internal virtual void SetPassword(string password) {...}
}
I then map this using fluent as
ClassMap<User> {...}
SubclassMap<DbUser> {...}
The problem I get is that fluent tries to create a table named DbUser.
If I skip the SubclassMap and creates a DbUser object and tries to save it I get an "No persister for this object" error.
Is it possible to solve?
You could probably override what is done with Fluent
public class DbUser: IAutoMappingOverride<DbUser>
{
public void Override(AutoMapping<DbUser> mapping)
{
//tell it to do nothing now, probably tell it not to map to table,
// not 100% on how you'd do this here.
}
}
Or you could have an attribute
public class DoNotAutoPersistAttribute : Attribute
{
}
And in AutoPersistenceModelGenerator read for attribute in Where clause to exclude it.
Check would be something like
private static bool CheckPeristance(Type t) {
var attributes = t.GetCustomAttributes(typeof (DoNotAutoPersistAttribute), true);
Check.Ensure(attributes.Length<=1, "The number of DoNotAutoPersistAttribute can only be less than or equal to 1");
if (attributes.Length == 0)
return false;
var persist = attributes[0] as DoNotAutoPersistAttribute;
return persist == null;
}
Then it kind of depends how you're adding entities but you're probably adding via assembly so this might do it for you:
mappings.AddEntityAssembly(typeof(User).Assembly).Where(GetAutoMappingFilter);
....
...
private static bool GetAutoMappingFilter(Type t)
{
return t.GetInterfaces().Any(x => CheckPeristance(x)); //you'd probably have a few filters here
}