Using Fluent NHibernate I need a clue how to map my Invoice class.
public class Buyer
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string TaxRegNo { get; set; }
// .... more properties....
}
public class Invoice
{
public virtual int Id { get; set; }
public virtual int IdBuyer { get; set; }
public virtual Buyer Buyer { get; set; }
// ....more properties
}
The problem is that I want to have in Invoice class:
BuyerId - just an integer ID for reference and foregin key relationship
a copy of almost all buyer properties (its accounting document and properties cannot be changed after confirmation) - as component
I tried to this using following mapping but it doesn't work
public InvoiceMap()
{
Id(x => x.Id);
References(x => x.IdBuyer);
Component(x => x.Buyer, BuyerMap.WithColumnPrefix("buyer_"));
// ....more properties
}
You would normally not map both the foreign key and the child object. If you do map both, then do this in the mapping (or similar):
References(x => x.Buyer);
Map(x => x.IdBuyer).Column("BuyerId").Not.Insert().Not.Update();
Then you don't double up on the column name in SQL statements, which causes errors around mismatched numbers of parameters.
Related
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
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 am puzzled and frustrated by an exception I'm getting via NHibernate. I apologize for the length of this post, but I've tried to include an appropriate level of detail to explain the issue well enough to get some help!
Here's the facts:
I have a Person class which contains a property BillingManager, which is also a Person type. I map this as an FNH "Reference".
I have an ExpenseReport class which contains a property SubmittedBy, which is a Person type. I map this as an FNH "Reference".
I have a BillableTime class which contains a property Person, which is a Person type. I map this as an FNH "Reference".
Person contains a collection (IList) of ExpenseReport types (property ExpenseReports)
Person contains a collection (IList) of BilledTime types (property Time)
(See classes and mappings at bottom of post.)
All was cool until I added the IList<BilledTime> Time collection to Person. Now, when I try to access _person.Time, I get an exception:
The code:
// Get billable hours
if (_person.Time == null ||
_person.Time.Count(x => x.Project.ProjectId == project.ProjectId) == 0)
{
// No billable time for this project
billableHours = Enumerable.Repeat(0F, 14).ToArray();
}
The exception:
could not initialize a collection:
[MyApp.Business.Person.Time#211d3567-6e20-4220-a15c-74f8784fe47a]
[SQL: SELECT
time0_.BillingManager_id as BillingM8_1_,
time0_.Id as Id1_,
time0_.Id as Id1_0_,
time0_.ReadOnly as ReadOnly1_0_,
time0_.DailyHours as DailyHours1_0_,
time0_.Week_id as Week4_1_0_,
time0_.Person_id as Person5_1_0_,
time0_.Project_id as Project6_1_0_,
time0_.Invoice_id as Invoice7_1_0_
FROM [BillableTime] time0_
WHERE time0_.BillingManager_id=?]
It's true that BillingManager_id is an invalid column name, it doesn't exist in the BillableTime table. However, I don't understand why NHB has created this SQL... doesn't make sense to me. I have seen this "Invalid column name" exception a lot when searching for a solution, but nothing seems to work. Even more confusing: like BilledTime, the ExpenseReport type also contains a reference to Person and it works perfectly.
One thing I was able to figure out is that if I remove the BillingManager reference from the Person mapping (References(p => p.BillingManager)), the exception goes away and things seem to work (with respect to BillableTime; it of course breaks the BillingManager persistence). Now it seems like there is some "self-reference" problem, since the Person.BillingManager property is itself a reference to a Person.
Any idea what is going on here? I'm at a loss...
Thanks.
=== Classes & Mappings ===
public class Person
{
public virtual string LastName { get; set; }
public virtual string FirstName { get; set; }
public virtual Person BillingManager { get; set; }
public virtual IList<ExpenseReport> ExpenseReports { get; set; }
public virtual IList<BillableTime> Time { get; set; }
}
public class PersonMapping : ClassMap<Person>
{
public PersonMapping()
{
Id(p => p.UserId).GeneratedBy.Assigned();
Map(p => p.LastName).Not.Nullable();
Map(p => p.FirstName).Not.Nullable();
References(p => p.BillingManager);
HasMany(p => p.ExpenseReports).Cascade.AllDeleteOrphan();
HasMany(p => p.Time).Cascade.AllDeleteOrphan();
}
}
public class BillableTime
{
public virtual int Id { get; private set; }
public virtual Week Week { get; set; }
public virtual Person Person { get; set; }
public virtual Project Project { get; set; }
public virtual float[] DailyHours { get; set; }
public virtual Invoice Invoice { get; set; }
public virtual bool ReadOnly { get; set; }
}
public class BillableTimeMapping : ClassMap<BillableTime>
{
public BillableTimeMapping()
{
Id(x => x.Id);
References(x => x.Week);
References(x => x.Person);
References(x => x.Project);
References(x => x.Invoice);
Map(x => x.ReadOnly).Not.Nullable().Default("0");
Map(x => x.DailyHours).Length(28);
}
}
public class ExpenseReport
{
public virtual long Id { get; set; }
public virtual Person SubmittedBy { get; set; }
}
the following line should solve the issue, but i' dont know exactly why it is happening. if i have the spare time i will investigate.
HasMany(p => p.Time).Cascade.AllDeleteOrphan().KeyColumn("Person_Id");
I try to create unique index with fluent nhibernate. But when i use the following classes
tables are created like :
Person Table:
Id
Name
PersonStatistic Table:
Id
Date
Count
Person_Id
Because of this structure when i create the unique key, column order look like "Date - Person_Id". But i want to column order in key like "Person_Id - Date"
Entity and map classes like below.
Entity classes :
public class Person()
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
public class PersonStatistic()
{
public virtual Guid Id { get; set; }
public virtual DateTime Date { get; set; }
public virtual long? Count { get; set; }
public virtual Person Person { get; set; }
}
Map classes :
public PersonMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Name).Not.Nullable().Length(50);
}
public PersonStatisticMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Date).Not.Nullable().UniqueKey("UK_Person_Date");
Map(x => x.Count).Nullable();
References(x => x.Person)
.Not.Nullable()
.UniqueKey("UK_Person_Date");
}
Something is wrong in my classes or mapping or another trick to set column order in key?
Try putting the References call before the Map one.