a tricky problem - please bear with me. Any help greatly appreciated.
I have a table/class Contact (PK Id) and two derived Client and Debtor (PK and FK ContactId). The 4th table Case has foreign keys to Debtor and Client (mappings below).
Everything worked fine at first. But then I hit some data where the same Contact is a Client in one Case but a Debtor in another. If those are read in one nhibernate query like Session.Query<Case>().Fetch(c => c.Debtor).Fetch(c => c.Client)
there is a
NHibernate.WrongClassException
"Object with id: {someGuid...} was not of the specified subclass: Client
(loading object was of wrong class [Debtor])
Seems like the session first level cache is recognizing the record by it's Id and tries to avoid reading the data from the sql result set. Of course the cast NH thinks is necessary for the reuse fails.
Unfortunately changing the DB schema is not an option. It's a legacy system. (an the schema is ok and clean IMO)
Don't know if it is important: The class Contact is not abstract. There are Contacts used who are neither Client nor Debtor.
Is there any chance of getting this to work with these multi-role-contacts? Thanks in advance.
public partial class ContactMap : ClassMap<Contact>
{
public ContactMap()
{
Id(x=>x.Id).GeneratedBy.Guid();
Map(x=>x.FirstName);
Map(x=>x.Name1).Not.Nullable();
...
}
}
public class DebtorMap : SubclassMap<Debtor>
{
public DebtorMap()
{
KeyColumn("ContactID");
Table("[dbo].[Debtor]");
Map(x => x.MaritalStatus);
...
}
}
public partial class ClientMap : SubclassMap<Client>
{
public ClientMap()
{
KeyColumn("ContactID");
Map(x => x.ClientNo).Not.Nullable();
...
}
}
public partial class CaseMap : ClassMap<Case>
public CaseMap()
{
...
References<Client>(x=>x.Client)
References<Debtor>(x=>x.Debtor)
...
}
If you can add a view to the schema, you can create a view called Roles which unions both Client and Debtor records. You can then change your object model to represent roles:
class Contact
{
public virtual Guid Id { get; set; }
public virtual string FirstName { get; set; }
public virtual ICollection<Role> Roles { get; private set; }
}
class Role
{
public virtual Guid Id { get; set; }
}
class Client : Role
{
public virtual string ClientNo { get; set; }
}
class Debtor : Role
{
public virtual string MaritalStatus { get; set; }
}
class ContactMap : FluentNHibernate.Mapping.ClassMap<Contact>
{
public ContactMap()
{
Table("dbo.Contacts");
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.FirstName);
HasMany(x => x.Roles)
.KeyColumn("ContactId")
.Not.LazyLoad()
.Fetch.Join();
}
}
class RoleMap : FluentNHibernate.Mapping.ClassMap<Role>
{
public RoleMap()
{
Table("dbo.Roles");
Id(x => x.Id).GeneratedBy.GuidComb();
this.Polymorphism.Implicit();
}
}
class ClientMap : FluentNHibernate.Mapping.SubclassMap<Client>
{
public ClientMap()
{
Table("dbo.Clients");
KeyColumn("Id");
Map(x => x.ClientNo);
}
}
class DebtorMap : FluentNHibernate.Mapping.SubclassMap<Debtor>
{
public DebtorMap()
{
Table("dbo.Debtors");
KeyColumn("Id");
Map(x => x.MaritalStatus);
}
}
Since the Contact table does now share a PK with Client and Debtor tables this should work. The Roles view would look something like this:
create view dbo.Roles as
select
Id,
ContactId
from dbo.Clients
union all
select
Id,
ContactId
from dbo.Debtors
Related
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();
}
}
as the title says, I would like to create a many-to-one relationship using Fluent NHibernate. There are GroupEntries, which belong to a Group. The Group itself can have another Group as its parent.
These are my entities:
public class GroupEnty : IGroupEnty
{
public virtual long Id { get; set; }
public virtual string Name { get; set; }
...
public virtual IGroup Group { get; set; }
}
public class Group : IGroup
{
public virtual long Id { get; set; }
public virtual string Name { get; set; }
...
public virtual IGroup Parent { get; set; }
}
And these are the mapping files:
public class GroupEntryMap : ClassMap<GroupEntry>
{
public GroupEntryMap()
{
Table(TableNames.GroupEntry);
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.Name).Not.Nullable();
...
References<Group>(x => x.Group);
}
}
public class GroupMap : ClassMap<Group>
{
public GroupMap()
{
Table(TableNames.Group);
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.Name).Not.Nullable();
...
References<Group>(x => x.Parent);
}
}
With this configuration, Fluent NHibernate creates these tables:
GroupEntry
bigint Id string Name ... bigint Group_id
Group
bigint Id string Name ... bigint Parent_id bigint GroupEntry_id
I don't know why it creates the column "GroupEntry_id" in the "Group" table. I am only mapping the other side of the relation. Is there an error in my configuration or is this a bug?
The fact that "GroupEntry_id" is created with a "not null" constraint gives me a lot of trouble, otherwise I would probably not care.
I'd really appreciate any help on this, it has been bugging me for a while and I cannot find any posts with a similar problem.
Edit: I do NOT want to create a bidirectional association!
If you want a many-to-one where a Group has many Group Entries I would expect your models to look something like this:
public class GroupEntry : IGroupEntry
{
public virtual long Id { get; set; }
public virtual string Name { get; set; }
...
public virtual IGroup Group { get; set; }
}
public class Group : IGroup
{
public virtual long Id { get; set; }
public virtual string Name { get; set; }
...
public virtual IList<GroupEntry> GroupEntries { get; set; }
public virtual IGroup Parent { get; set; }
}
Notice that the Group has a list of its GroupEntry objects. You said:
I don't know why it creates the column "GroupEntry_id" in the "Group" table. I am only mapping the other side of the relation.
You need to map both sides of the relationship, the many side and the one side. Your mappings should look something like:
public GroupEntryMap()
{
Table(TableNames.GroupEntry);
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.Name).Not.Nullable();
...
References<Group>(x => x.Group); //A GroupEntry belongs to one Group
}
}
public class GroupMap : ClassMap<Group>
{
public GroupMap()
{
Table(TableNames.Group);
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.Name).Not.Nullable();
...
References<Group>(x => x.Parent);
//A Group has-many GroupEntry objects
HasMany<GroupEntry>(x => x.GroupEntries);
}
}
Check out the fluent wiki for more examples.
The solution was that I accidentally assigned the same table name for two different entities... Shame on me :(
Thanks a lot for the input though!
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
I have a class Client which has a attribute of dogs
public class ClientsMap : ClassMap<Clients>
{
public ClientsMap()
{
Id(x => x.ClientID);
HasMany(x => x.Dogs);
}
}
public class Client
{
public virtual IList<Dog> Dogs { get; set; }
public virtual int ClientID { get; set; }
}
and a class of dog that references client.
public class Dog
{
public virtual Clients Client { get; private set; }
public virtual int Id { get; set; }
}
public class DogMap : ClassMap<Dog>
{
public DogMap()
{
Table("Pooches");
Id(x => x.Id);
References(x => x.Client).Column("ClientId");
}
}
Because I am mapping on to an existing DB i cannot change the field names.
When I try and return the dogs collection I am getting an invalid column error on client_id with the SQL
SELECT
dogs0_.Clients_id as Clients3_1_,
dogs0_.Id as Id1_,
dogs0_.Id as Id1_0_,
dogs0_.ClientId as ClientId1_0_
FROM
pooches dogs0_
How can I make this use clientid over cliet_id. I thought I specified this in the dogs map.
You should also specify the column name on the one to many relationship.
HasMany(x => x.Dogs)
.KeyColumn("ClientId");
I have the following tables in my database:
Announcements:
- AnnouncementID (PK)
- Title
AnouncementsRead (composite PK on AnnouncementID and UserID):
- AnnouncementID (PK)
- UserID (PK)
- DateRead
Users:
- UserID (PK)
- UserName
Usually I'd map the "AnnouncementsRead" using a many-to-many relationship but this table also has an additional "DateRead" field.
So far I have defined the following entities:
public class Announcement
{
public virtual int AnnouncementID { get; set; }
public virtual string Title { get; set; }
public virtual IList<AnnouncementRead> AnnouncementsRead { get; private set; }
public Announcement()
{
AnnouncementsRead = new List<AnnouncementRead>();
}
}
public class AnnouncementRead
{
public virtual Announcement Announcement { get; set; }
public virtual User User { get; set; }
public virtual DateTime DateRead { get; set; }
}
public class User
{
public virtual int UserID { get; set; }
public virtual string UserName { get; set; }
public virtual IList<AnnouncementRead> AnnouncementsRead { get; private set; }
public User()
{
AnnouncementsRead = new List<AnnouncementRead>();
}
}
With the following mappings:
public class AnnouncementMap : ClassMap<Announcement>
{
public AnnouncementMap()
{
Table("Announcements");
Id(x => x.AnnouncementID);
Map(x => x.Title);
HasMany(x => x.AnnouncementsRead)
.Cascade.All();
}
}
public class AnnouncementReadMap : ClassMap<AnnouncementRead>
{
public AnnouncementReadMap()
{
Table("AnnouncementsRead");
CompositeId()
.KeyReference(x => x.Announcement, "AnnouncementID")
.KeyReference(x => x.User, "UserID");
Map(x => x.DateRead);
}
}
public class UserMap : ClassMap<User>
{
public UserMap()
{
Table("Users");
Id(x => x.UserID);
Map(x => x.UserName);
HasMany(x => x.AnnouncementsRead)
.Cascade.All();
}
}
However when I run this I receive the following error:
"composite-id class must override Equals(): Entities.AnnouncementRead"
I'd appreciate it if someone could point me in the right direction. Thanks
You should do just what NHibernate is telling you. AnnouncementRead should override Equals and GetHashCode methods. They should be based on fields that are part of primary key
When implementing equals you should use instanceof to allow comparing with subclasses. If Hibernate lazy loads a one to one or many to one relation, you will have a proxy for the class instead of the plain class. A proxy is a subclass. Comparing the class names would fail.
More technically: You should follow the Liskows Substitution Principle and ignore symmetricity.
The next pitfall is using something like name.equals(that.name) instead of name.equals(that.getName()). The first will fail, if that is a proxy.
http://www.laliluna.de/jpa-hibernate-guide/ch06s06.html