"No persister for" using Fluently mapping of Subclass in Fluent NHibernate - fluent-nhibernate

I'm doing a very basic thing with Fluent NHibernate. I found a lot of people with similar problems here in SO but none seemed to fix my problem.
I have 1 Class like:
public abstract class ParentClass
{
public virtual long Id { get; private set; }
public virtual DateTime CreateDate { get; set; }
public virtual int Type { get; set; }
}
And 1 Concrete classes like:
public class ChildClass : ParentClass
{
public virtual string PropertyX { get; set; }
public virtual int PropertyY{ get; set; }
}
So I made the mapping as follows:
public class ParentMap : ClassMap<ParentClass>
{
public ParentMap()
{
Id(x => x.Id);
Map(x => x.CreateDate);
DiscriminateSubClassesOnColumn("Type");
}
}
And
public class ChildMap : SubclassMap<ChildClass>
{
public ChildMap()
{
Extends<ParentClass>();
DiscriminatorValue(1);
Map(x => x.PropertyX);
Map(x => x.PropertyY);
}
}
My legacy database has 2 tables, 1 that holds all the data from the ParentClass and another one that holds the data from the Child with a foreign key in the ID.
The idea is to have different tables for each different implementation of the ParentClass but having the ParentClass table as a single repository for "Ids" and "Create Dates".
I'm creating my SessionFactory as follows:
private static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(MsSqlCeConfiguration.Standard.ConnectionString(cstr => cstr.FromConnectionStringWithKey("TheConnectionString")))
.Mappings(mappings => mappings.FluentMappings.AddFromAssemblyOf<ParentClass>()
.ExportTo(#"c:\temp\Mappings"))
.BuildSessionFactory();
}
I'm doing just a basic test of storing things to the database as:
public void Store(ParentClass parent)
{
using (var session = sessionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
session.SaveOrUpdate(parent);
transaction.Commit();
}
}
}
But despite the method waits for a ParentClass variable, I'm passing a ChildClass instance for it (the method is actually a inheritance of an interface, that's why it expects a ParentClass).
And every time I it raises an error on "SaveOrUpdate" method saying "No persister for: ChildClass".
What am I doing wrong?
ps.: Another strange thing is that even with the "ExportTo" method on the SessionFactory creation, no mapping is being write on the folder.

