How to map this legacy table with NHibernate (Fluent)? - nhibernate

New to NHibernate. Having trouble wrapping my head around how to map this legacy table.
CREATE TABLE [dbo].[CmnAddress](
[addressId] [int] NOT NULL,
[objectType] [varchar](63) NULL,
[objectId] [int] NULL,
[addressType] [varchar](7) NULL,
[recordStatus] [char](1) NULL,
[fromDate] [int] NULL,
[toDate] [int] NULL,
[onStreet] [varchar](254) NULL,
[atStreet] [varchar](254) NULL,
[unit] [varchar](30) NULL,
[city] [varchar](254) NULL,
[state] [varchar](30) NULL,
[zipCode] [varchar](30) NULL,
)
There is also a "CmnPerson" table that I have mapped to a Person class. I need the Person class to contain a list of Addresses, where the objectType column contains "CmnPerson" and the objectId field matches my Person.Id ("CmnPerson.personId") field.
I am also later going to have to create a Contact class that also contains a list of Addresses where the objectType column contains "CmnContact".
I'm having a very tough time figuring out if I should be using the Any mapping or class-hierarchy-per-table with discrimination on subcolumns? Or if either of those will even meet my needs.
Can anyone show my how to map this Address class? Fluent config would be preferable.
ADDED INFO:
The following classes and mappings almost work, but the Addresses list return ALL rows from the CmnAddress table with a matching objectId, regardless of the objectType field's value. I think I could use an ApplyFilter on the HasMany mapping for Person.Addresses, but that doesn't seem like the "right" way.
More added info: I was able to resolve this last issue by chaining AlwaysSelectWithValue() on after the call to DiscriminateSubClassesOnColumn(...)
public class Person
{
public virtual int Id { get; private set; }
public virtual string LastName { get; set; }
public virtual string FirstName { get; set; }
public virtual string MiddleName { get; set; }
public virtual string Gender { get; set; }
public virtual IList<PassClient> PassClients { get; set; }
public virtual IList<PersonAddress> Addresses { get; set; }
}
public class PersonMap : ClassMap<Person>
{
public PersonMap() {
Table("CmnPerson");
Id(x => x.Id).Column("personId");
Map(x => x.LastName);
Map(x => x.FirstName);
Map(x => x.MiddleName);
Map(x => x.Gender);
HasMany(x => x.PassClients).KeyColumn("personId");
HasMany(x => x.Addresses).KeyColumn("objectId");
}
}
abstract public class Address
{
public virtual int Id { get; private set; }
public virtual string StreetNo { get; set; }
public virtual string OnStreet { get; set; }
public virtual string Unit { get; set; }
public virtual string City { get; set; }
public virtual string State { get; set; }
public virtual string ZipCode { get; set; }
}
public class PersonAddress : Address {
public virtual Person Person { get; set; }
}
public class AddressMap : ClassMap<Address>
{
public AddressMap() {
Table("CmnAddress");
Id(x => x.Id).Column("addressId");
Map(x => x.StreetNo);
Map(x => x.OnStreet);
Map(x => x.Unit);
Map(x => x.City);
Map(x => x.State);
Map(x => x.ZipCode);
DiscriminateSubClassesOnColumn("objectType").AlwaysSelectWithValue();
}
}
public class PersonAddressMap : SubclassMap<PersonAddress>
{
public PersonAddressMap() {
DiscriminatorValue("CmnPerson");
References(x => x.Person).Column("objectId");
}
}

You can not use the Any-mapping when it's a has many relation. It can be used, when an address can point to different kinds of objects - like a person, an order or other things not related.
To map the hierarachy you can do it like this:
public class CmnAddressMap : ClassMap<CmnAddress>
{
public CmnAddressMap()
{
Id(x => x.addressId);
Map(x => x...);
DiscriminateSubClassesOnColumn("objectType");
}
}
public class PersonAdressMap : SubclassMap<PersonAddress>
{
public PersonAdressMap()
{
DiscriminatorValue("objectType1");
}
}
public class ContactAdressMap : SubclassMap<ContactAddress>
{
public ContactAdressMap()
{
DiscriminatorValue("objectType2");
}
}
Have an abstract CmnAddress with all the fields (map all the fields in the CmnAdressMap for example) and two subclasses named PersonAddress and ContactAddress.
And the person should then have a collection like IList which should mapped with HasMany. And you should be done.

Sorry, am not familiar with fluent mappings, however, one way to do it would be:
Create an Address abstract class with properties corresponding to all the columns in your table except for objectType
Create a PersonAddress class which extends Address
Create a ContactAddress class which extends Address
Map all the columns of CmnAddress to properties of the Address class in the normal way, except for objectType which you declare as the discriminator column
Map PersonAddress as a subclass of Address with discriminator value "CmnPerson"
Map ContactAddress as a subclass of Address with a discriminator value of "CmnContact"
In code, your Person class would hold a list of PersonAddresses and your Contact class would hold a list of ContactAddresses

Address class
public class Address
{
public virtual int Id { get; set; }
public virtual Person Person { get; set; }
// etc.
}
Person class
public class Person
{
public Person()
{
Addresses = new List<Address>();
}
public virtual int Id { get; set; }
// etc.
public virtual IList<Address> Addresses { get; set; }
}
AddressMap
public class AddressMap : ClassMap<Address>
{
public AddressMap()
{
Table("CmnAddress");
Id(x => x.Id).Column("addressId");
// etc.
References(x => x.Person);
}
}
To separate the difference between Address => Person and Address => Contact, you'll want to read up on NHibernate's polymorphism and discriminate sub classes based on column.

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

