My many-to-many relationship does not involve the standard "joining-table" approach, in which a table stores the "FK1-to-FK2" relationships.
Instead, I'm "loosely" joining to a legacy read-only view as follows:
Appointment class (based on the Appointment table)
int AppointmentId (PK)
int OrderId
List<LegacyOrder> LegacyOrders
LegacyOrder class (based on the LEGACY_ORDERS_VIEW view in our legacy system)
int OrderId (composite PK)
int VersionNumber (composite PK)
An Appointment can have many (versions of a) LegacyOrder.
A LegacyOrder can have many Appointments, but this relationship is not important in our application.
I want to populate the LegacyOrders property with all LegacyOrders for the specified OrderId.
My attempt at mapping is as follows:
<class name="Appointment" table="Appointments" lazy="true">
<bag name="Orders" table="LEGACY_ORDERS_VIEW" inverse="true">
<key column="OrderId" />
<many-to-many class="LegacyOrder" column="ORDER_ID" />
</bag>
</class>
....but I'm getting "could not execute query" exceptions due to invalid SQL.
I think the table referred to in the <bag> mapping should be the "joining table".... but I don't have one.
I'm fairly sure my mapping approach is fundamentally wrong.... what's the right way to go about it?
Edit:
Thanks Radim: perhaps a better name for LegacyOrder would be LegacyOrderVersion: each record in that view corresponds to a "version" of an order, rather than an order.
i.e. An order may be for 100 units, then when say 20 units are collected, another record is written with the same OrderId but for 80 units. (I did warn you it was legacy :)
If an Appointment (in the new system) can retrieve all related LegacyOrderVersions, then it can derive useful properties such as CurrentLegacyOrderVersion and OriginalLegacyOrderVersion.
FWIW: this works great for me:
<class name="Appointment" table="Appointments" lazy="true">
<bag name="Orders" inverse="true">
<key property-ref="OrderId" column="ORDER_ID" />
<one-to-many class="LegacyOrder" />
</bag>
</class>
One way how to solve this a bit challenging DB structure, could be with the property-ref feature. See more details here: 5.1.10. many-to-one, working even for our many-to-many scenario.
So firstly we have to map the property, which we will use as a reference:
<class name="Appointment" table="Appointments" lazy="true">
...
// the column name is coming from the Appointment table
<property name="OrderId" column="ORDER_ID" />
So, now we have mapped the OrderId - the property (column) - which we will use to map the <bag>.
Well, honestly, now I am not sure what your thoughts were. In case that LegacyOrder would have one column mapped as key (the Order_ID) we can do it like this.
<bag name="Orders" table="LEGACY_ORDERS_VIEW" inverse="true">
<key column="ORDER_ID" property-ref="OrderId" />
<many-to-many class="LegacyOrder" formula="ORDER_ID" />
</bag>
But that's not reasonable, because the Order_Id is not unique. In fact the LegacyOrder view, does not seem to be the entity at all. It could be some real intermediate structure.
I would say, that what the pairing view Legacy_orders_view represents, is the map (dictionary) saying: The Order with ID == X, had these Versions.
This information, the int Version numbers, is the only thing/information I can find out as really interesting. The OrderId is representing still the same Order
Anyhow, with the proeprty-ref and more detailed knowledge what you need to achieve we can at the end have:
// I. Map
public virtual IDictionary<int, Order> OrderMap { get; set; }
above the Version will play the role of the Key, the Order is questinable, because it will be the same Order as the OrderId says
// II. Version collection
public virtual IList<int> OrderVersions { get; set; }
in this case we will get set of int numbers related to the OrderId. Seems to be the only interesting message we can get.
III. There must be more information, about your entity/DB model. Why does the Legacy_orders_view exists at all? What would we like to get from that "relation" at the end?
Related
I have recently inherited a project at work that contains NHibernate. I am extremely new to it and have to make a modification to one of the mappings. I've read through the documentation here and I'm still not sure how to do this or if my understanding/terminology is even correct.
So given the following table structure I need a bag that will get me the ProjectName:
User
UserID (PK)
ProjectUser
UserID (PK, FK User.UserID)
ProjectID (PK, FK Project.ProjectID)
Project
ProjectID (PK)
ProjectName
Here is the existing bag mapping and it correctly returns the ProjectID, but now I'm trying to understand how I need to modify it to return both the ProjectID and the ProjectName:
<bag name="Projects" table="ProjectUser" lazy="true" inverse="true" cascade="save-update">
<key column="UserId"></key>
<many-to-many class="Project" column="ProjectID"></many-to-many>
</bag>
Well, your mapping seems to be correct, i.e. already returning the ProjectName. To be sure please, check that the object Project is mapped like this:
<class name="Project" table="Project">
<id name="Id" column="ProjectID" generator class="native"/>
<!-- above as the Id we have mapping for column ProjectId
below the C# Name will contain the column ProjectName -->
<property name="Name" column="ProjectName" />
<!-- related Users to this Project -->
<bag name="Users" table="ProjectUser" lazy="true" >
<key column="ProjectID"></key>
<many-to-many class="User" column="UserID" />
</bag>
</class>
And the Project would be like this
public class Project
{
public virtual int Id { get; set;}
public virtual string Name { get; set;}
public virtual IList<User> Users { get; set;}
...
So, having this in place, we should be able to use the User:
public class User
{
public virtual IList<Project> Projects { get; set;}
...
mapped and loaded by NHibernate like this
user = session.Get<User>(x) // id of searched user
foreach(var project in user.Projects)
{
var name = project.Name;
var id = project.Id;
...
}
NOTES:
In case of many-to-many there obviously could/should be <bag> mapping on both sides - Project and User. But only one of them can have inverse="true". So in this case Project.Users does not have that.
The cascade setting on many-to-many is doing (most likely) different thing than one would expect. It is not related to the pairing table but to the second end of that mapping.
Cascading of the pairing object is done out of the box. It does not have to be mapped, in fact it cannot be mapped or turned off... Other words I would suggest to remove that cascade, unless you really want to change the Project in persistence, if you are working with some User.
Check also:
23.2. Author/Work
How to do it without many-to-many ... with explicit pairing object as a mapped Entity: Nhibernate: How to represent Many-To-Many relationships with One-to-Many relationships? or Am I doing many to many incorrectly when using fluent nhibernate?
I have customer and BankAccount tables. CustomerID is a foreign key in the BankAccount table. Here we have to do with one to many relationship. I have done mapping for CustomerInfo and BankAccountInfo. I have attached with the question post.
Here we want to do with collection using Iesi.collections. Does anyone have ideas on how to do this in Nhibernate?
To do Collection mapping start reading this documentation: Chapter 6. Collection Mapping
In case that you will use the Iesi collections, most suitable could be ISet<>, which is designed to allow only unqiue values to be inserted:
public class Customer
{
public virtual Iesi.Collections.Generic.ISet<BankAccount> BankAccounts { get; set; }
}
And the mapping could be like this
<set name="BankAccounts" inverse="true" lazy="true" table="BankAccount"
cascade="all" batch-size="25" >
<key column="CustomerId" />
<one-to-many class="BankAccount" />
</set>
Notes: This mapping has some redundant attributes, which can help to do smarter loading. E.g. batch-size will avoid 1+N loads, while doing 1 + N/25... cascade will implicitly solve the persistence of added items... read more in doc.
The complete ISet description: http://ayende.com/blog/3943/nhibernate-mapping-set
Here are the relevant pieces. This is a parent object:
public class Article
{
public virtual IList<ArticleRevision> Revisions { get; set; }
}
<list name="Revisions" cascade="all" inverse="true" table="ArticleRevision">
<cache usage="read-write" />
<key column="ArticleID" not-null="true" />
<index column="Number" type="int32" />
<one-to-many class="ArticleRevision" />
</list>
This is a child:
public class ArticleRevision
{
public virtual Article Article { get; set; }
}
<many-to-one name="Article" column="ArticleID" not-null="true" />
Now, I create an instance of Article, add one ArticleRevision to Article.Revisions collection, set ArticleRevision.Article to reference the Article instance and shove it to the database:
INSERT
INTO
ArticleRevision
(Content, Keywords, CreatedAt, SiteID, ArticleID, CreatedByUserID, ID)
VALUES
(#p0, #p1, #p2, #p3, #p4, #p5, #p6);
No Number column gets inserted.
How do I correctly map a bidirectional one-to-many collection with list semantics in NHibernate?
From the NHibernate documentation:
Please note that NHibernate does not
support bidirectional one-to-many
associations with an indexed
collection (list, map or array) as the
"many" end, you have to use a set or
bag mapping.
As far as I know, All of ORM frameworks state that when you have a bi-directional relationship, you should define one side, in which your ORM will rely on when updating the state of your bi-directional relationship. Otherwise, you can get side effects such as
Inconsistent mapping
Violation of constraints
Among other stuffs
In your case, the column ArticleID can be managed by both ends of the relationship. Because of that, you should mark one side as inverse="true", which should be placed in the many side when you have a #OneToMany relationship.
However, you can achieve the same behavior by disabling the property ArticleRevision.Article, by setting its mapping as
<many-to-one name="Article" column="ArticleID" insert="false" update="false" />
Using insert="false" update="false", you are saying: NHibernate, does not care about me, in which, as a result, NHibenate you simply ignore it. Now, discard inverse="false" in the property Article.Revisions, and you will get your answer.
UPDATE:
Remove not-null="true" from your key column because it is updated right after ArticleRevision. As a consequence, you will get a constraint violation otherwise.
I am building an application that allows users to make changes to objects, but the changes have to be approved before they are made permanent. I am using NHibernate.
How would you handle this sort of scenario using? Are there any articles out there that tackle this issue?
I am thinking of having two tables for each object. One for the current state and one for the proposed state. Then having a Generic ChangeRequest object that would specify the Delete/Insert/Update and the subject that wants to be changed. But, I don't believe NHibernate will allow you to have two different mappings for the same object.
The two options off the top of my head...
Each object could have an approved flag or approved date. (This could be encapsulated in a common base class.) You would either need a composite key including the approval column (not recommended - composite keys are a pain) or each object could have a business key in addition to the PK. This would mean one table per entity with some extra metadata columns in each table to determine approved. (You could decide whether to keep all changes or only the latest.)
The other option would be two separate tables for each object. You can do this using Entity Names, which were introduced in NH2.1. Easiest to show an example. We have only one class definition:
public class Foo {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
We have two hbm.xml files. Note the entity-name attribute after the class. This creates two tables, Foo1 and Foo2. (You could pick your own names via the table attribute.)
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NhHacking" assembly="NhHacking">
<class name="Foo" entity-name="Foo1">
<id name="Id">
<generator class="native" />
</id>
<property name="Name"/>
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NhHacking" assembly="NhHacking">
<class name="Foo" entity-name="Foo2">
<id name="Id">
<generator class="native" />
</id>
<property name="Name"/>
</class>
</hibernate-mapping>
When we save the entities, we provide the entity-name with the operation:
var foo1 = new Foo {Name = "Foo1"};
var foo2 = new Foo {Name = "Foo2"};
session.Save("Foo1", foo1);
session.Save("Foo2", foo2);
This allows you to select which table the entity goes to. You would of course want to encapsulate the entity names in a constants class. You need to specify the entity name for all operations (session.Get(), session.Save(), session.Update(), session.Delete(), etc.)
For your case I would suggest that each object will have the following design: PropertyName, PropertyValue, PropertyStatus (propesed/approved). By this desing you can alter your objects in any way while keeping one mapping for the file.
For example if a person table should have the columns ssn, name, birthdate .... In this desing you will have 3 rows:
In regular design
-----------------
ColumnName ColumnValue
ID 1
ssn 123
name xyz
birthdate dd/mm/yyy
This will be
------------
PropertyName PropertyValue ObjectID PropertyStatus
ssn 123 1 approved
name xyz 1 approved
birthdate dd/mm/yyyy 1 approved
Think of it as if it is a vertical table where rows are column and vice versa.
It seems that NHibernate needs to have an id tag specified as part of the mapping. This presents a problem for views as most of the time (in my experience) a view will not have an Id. I have mapped views before in nhibernate, but they way I did it seemed to be be messy to me.
Here is a contrived example of how I am doing it currently.
Mapping
<class name="ProductView" table="viewProduct" mutable="false" >
<id name="Id" type="Guid" >
<generator class="guid.comb" />
</id>
<property name="Name" />
<!-- more properties -->
</class>
View SQL
Select NewID() as Id, ProductName as Name, --More columns
From Product
Class
public class ProductView
{
public virtual Id {get; set;}
public virtual Name {get; set;}
}
I don't need an Id for the product or in the case of some views I may not have an id for the view, depending on if I have control over the View
Is there a better way of mapping views to objects in nhibernate?
Edit
Answer So Far
Mapping
<class name="ProductView" table="viewProduct" mutable="false" >
<id name="Id" type="Guid" />
<property name="Name" />
<!-- more properties -->
</class>
Class
public class ProductView
{
public virtual Name {get; set;}
//more properties
}
View SQL
Do I still need NewID()?
Select NewID() as Id, ProductName as Name, --More columns
From Product
You can make it just a little bit cleaner by not mapping the Id to a property and omitting the generator:
<id column="Id" type="guid"/>
That way, you keep the problem in the data layer, without leaking the implementation detail to your domain.
As far as I know, NHibernate will require either an id or a composite-id definition since it's the mechanism by which it uniquely identifies a given record. If there is no combination of columns that provides a key for each row in the view, I think you are stuck with hacky workarounds.