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
Related
I have a base class that looks like this:
public abstract class MyBaseClass
{
public virtual DateTime UpdatedOn { get; set; }
}
I then have a series of other entities that inherit from this:
public class User : MyBaseClass
{
public virtual string UserName { get; set; }
public virtual string Password { get; set; }
}
My mapping for User would be:
public class UserMap : ClassMap<User>
{
public UserMap()
{
Id(x => x.UserName);
Map(x => x.Password);
Map(x => x.UpdatedOn);
}
}
I then have other entities, mapped the same way.
My problem is I get the following error:
Tried to add property 'UpdatedOn' when already added.
I guess this is because I map the UpdatedOn column in every entity?
Each of my tables has this UpdatedOn column, so how should I be mapping it?
Use a derived class in fluent nHibernate
This question should help you out. You can basically use a base fluent nhibernate mapping class to map your UpdatedOn column and derive all of your other mapping classes from that base class.
I had similar kind of situation. I did like this..it works...
I was supposed to exposes entities as Interface..
public interface IEntity
{
DateTime CreationDate { get; set; }
DateTime UpdationDate { get; set; }
}
public interface IUser : IEntity
{
DateTime UserName { get; set; }
DateTime Password { get; set; }
}
public interface IEmployee : IEntity
{
DateTime Name { get; set; }
DateTime Key { get; set; }
}
public abstract class Entity : IEntity
{
public virtual DateTime CreationDate { get; set; }
public virtual DateTime UpdationDate { get; set; }
}
public class User : Entity, IUser
{
public virtual string UserName { get; set; }
public virtual string Password { get; set; }
}
public class Employee : Entity, IEmployee
{
public virtual string Name { get; set; }
public virtual string Key { get; set; }
}
public class UserMap : ClassMap<User>
{
public UserMap()
{
Id(x => x.UserName);
Map(x => x.Password);
Map(x => x.CreationDate);
Map(x => x.UpdationDate);
}
}
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Id(x => x.Name);
Map(x => x.Key);
Map(x => x.UpdationDate);
Map(x => x.CreationDate);
}
}
I try to query data using FluentNhibernate and I get this error: "Sequence contains more than one matching element"
Here are my classes and mappings:
public class Course
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual IList<Instructor> Instructors { get; set; }
}
public class Instructor
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual ImageData Portrait { get; set; }
public virtual ImageData PortraitThumb { get; set; }
public virtual IList<Course> TeachingCourses { get; private set; }
}
public class ImageData : Entity
{
public virtual int Id { get; private set; }
public virtual byte[] Data { get; set; }
}
public class CourseMap : ClassMap<Course>
{
public CourseMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasManyToMany(x => x.Instructors)
.Cascade.All()
.Table("CourseInstructor");
}
}
public class InstructorMap : ClassMap<Instructor>
{
public InstructorMap()
{
Id(x => x.Id);
Map(x=> x.Name);
References(x => x.Portrait)
.Nullable()
.Cascade.All();
References(x => x.PortraitThumb)
.Nullable()
.Cascade.All();
HasManyToMany(x => x.TeachingCourses)
.Cascade.All()
.Inverse()
.Table("CourseInstructor");
}
}
public class ImageDataMap : ClassMap<ImageData>
{
public ImageDataMap()
{
Id(x => x.Id);
Map(x => x.Data);
}
}
Then I try to get data using below code:
var course = session.CreateCriteria(typeof(Course))
.SetFetchMode("Instructors", FetchMode.Eager)
.SetFetchMode("Instructors.Portrait", FetchMode.Eager)
.SetFetchMode("Instructors.PortraitThumb", FetchMode.Eager)
.List<Course>();
But I get the following error: "Sequence contains more than one matching element"
Also, when I try this
var course = session.CreateCriteria(typeof(Course))
.SetFetchMode("Instructors", FetchMode.Eager)
.SetFetchMode("Instructors.Portrait", FetchMode.Eager)
.SetFetchMode("Instructors.PortraitThumb", FetchMode.Eager)
.SetResultTransformer(new DistinctRootEntityResultTransformer())
.List<Course>();
No error occurs but I get duplicate Instructor objects.
I did try below posts and some others as well. But it doesn't help.
NHibernate Eager loading multi-level child objects
Eager Loading Using Fluent NHibernate/Nhibernate & Automapping
FluentNhibernate uses a bag-mapping for many-to-many relations, if the mapped property is of type IList.
A bag mapping has a few major drawbacks Performance of Collections / hibernate. The one that currently bites you is that NH does not permit duplicate element values and, as they have no index column, no primary key can be defined.
Simply said NH does not know to which bag do they belong to when you join them all together.
Instead of a bag I would use a indexed variant a set, assuming that an Instructor does not has the same persistent Course assigned twice.
You can fix your query results by amending your domain classes, this tells FluentNhibernate to use a set instead of a bag by convention:
public class Course
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual Iesi.Collections.Generic.ISet<Instructor> Instructors { get; set; }
}
public class Instructor
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual ImageData Portrait { get; set; }
public virtual ImageData PortraitThumb { get; set; }
public virtual Iesi.Collections.Generic.ISet<Course> TeachingCourses { get; private set; }
}
In addition you can amend your mapping by using .AsSet(). FluentNHibernate: What is the effect of AsSet()?
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!
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");
My Domain:
public class Person
{
public Person() { }
public virtual int PersonId { get; set; }
public virtual string Title { get; set; }
public virtual string FirstName { get; set; }
public virtual IList<Address> Addresses { get; set; }
}
public class Address
{
public Address()
{}
public virtual int AddressId { get; set; }
public virtual Person AddressPerson { get; set; }
public virtual string BuildingNumber { get; set; }
public virtual string AddressLine1 { get; set; }
}
My Mapping:
public class AddressMap : ClassMap<Address>
{
public AddressMap()
{
Table("Address");
LazyLoad();
Id(x => x.AddressId).GeneratedBy.Identity();
References(x => x.AddressPerson).Column("PersonId").Not.Nullable();
Map(x => x.BuildingNumber).Length(250).Not.Nullable();
Map(x => x.AddressLine1).Length(100).Not.Nullable();
}
}
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Table("Person");
LazyLoad();
Id(x => x.PersonId).Column("PersonId").GeneratedBy.Identity();
Map(x => x.Title).Length(6).Nullable();
Map(x => x.FirstName).Length(100).Not.Nullable();
HasMany(x => x.Addresses).KeyColumn("PersonId");
HasMany(x => x.Applications).KeyColumn("PersonId");
}
}
So when I attempt to add an address to the person list and save I get the following error:
object references an unsaved transient
instance - save the transient instance
before flushing. Type:
Rise.Core.Domain.Address, Entity:
Rise.Core.Domain.Address
I am new to NHibernate and I am a little confused as to what exactly is going on. I beleive I need to Create a BiDirectional attribute Or should I just be saving the address myself after I have saved the person ID session.SaveOrUpdate(Person) and then session.SaveOrUpdate(Address)? Not sure what exactly I am doing wrong, I do like having the list of address on Person that can be lazy loaded as it makes it really easy to write some Linq.
Any suggestions?
I believe the error came up since you tried to save an Address before saving the Person to whom it belonged.
I think you should
1) Save Person P with empty list of <IList> Addreses
2) Save Address A after adding this Person P as AddressPerson of A
3) Add Address A to <IList> Addresses of Person P
Found a post on stackoverflow about this! I just need to add
.KeyColumn("PersonId")
.Inverse().Cascade.SaveUpdate();