Fluent NHibernate Dictionary mapping problem

I have following entities
public class Entity1
{
public virtual Guid Id { get; set; }
public virtual IDictionary<Guid, Entity2> Entities2 { get; set; }
}
public class Entity2
{
public virtual Guid Id { get; set; }
public virtual IDictionary<Guid, Entity1> Entities1 { get; set; }
}
DB table
CREATE TABLE [dbo].[EntityLinks](
[Entity1Id] [uniqueidentifier] NOT NULL,
[Entity2Id] [uniqueidentifier] NOT NULL,
[LinkItemId] [uniqueidentifier] NOT NULL
)
and following mappings:
for Entity1
mapping.HasManyToMany<Entity2>(rc => rc.Entities2)
.Table("EntityLinks")
.ParentKeyColumn("Entity1Id")
.ChildKeyColumn("Entity2Id")
.AsMap<Guid>("LinkItemId")
for Entity2
mapping.HasManyToMany<Entity1>(rc => rc.Entities1)
.Table("EntityLinks")
.ParentKeyColumn("Entity2Id")
.ChildKeyColumn("Entity1Id")
.AsMap<Guid>("LinkItemId")
adding data works fine and I can get and see Entity1.Entities2 populated but Entity2.Entities1 is not populated.
Any suggestions why this might be?
Thank you in advance.
I'm probably wrong but you can try:
mapping.HasManyToMany<Entity2>(rc => rc.Entities2)
.Table("EntityLinks")
.ParentKeyColumn("Entity1Id")
.ChildKeyColumn("Entity2Id")
.AsMap<Guid>("LinkItemId")
.Inverse()
and
mapping.HasManyToMany<Entity1>(rc => rc.Entities1)
.Table("EntityLinks")
.ParentKeyColumn("Entity2Id")
.ChildKeyColumn("Entity1Id")
.AsMap<Guid>("LinkItemId")
.Cascade.All();

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

Fluent NHibernate Many to one mapping

I am creating a NHibenate application with one to many relationship. Like City and State data.
City table
CREATE TABLE [dbo].[State](
[StateId] [varchar](2) NOT NULL primary key,
[StateName] [varchar](20) NULL)
CREATE TABLE [dbo].[City](
[Id] [int] primary key IDENTITY(1,1) NOT NULL ,
[State_id] [varchar](2) NULL refrences State(StateId),
[CityName] [varchar](50) NULL)
My mapping is follows
public CityMapping()
{
Id(x => x.Id);
Map(x => x.State_id);
Map(x => x.CityName);
HasMany(x => x.EmployeePreferedLocations)
.Inverse()
.Cascade.SaveUpdate();
References(x => x.State)
//.Cascade.All();
//.Class(typeof(State))
//.Not.Nullable()
.Cascade.None()
.Column("State_id");
}
public StateMapping()
{
Id(x => x.StateId)
.GeneratedBy.Assigned();
Map(x => x.StateName);
HasMany(x => x.Jobs)
.Inverse();
//.Cascade.SaveUpdate();
HasMany(x => x.EmployeePreferedLocations)
.Inverse();
HasMany(x => x.Cities)
// .Inverse()
.Cascade.SaveUpdate();
//.Not.LazyLoad()
}
Models are as follows:
[Serializable]
public partial class City
{
public virtual System.String CityName { get; set; }
public virtual System.Int32 Id { get; set; }
public virtual System.String State_id { get; set; }
public virtual IList<EmployeePreferedLocation> EmployeePreferedLocations { get; set; }
public virtual JobPortal.Data.Domain.Model.State State { get; set; }
public City(){}
}
public partial class State
{
public virtual System.String StateId { get; set; }
public virtual System.String StateName { get; set; }
public virtual IList<City> Cities { get; set; }
public virtual IList<EmployeePreferedLocation> EmployeePreferedLocations { get; set; }
public virtual IList<Job> Jobs { get; set; }
public State()
{
Cities = new List<City>();
EmployeePreferedLocations = new List<EmployeePreferedLocation>();
Jobs = new List<Job>();
}
//public virtual void AddCity(City city)
//{
// city.State = this;
// Cities.Add(city);
//}
}
My Unit Testing code is below.
City city = new City();
IRepository<State> rState = new Repository<State>();
Dictionary<string, string> critetia = new Dictionary<string, string>();
critetia.Add("StateId", "TX");
State frState = rState.GetByCriteria(critetia);
city.CityName = "Waco";
city.State = frState;
IRepository<City> rCity = new Repository<City>();
rCity.SaveOrUpdate(city);
City frCity = rCity.GetById(city.Id);
The problem is , I am not able to insert record. The error is below.
"Invalid index 2 for this SqlParameterCollection with Count=2."
But the error will not come if I comment State_id mapping field in the CityMapping file. I donot know what mistake is I did. If do not give the mapping Map(x => x.State_id); the value of this field is null, which is desired. Please help me how to solve this issue.
Few remarks:
Remove this State_id property from the City class and the mapping. You already have a State property so it makes no sense in your object model.
Those Jobs and EmployeePreferedLocations properties in the State class don't have any related columns/tables in your database (at least the one you've shown here) while you have mappings for them.