Change
.Mappings(mappings => mappings.FluentMappings.AddFromAssemblyOf<ParentClass>()
To
.Mappings(mappings => mappings.FluentMappings.AddFromAssemblyOf<ParentMap>()

Related

Fluent Nhibernate Enum Mapping

I have some problem with enum mapping in fluent NHibernate. I know this question has been asked many times but I couldn't find any solution that worked for me. I'm newbie in NHibernate and it looks like I may have missed something simple and stupid. Here is my code.
public class DBPublication
{
public virtual int pub_id { get; set; }
public virtual PublicationStatuses status { get; set; }
...
}
public enum PublicationStatuses
{
on_moderation,
active,
...
}
public class DBPublicationMap : ClassMap<DBPublication>
{
public DBPublicationMap()
{
Table("content.publications");
Id(x => x.pub_id).GeneratedBy.Sequence("content.pub_sq");
Map(x => x.status);
...
}
}
postgres enum type
CREATE TYPE content.enum_publication_status AS ENUM('on_moderation', 'active', ...);
but when I try to save, postgres throws this
column "status" is of type content.enum_publication_status but expression is of type text
any suggestion?
Here is a working sample of configuring nhibernate to store enum field.
public class Entity
{
public virtual int id { get; set; }
public virtual SomeEnum EnumField { get; set; }
}
public enum SomeEnum
{
Value1,
Value2
}
class EntityMap : ClassMap<Entity>
{
public EntityMap()
{
Id(x => x.id).GeneratedBy.Native();
Map(x => x.EnumField);
}
}
class Program
{
static void Main(string[] args)
{
var factory = Fluently.Configure().Mappings(x => x.FluentMappings.AddFromAssemblyOf<Entity>())
.ExposeConfiguration(config => new SchemaExport(config).Create(false, true))
.Database(MsSqlConfiguration.MsSql2008.ConnectionString("Data Source=.;Initial Catalog=nhtest;Integrated Security=True"))
.BuildSessionFactory();
using (var session = factory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
var entity = new Entity();
entity.EnumField = SomeEnum.Value2;
session.Save(entity);
transaction.Commit();
}
}
}
}
In such case it is stored as strings in data base. If you want it be saved as integers, you need to change mapping for Enum field property to the following:
Map(x => x.EnumField).CustomType<int>();
Why are you creating enum types in progres? isn't this a maintenance-nightmare?
What does your configuration look like? Have you already tried using the conventions discribed here?
Looks like a simple casting error, so please consider using CustomType() / CustomSQLType() inside your mapping.
Simply add this class to your project:
public class PgEnumMapper<T> : NHibernate.Type.EnumStringType<T>
{
public override NHibernate.SqlTypes.SqlType SqlType
{
get { return new NHibernate.SqlTypes.SqlType(System.Data.DbType.Object); }
}
}
Then, you can use:
Map(x => x.status).CustomType<PgEnumMapper<PublicationStatuses>>();

Fluent Nhibernate: ignore interfaces/abstract classes during BuildSchema or UpdateSchema

Edit
I've got an interface that I'd like to base some classes on. When I go to BuildSchema (or UpdateSchema), Nhibernate creates a table for the interface. I thought I found a work-around as follows:
The Interface and Classes:
public interface ILevel
{
int Id { get; set; }
string Name { get; set; }
}
public abstract class Level : ILevel
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class LevelOne : Level { }
public class LevelTwo : Level { }
The Mappings:
public class LevelMap : ClassMap<ILevel>
{
public LevelMap()
{
Id(x => x.Id);
Map(x => x.Name);
}
}
public class LevelOneMap : SubclassMap<LevelOne>
{
}
public class LevelTwoMap : SubclassMap<LevelTwo>
{
}
This doesn't work (I did another UpdateSchema and the pesky ILevel table appeared).
Is there a configuration I'm unaware of to ignore interfaces/abstract classes altogether?
For seperate tables LevelOne and LevelTwo, that both have the properties of Level/ILevel, use a generic mapping for Level:
public abstract class LevelMap<T> : ClassMap<T>
where T : Level // ILevel should work, too
{
protected LevelMap()
{
Id(x => x.Id);
Map(x => x.Name);
}
}
public class LevelOneMap : LevelMap<LevelOne>
{
}
public class LevelTwoMap : LevelMap<LevelTwo>
{
}
You provide two separate class mappings without telling Fluent the real class hierarchy.

NHibernate bi-directional association

I am trying to model a parent/child association where a Parent class (Person) owns many instances of a child class (OwnedThing) - I want the OwnedThing instances to be saved automatically when the Person class is saved, and I want the association to be bi-directional.
public class Person
{
public class MAP_Person : ClassMap<Person>
{
public MAP_Person()
{
this.Table("People");
this.Id(x => x.ID).GeneratedBy.GuidComb().Access.BackingField();
this.Map(x => x.FirstName);
this.HasMany(x => x.OwnedThings).Cascade.AllDeleteOrphan().KeyColumn("OwnerID").Inverse();
}
}
public virtual Guid ID { get; private set; }
public virtual string FirstName { get; set; }
public virtual IList<OwnedThing> OwnedThings { get; set; }
public Person()
{
OwnedThings = new List<OwnedThing>();
}
}
public class OwnedThing
{
public class MAP_OwnedThing : ClassMap<OwnedThing>
{
public MAP_OwnedThing()
{
this.Table("OwnedThings");
this.Id(x => x.ID).GeneratedBy.GuidComb().Access.BackingField();
this.Map(x => x.Name);
this.References(x => x.Owner).Column("OwnerID").Access.BackingField();
}
}
public virtual Guid ID { get; private set; }
public virtual Person Owner { get; private set; }
public virtual string Name { get; set; }
}
If I set Person.OwnedThings to Inverse then the OwnedThing instances are not saved when I save the Person. If I do not add Inverse then the save is successful but person.OwnedThings[0].Owner is always null after I retrieve it from the DB.
UPDATE
When saving the data NHibernate will set the single association end in the database because it is set via the many-end of the association, so when I retrieve the OwnedThing from the DB it does have the link back to the Person set. My null reference was from Envers which doesn't seem to do the same thing.
Am I understanding you correctly that your problem only occur on "history" entities read by nhibernate envers?
If so, it might be caused by this bug
https://nhibernate.jira.com/browse/NHE-64
The workaround for now is to use Merge instead of (SaveOr)Update.
OwnedThings[0].Owner is most likely null because you are not setting it when you do the add. When using bidirectional relationships you have to do something like the below:
Person person = new Person();
OwnedThing pwnedThing = new OwnedThing();
pwnedThing.Owner = person;
person.OwnedThings.Add(pwnedThing);
If you do not explicity set the pwnedThing.Owner and you query that same object in the same ISession that you created it on it will be null. Typically I have add or remove methods that do this "extra" work for me. Take the below example:
public class Order : Entity
{
private IList<OrderLine> orderLines;
public virtual IEnumerable<OrderLine> OrderLines { get { return orderLines.Select(x => x); } }
public virtual void AddLine(OrderLine orderLine)
{
orderLine.Order = this;
this.orderLines.Add(orderLine);
}
public virtual void RemoveLine(OrderLine orderLine)
{
this.orderLines.Remove(orderLine);
}
}
public class OrderMap : ClassMap<Order>
{
public OrderMap()
{
DynamicUpdate();
Table("ORDER_HEADER");
Id(x => x.Id, "ORDER_ID");
HasMany(x => x.OrderLines)
.Access.CamelCaseField()
.KeyColumn("ORDER_ID")
.Inverse()
.Cascade.AllDeleteOrphan();
}
}

Why doesn't this class hierarchy fluent mapping work?

What's wrong with my mapping shown below? Is this a problem with GeneratedBy.Foreign()? How should I use it cause my PK in UserTable(UID) is also the FK which refers to PersonTable PK(PID). I get the Duplicate class/entity mapping consoleMappingTest.SystemUser error. what do you suggest(be sure to look at database structure- no way to change it). thanks.
Inheritance structure:
public class Person
{
public virtual int ID { get; set; }
}
public class User:Person
{
public override int ID
{
get
{
return base.ID;
}
set
{
base.ID = value;
}
}
public virtual string Name { get; set; }
public virtual int Salary { get; set; }
}
public class SystemUser:User
{
public virtual int Password { get; set; }
}
Database structure:
for saving some info about person(some fields not shown here):
PersonTable(PID)
for saving User and all it's subclasses like system user:
UserTable(UID,Name,Salary,Type)
and here is my mapping:
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Table("PersonTable");
Id(x => x.ID, "PID").GeneratedBy.Assigned();//or HiLo-not important
}
}
public class UserMap : ClassMap<User>
{
public UserMap()
{
Table("UserTable");
DiscriminateSubClassesOnColumn("Type").Default("U");
Id(x => x.ID, "UID").GeneratedBy.Foreign("Person");//how should use this?
Map(x => x.Salary);
Join("PTable", j =>
{
j.KeyColumn("UID");
j.Map(x => x.Name);
});
}
}
public class SystemUserMap : SubclassMap<SystemUser>
{
public SystemUserMap()
{
DiscriminatorValue("SU");
Map(x => x.Password);
}
}
Foreign("") is meant to point to a Reference (Property with another mapped entity) from which the Id should be retrieved. You don't have a Reference to class Person named Person so you can't use it like this.
you already asked the same question with an answer. I know i didn't do it right first shot but would be nice if you told me what doesnt work with the latest edit or you dont like the solution befor asking the same question again

