NHibernate ternary association with multiple values - how to map in a nice way - nhibernate

I've asked a similar question, but I've given up on the idea I had there to solve this problem so I would like some help solving this in a neat way instead.
I've got tables
Image - (Id, Name, RelativeFilePath)
ImageFilter - (Id, Type)
ImageContext - (Id, Name, ...)
ImageContextImage - (Id, ImageContextId, ImageId, ImageFilterId)
Example of data:
ImageContextImage Id ImageContextId ImageId ImageFilterId
1 1 1 1
2 1 1 2
3 2 1 1
4 3 2 1
As you can see, an image in a context can have several filters applied.
All of my entities are very simple, except this mapping of the above. Currently I've got
ImageContext
public virtual int Id
public virtual string Name
public virtual IList<ImageContextImage> Images
ImageContextImage
public virtual int Id
public virtual ImageContext Context
public virtual Image Image
public virtual ImageFilter ImageFilter
The above is very easy to map, but for each image I then get multiple ImageContextImage objects. I would rather have ImageContextImage contain a list of ImageFilter, so that I can simply iterate through that collection. I've tried alot of permutations of AsTernaryAssociation() and it complains that I need a Dictionary, but I want multiple values per key! Any ideas?
Any ideas? Thanks!

Ternary association can be replaced with binary associations but a new entity will appear after the replacement (Removing Ternary relationship types). It is ImageContextImage entity in your case and the hard part is to find best name for such entity. Your example is very close to this replacement.
ImageContextImage entity:
public virtual int Id { get; set; }
public virtual Image Image { get; set; }
public virtual ImageContext Context { get; set; }
public virtual IList<ImageFilter> Filters { get; set; }
and it's mapping:
Id(x => x.Id);
References(x => x.Image);
References(x => x.Context);
HasManyToMany(x => x.Filters); // filters are referenced via many-to-many relation
ImageContext entity:
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<ImageContextImage> ImageContextImageList { get; set; }
and it's mapping:
Id(x => x.Id);
Map(x => x.Name);
HasMany(x => x.ImageContextImageList)
.Inverse(); // to aggregate all ImageContextImage that are fererencing this instnstance of ImageContext
.KeyColumn("Context_id");
Corresponding database schema is slightly different:
ImageContextImage(Id, Image_id, Context_id)
and new association table must be created for the many-to-many relation:
ImageFilterToImageContextImage(ImageContextImage_id, ImageFilter_id)
Note that this is only a sketch of one possible approach. Many details depends on your problem domain and must be tweaked before it is ready for production:) - e.g.cascades.
I have never used AsTernaryAssociation but it seems interesting. I will investigate it later, thank you for the inspiration:).
EDIT:
Ternary association can be implemented by slightly different mapping (using composite-element) but there is still additional entity - ImageContextImage which is mapped as component in this case:
public class ImageContext
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<ImageContextImage> ImageContextImageList { get; set; }
public ImageContext()
{
ImageContextImageList = new List<ImageContextImage>();
}
}
public class ImageContextMap : ClassMap<ImageContext>
{
public ImageContextMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasMany(x => x.ImageContextImageList).Component(c =>
{
c.References(x => x.Image);
c.References(x => x.Filter);
}).Cascade.AllDeleteOrphan();
}
}
public class ImageContextImage
{
public virtual Image Image { get; set; }
public virtual ImageFilter Filter { get; set; }
}
ImageContext class can be extended by these methods to make things simpler:
public virtual IEnumerable<Image> AssociatedImages
{
get
{
return ImageContextImageList.Select(x => x.Image).Distinct().ToList();
}
}
public virtual IEnumerable<ImageFilter> GetFilters(Image image)
{
return ImageContextImageList.Where(x => x.Image == image).Select(x => x.Filter).ToList();
}
No success with AsTernaryAssociation.

Related

Using References (many-to-one) in Fluent NHibernate creates a Foreign Key in both ends, the many end and the one-end

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!

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

NHibernate exception: could not initialize a collection, Invalid column name. Fluent mapping. Maybe a many-to-one issue?

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

NHibernate uni-directional associations

Playing around with Fluent NHibernate's Getting Started project. I tried to customize the example a bit, for a few reasons, among them elimination of circular reference for json serialization.
What I have done is to strip the Store and StoreMap classes of it's references back to Employee and Product classes. It now looks like this:
Store/StoreMap
public class Store
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
}
public StoreMap()
{
Id(x => x.Id);
Map(x => x.Name);
}
Employee/EmployeeMap
public class Employee
{
public virtual int Id { get; private set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual Store Store { get; set; }
}
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Id(x => x.Id);
Map(x => x.FirstName);
Map(x => x.LastName);
References(x => x.Store).Cascade.All();
}
}
Product/ProductMap
public class Product
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual double Price { get; set; }
public virtual IList<Store> StoresStockedIn { get; private set; }
public Product()
{
StoresStockedIn = new List<Store>();
}
public virtual void StockAt(Store store)
{
StoresStockedIn.Add(store);
}
}
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
Id(x => x.Id);
Map(x => x.Name);
Map(x => x.Price);
HasManyToMany(x => x.StoresStockedIn)
.Cascade.All()
.Table("StoreProduct");
}
}
I've moved "Cascade" operations into the Product and Employee instead of Store. From the testing I've done, both HasMany and HasManyToMany associations seem to be working okay.
My question is if it's the right approach. Whether or not it will lead to something that I have not anticipated.
as far as I know, it's standard practice in nHibernate to have both ends of a relationship in your model classes (actually, it usually makes sense from the business point of view).
Although you don't have to do it (your example, I think, should work fine).
If you don't want to expose one (or both) ends you can always define them as private or protected.
p.s concerning json serialization: it's not recommended to serialize your model entities. this is for a few reasons, the top ones being:
1. the data you display to the user may be different from what you have in your entities (for example- you might want to present an employee with their name, Id, their store name and number of products they own. it would be hard to do that using your model classes).
2. it's quite possible that you'd have uninitialized collections in your objects (if you use lazy-loading). These do not get serialized.
For serialization, DTOs is the recommended approach.

How do I create this reference mapping in Fluent NHibernate?

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.