FluentNhibernate Eager loading Many-To-Many child objects - nhibernate

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()?

Related

How do I use Fluent NHibernate ReferencesAny mapping?

I've read a lot about Fluent NHibernate's ReferencesAny but I haven't seen a complete example. I think I understand most of it, but there is one part I don't get. In the class mapping ReferencesAny(x => x.MemberName) is used to define the relationship to the one or more referenced classes. What is MemberName? How is it defined and how is it used to create the data in the database.
I have three tables, the records in one table can reference records in one of the other two tables. The first two are auto mapped, so the Id field is not specifically defined.
public class Household
{
public virtual string Name { get; set; }
public virtual IList<AddressXref> AddressXrefs { get; set; }
}
public class Client
{
public virtual string Name { get; set; }
public virtual IList<AddressXref> AddressXrefs { get; set; }
}
I'm not sure if the AddressXref table can be auto mapped. If so I need to find out how to do that too. For now I'll do it the conventional way with Fluent.
public class AddressXref
{
public virtual int id { get; set; }
public virtual string TableName { get; set; }
public virtual Int32 Table_id { get; set; }
public virtual string Street { get; set; }
public virtual string City { get; set; }
}
class AddressXrefMap : ClassMap<AddressXref>
{
public AddressXrefMap()
{
Table("AddressXref");
Id(x => x.id);
Map(x => x.TableName);
Map(x => x.Table_id);
Map(x => x.Street);
Map(x => x.City);
ReferencesAny(x => x.TableRef)
.AddMetaValue<Household>(typeof(Household).Name)
.AddMetaValue<Client>(typeof(Client).Name)
.EntityTypeColumn("TableName")
.EntityIdentifierColumn("Table_id")
.IdentityType<int>();
}
}
The part I need help with is how is the TableRef, referred to in ReferencesAny(), member of AddressXref defined in the class?
Also, how it is used in the code when creating data records? I image it will be similar to this:
Household Household = new Household();
Household.Name = "Household #1";
AddressXref AddrXref = new AddressXref();
AddrXref.Street1 = "123 Popular Street";
AddrXref.City = "MyTown";
AddrXref.TableRef = Household;
Session.SaveOrUpdate(AddrXref);
I love using Fluent with NHibernate, but I'm still amazed at the learning curve. :)
Thanks,
Russ
since both Household and Client don't share a base class other than object you have to declare it as this:
public class AddressXref
{
public virtual int Id { get; set; }
public virtual object TableRef { get; set; }
public virtual string Street { get; set; }
public virtual string City { get; set; }
}
and test it like this
if (addrXref.TableRef is HouseHold)
// it's a household

Simple mapping in fluent nhibernate

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");

Fluent Nhibernate - search for an item based on value of a many to many relationship

Hopefully the title of this question makes sense, if not, here is my elaboration.
With two entities, Brand and Affiliate and a many-to-may relationship between them i would like to be able to use a query to find the Affiliates where the BrandName is a variable value.
Here is the Affiliate class and Affiliate MapClass (simplified of course)
public class Affiliate
{
public virtual int Id { get; private set; }
public virtual DateTime DateReceived { get; set; }
public virtual IList<Brand> Brands { get; set; }
public Affiliate()
{
Brands = new List<Brand>();
}
}
public class AffiliateApplicationRecordMap : ClassMap<Affiliate>
{
public AffiliateApplicationRecordMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.DateReceived, "TimeStampCreated");
HasManyToMany(x => x.Brands)
.Cascade.All()
.ParentKeyColumn("AffiliateID")
.ChildKeyColumn("BrandID")
.Table("AffiliateBrand");
}
}
There is a mapping table called AffiliateBrand which provides the many to many mapping.
Here is the Brand class and ClassMap
public class Brand
{
public virtual int ID { get; private set; }
public virtual string Name { get; set; }
public virtual IList<Affiliate> Affiliates{ get; set; }
public Brand()
{
Affiliates = new List<Affiliate>();
}
public virtual void AddAffiliateApplication(Affiliate affiliate)
{
affiliate.Brands.Add(this);
Brands.Add(affiliate);
}
}
public class BrandMap : ClassMap<Brand>
{
public BrandMap()
{
Id(x => x.ID).GeneratedBy.Identity();
Map(x => x.Name);
HasManyToMany(x => x.Affiliates)
.Cascade.All()
.Inverse()
.ParentKeyColumn("BrandID")
.ChildKeyColumn("PartnerID")
.Table("AffiliateBrand");
}
}
Now i'm tyring to write this query with NHibernate:
var result = session
.CreateCriteria(typeof(Partner))
.AddOrder(Order.Asc("DateReceived"))
.Add(Restrictions.Eq("Brands.Name", brandName))
.SetMaxResults(10)
.List<Partner>();
Now clearly this isn't working and i didn't really think it would. What i'm trying to do is get all Affiliates back where the Brand has a specific name. How do i write this query?
You need to add a join to your criteria using CreateAlias
var result = session
.CreateCriteria(typeof(Partner))
.AddOrder(Order.Asc("DateReceived"))
.CreateAlias("Brands", "brand")
.Add(Restrictions.Eq("brand.Name", brandName))
.SetMaxResults(10)
.List<Partner>();

Fluent NHibnerate Domain mapping issue

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();

Composite Key/Id Mapping with NHibernate

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