Problem with Fluent nhibernate

i have written a code like this
public class person
{
public person()
{
public virtual int Id { get; set; }
public virtual string Code { get; set; }
}
}
public class Child : Person
{
public person()
{
public virtual string Name{ get; set; }
public virtual string Lastname{ get; set; }
}
}
public class Book
{
public virtual int Id { get; set; }
public virtual string Name {get;set;}
}
and my Mapper classes is like these
public class PersonMapping : ClassMap<Person>
{
public PersonMapping()
{
Table("tblPersons");
Id(x => x.Id).GeneratedBy.Native();
Map(p => p.Code);
JoinedSubClass<Child>("Id", MapChild);
}
public static void MapChild(JoinedSubClassPart<Child> child)
{
child.Table("tblChilds");
child.Map(p => p.Name);
child.Map(p => p.Lastname);
child.HasMany(x => x.Relatives);
}
}
public class RelativeMapping : ClassMap<Relative>
{
public RelativeMapping()
{
Table("tblRelatives");
Id(x => x.Id);
Map(p => p.Name);
References(x => x.Child).Column("ChildId");
}
}
this is Configuration
Assembly assm = Assembly.Load("BLL");
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(c => c
.FromAppSetting("ConnStr"))
.Cache(c => c
.UseQueryCache()
.ProviderClass<HashtableCacheProvider>())
.ShowSql())
.Mappings(m => m.FluentMappings.AddFromAssembly(assm))
.BuildSessionFactory();
and this is the code of delete
public void Delete<T>(T obj)
{
ISessionFactory fact = FluentConfiguration.CreateSessionFactory();
ISession session = fact.OpenSession();
session.Delete(obj);
session.Flush();
}
my problem : when i wanna delete a Child it messages
" ILLEGAL ATTEMPT TO ASSOCIATE WITH TWO OPEN SESSION "
please help me
The error tells you the problem, and it is not with your entities or your mapping.
You have two or more open sessions and you're attempting to associate some entity with more than one of them.
update
In response to the updated code, I see that you have a method that accepts an entity as a parameter, creates a new session factory, creates a new session, and then tries to delete the entity.
There are some problems here:
You should only create the session factory once. Ever. This is an expensive operation.
You are passing the entity to the Delete() method. Where is this entity coming from? You've clearly already loaded it elsewhere in your application, using a different ISession. This is the crux of the problem. Unless you Evict() the entity from the first ISession (not recommended), trying to manipulate it with a different ISession will throw.
You're calling Flush() which should almost never be used.
You're using an implicit transaction.
You should really be deleting the entity with the same ISession with which it was loaded, and you should be performing work within a transaction, like this:
using(var transaction = session.BeginTransaction())
{
session.Delete(obj);
transaction.Commit();
}