We're using FluentNHibernate and we have run into a problem where our object model requires data from two tables like so:
public class MyModel
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual int FooId { get; set; }
public virtual string FooName { get; set; }
}
Where there is a MyModel table that has Id, Name, and FooId as a foreign key into the Foo table. The Foo tables contains Id and FooName.
This problem is very similar to another post here: Nhibernate: join tables and get single column from other table but I am trying to figure out how to do it with FluentNHibernate.
I can make the Id, Name, and FooId very easily..but mapping FooName I am having trouble with. This is my class map:
public class MyModelClassMap : ClassMap<MyModel>
{
public MyModelClassMap()
{
this.Id(a => a.Id).Column("AccountId").GeneratedBy.Identity();
this.Map(a => a.Name);
this.Map(a => a.FooId);
// my attempt to map FooName but it doesn't work
this.Join("Foo", join => join.KeyColumn("FooId").Map(a => a.FooName));
}
}
with that mapping I get this error:
The element 'class' in namespace 'urn:nhibernate-mapping-2.2' has invalid child element 'join' in namespace 'urn:nhibernate-mapping-2.2'. List of possible elements expected: 'joined-subclass, loader, sql-insert, sql-update, sql-delete, filter, resultset, query, sql-query' in namespace 'urn:nhibernate-mapping-2.2'.
any ideas?
I think you misunderstood something here.
Instead of having Id, Name, FooId and FooName in same class you need to create two classes
public class MyModel
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Foo Foo { get; set; }
}
public class Foo
{
public virtual int Id { get; set; }
public virtual string FooName { get; set; }
}
And your mapping classes for these:
public class MyModelMapping : ClassMap<MyModel>
{
public MyModelMapping()
{
this.Id(x => x.Id);
this.Map(x => x.Name);
this.References(x => x.Foo);
}
}
public class FooMapping : ClassMap<Foo>
{
public FooMapping()
{
this.Id(x => x.Id);
this.Map(x => x.FooName);
}
}
this should help.
But remember Convention other Configuration :)
Related
I'm having an issue trying to map a property to another table via a join but with extra criteria. My Code below is for the class I am trying to join from, I basically want to join ServerEventLog property by joining on a TestLog table but searching for a specific Message type id in the process which is defined in the test log table.
public class ExecutedTest
{
public virtual int Id { get; set; }
public virtual TestLog TestLog { get; set; }
public virtual TestLog ServerEventLog { get; set; }
}
And then the test log class is the class i want to map to
public class TestLog
{
public virtual int ID { get; set; }
public virtual string Message { get; set; }
public virtual int ExecutedTestId { get; set; }
}
I can get the SQL to generate something like the following using the class map below.
SELECT ...
FROM [ExecutedTest] executedte0_
inner join TestLog executedte0_1_ on
executedte0_.Id=executedte0_1_.ExecutedTestId
WHERE executedte0_.Id=?
public class ExecutedTestMap : ClassMap<ExecutedTest>
{
public ExecutedTestMap()
{
Id(x => x.Id);
References(x => x.TestLog).Column("LogId").Cascade.All();
this.Join(
"TestLog",
x =>
{
x.KeyColumn("ExecutedTestId");
});
}
}
But what I can't work out is how I get sql to be generated like the following through the join criteria (have highlighted the bit i cant generate inbetween the stared bit).
SELECT ...
FROM [ExecutedTest] executedte0_
inner join TestLog executedte0_1_ on
executedte0_.Id=executedte0_1_.ExecutedTestId
**** and executedte0_1_.MessageTypeId = 9 ****
WHERE executedte0_.Id=?
Any help would be greatly appreciated if someone knows how to achieve this through the ClassMap. Cheers
That's not what Join() is meant for. You could devide Testlog into
public virtual EventLog ServerEventLog { get; set; }
public class TestLog
{
public virtual int ID { get; set; }
public virtual string Message { get; set; }
}
public class EventLog : TestLog { }
public class TestLogMap : ClassMap<TestLog>
{
public TestLogMap()
{
Id(x => x.Id);
Map(x => x.Message, "Message");
DiscriminateSubclassesOnColumn("MessageTypeId", 1 /*MessageTypeId of TestLog*/);
}
}
public class EventLogMap : ClassMap<EventLog>
{
public EventLogMap()
{
DiscriminatorValue(9 /*MessageTypeId of EventLog*/);
}
}
or change the ServerEventLog to a Collection
// in ExecutedTestMap
HasMany(x => x.ServerEventLogs)
.KeyColumn("ExecutedTestId")
.Where("ExecutedTestId")
.KeyColumn("MessageTypeId = 9");
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");
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