FluentNHibernate Many-To-One References where Foreign Key is not to Primary Key and column names are different - nhibernate

I've been sitting here for an hour trying to figure this out...
I've got 2 tables (abbreviated):
CREATE TABLE TRUST
(
TRUSTID NUMBER NOT NULL,
ACCTNBR VARCHAR(25) NOT NULL
)
CONSTRAINT TRUST_PK PRIMARY KEY (TRUSTID)
CREATE TABLE ACCOUNTHISTORY
(
ID NUMBER NOT NULL,
ACCOUNTNUMBER VARCHAR(25) NOT NULL,
TRANSAMT NUMBER(38,2) NOT NULL
POSTINGDATE DATE NOT NULL
)
CONSTRAINT ACCOUNTHISTORY_PK PRIMARY KEY (ID)
I have 2 classes that essentially mirror these:
public class Trust
{
public virtual int Id {get; set;}
public virtual string AccountNumber { get; set; }
}
public class AccountHistory
{
public virtual int Id { get; set; }
public virtual Trust Trust {get; set;}
public virtual DateTime PostingDate { get; set; }
public virtual decimal IncomeAmount { get; set; }
}
How do I do the many-to-one mapping in FluentNHibernate to get the AccountHistory to have a Trust? Specifically, since it is related on a different column than the Trust primary key of TRUSTID and the column it is referencing is also named differently (ACCTNBR vs. ACCOUNTNUMBER)???? Here's what I have so far - how do I do the References on the AccountHistoryMap to Trust???
public class TrustMap : ClassMap<Trust>
{
public TrustMap()
{
Table("TRUST");
Id(x => x.Id).Column("TRUSTID");
Map(x => x.AccountNumber).Column("ACCTNBR");
}
}
public class AccountHistoryMap : ClassMap<AccountHistory>
{
public AccountHistoryMap()
{
Table("TRUSTACCTGHISTORY");
Id (x=>x.Id).Column("ID");
References<Trust>(x => x.Trust).Column("ACCOUNTNUMBER").ForeignKey("ACCTNBR").Fetch.Join();
Map(x => x.PostingDate).Column("POSTINGDATE");
);
I've tried a few different variations of the above line but can't get anything to work - it pulls back AccountHistory data and a proxy for the Trust; however it says no Trust row with given identifier.
This has to be something simple. Anyone?
Thanks in advance.

You need to use property-ref:
public class AccountHistoryMap : ClassMap<AccountHistory>
{
public AccountHistoryMap()
{
Table("TRUSTACCTGHISTORY");
Id (x=>x.Id).Column("ID");
References(x => x.Trust, "ACCOUNTNUMBER").PropertyRef("ACCTNBR").Fetch.Join();
Map(x => x.PostingDate).Column("POSTINGDATE");
}
}

Related

How can I map a Foreign Key in EF Code First Fluent when the column name does not match the parent table?

I would like to do mapping using the Fluent API. I saw some other examples but these look different to what I want to do.
Here is my SQL:
ALTER TABLE [Question] ADD CONSTRAINT [FK_QuestionUserProfile]
FOREIGN KEY([AssignedTo]) REFERENCES [UserProfile] ([UserId])
Which gives me this error message:
There are no primary or candidate keys in the referenced table 'UserProfile'
that match the referencing column list in the foreign key 'FK_QuestionUserProfile'
Could not create constraint
I have two classes:
public class Question
{
public int QuestionId { get; set; }
public string Text { get; set; }
public int AssignedTo { get; set; }
public int ModifiedBy { get; set; }
public virtual UserProfile UserProfile { get; set; }
}
public partial class UserProfile
{
public UserProfile()
{
this.webpages_Roles = new List<webpages_Roles>();
}
public int UserId { get; set; }
public string UserName { get; set; }
public virtual ICollection<webpages_Roles> webpages_Roles { get; set; }
public virtual ICollection<Question> Questions { get; set; }
}
I want to do the mapping between Question <> UserProfile so that:
AssignedTo is mapped to UserId in UserProfile.
Modified is mapped to UserId in UserProfile.
I have this so far in my QuestionMap
this.HasRequired(t => t.UserProfile)
.WithMany(t => t.Questions)
.HasForeignKey(d => d.AssignedTo);
this.HasRequired(t => t.UserProfile)
.WithMany(t => t.Questions)
.HasForeignKey(d => d.ModifiedBy);
But will this work as the ForeignKey has AssignedTo as the name in Question and UserId in UserProfile. Is
there a way in the mappings that I can specify they should map to UserId ?
The foreign key and the primary key in the principal table doesn't have to have the same name. No problem to map AssignedTo to UserId. However you are trying to define two relationships and to do this you also need two navigation properties. You cannot use UserProfile as navigation property in both relationships nor can you use the Questions collection twice. You could change your Question entity like so (UserProfile can remain unchanged):
public class Question
{
public int QuestionId { get; set; }
public string Text { get; set; }
public int AssignedTo { get; set; }
public int ModifiedBy { get; set; }
public virtual UserProfile AssignedToUser { get; set; }
public virtual UserProfile ModifiedByUser { get; set; }
}
And then create this mapping:
this.HasRequired(t => t.AssignedToUser)
.WithMany(t => t.Questions)
.HasForeignKey(d => d.AssignedTo);
this.HasRequired(t => t.ModifiedByUser)
.WithMany() // <- don't use "t => t.Questions" here again
.HasForeignKey(d => d.ModifiedBy)
.WillCasadeOnDelete(false);
Cascading delete must be disabled for at least one of the two relationships. Otherwise SQL Server will complain about multiple cascading delete paths.
However this all won't probably fix the exception you had when creating the foreign key constraint. This error means that UserProfile.UserId is not the primary key in the UserProfile table (or generally it does not have a unique constraint). Maybe UserId is only part of a composite primary key (with UserId+UserName perhaps)?

Fluent nhibernate map lookup table as enum

I am trying to map a lookup table to an enum using:
FluentNhibernate 1.3.0.733
NHibernate 3.3.1.4000
I am not able to load objects. If I remove the enum mapping I can load objects.
Code:
Order order = session.Get<Order>(id);
Error:
Provided id of the wrong type. Expected: Order+OrderStatus, got System.Int32
Object:
public class Order
{
public enum OrderStatus
{
PaymentPending = 0
}
public virtual int Id { get; set; }
public virtual Customer Customer { get; set; }
public virtual Address Address { get; set; }
public virtual IList<OrderLine> OrderLines { get; set; }
public virtual OrderStatus Status { get; set; }
public virtual DateTime Created { get; set; }
public Order()
{
OrderLines = new List<OrderLine>();
}
}
Mapping: (I have cut the mapping down to these fields for testing)
public OrderMapping()
{
Table("orders");
Id(x => x.Id);
Id(x => x.Status, "state_id").CustomType<Order.OrderStatus>().Not.Nullable();
References(x => x.Address).Cascade.All().Column("address_id");
References(x => x.Customer).Cascade.All().Column("customer_id");
}
Tables:
CREATE TABLE [order_states] (
[id] INTEGER NOT NULL PRIMARY KEY,
[state] NVARCHAR(50) NOT NULL
);
CREATE TABLE [orders] (
[id] INTEGER NOT NULL PRIMARY KEY,
[customer_id] INTEGER NOT NULL,
[address_id] INTEGER NOT NULL,
[state_id] INTEGER NOT NULL,
[created] DATE,
FOREIGN KEY(customer_id) REFERENCES customers(id),
FOREIGN KEY(address_id) REFERENCES addresses(id),
FOREIGN KEY(state_id) REFERENCES order_states(id)
);
What am I doing wrong?
Caused by a silly error.
I had the the status mapped as an id and not as a property.
Correct mapping:
public OrderMapping()
{
Table("orders");
Id(x => x.Id);
Map(x => x.Status, "state_id").CustomType<Order.OrderStatus>().Not.Nullable();
References(x => x.Address).Cascade.All().Column("address_id");
References(x => x.Customer).Cascade.All().Column("customer_id");
}

Fluent NHibernate automapping composite id with component

I have this complex situation: a database of countries/regions/states/cities which primary key is composed by a code (nvarchar(3)) in a column called "Id" plus all key columns of "ancestors" (regions/states/cities).
So the table country has only one key coumn (Id) while cities has 4 key columns (Id, StateId,regionId,CountryId). Obviously they're all related, so each ancestor column is a foreign key to the related table.
I have Entities in my Model that map this relationships. But they all derive from one type called Entity<T> where T may be a simple type (string, in etc) or a complex one (a component implementing the key).
Entity<T> implements a single property called Id of type T.
For each db table, if it has a comlex key, I implement it in a separate component, which oveerides also Equals and GetHashCode() Methods (in future I'll implement those in the Entity base class).
So I have a RegionKey componet that has 2 properties (Id and CountryId).
I have conventions for Foreign Key and primary key naming and type and that is ok.
I have also Mapping ovverrides for each complex Entity.
For simplicity, lets concentrate only on Countries and Regions table. Here they are:
public class Country: Entity<string>
{
public virtual string Name { get; set; }
public virtual IList<Region> Regions { get; set; }
}
public class Region: Entity<RegionKey>
{
public virtual string Name { get; set; }
public virtual Country Country { get; set; }
}
and the RegionKey component:
namespace Hell.RealHellState.Api.Entities.Keys
{
[Serializable]
public class RegionKey
{
public virtual string Id { get; set; }
public virtual string CountryId { get; set; }
public override bool Equals(object obj)
{
if (obj == null)
return false;
var t = obj as RegionKey;
if (t == null)
return false;
return Id == t.Id && CountryId == t.CountryId;
}
public override int GetHashCode()
{
return (Id + "|" + CountryId).GetHashCode();
}
}
}
Here is the configuration of AutoPersistenceModel:
public ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(
MsSqlCeConfiguration.Standard
.ConnectionString(x=>x.Is(_connectionString))
)
.Mappings(m => m.AutoMappings.Add(AutoMappings))
.ExposeConfiguration(BuildSchema)
.BuildSessionFactory();
}
private AutoPersistenceModel AutoMappings()
{
return AutoMap.Assembly(typeof (Country).Assembly)
.IgnoreBase(typeof(Entity<>))
.Conventions.AddFromAssemblyOf<DataFacility>()
.UseOverridesFromAssembly(GetType().Assembly)
.Where(type => type.Namespace.EndsWith("Entities"));
}
private static void BuildSchema(Configuration config)
{
//Creates database structure
new SchemaExport(config).Create(false, true);
//new SchemaUpdate(config).Execute(false, true);
}
Here is the Regions entity overrides
public class RegionMappingOverride : IAutoMappingOverride<Region>
{
public void Override(AutoMapping<Region> mapping)
{
mapping.CompositeId(x=>x.Id)
.KeyProperty(x => x.Id, x=> x.ColumnName("Id").Length(3).Type(typeof(string)))
.KeyProperty(x => x.CountryId, x => x.ColumnName("CountryId").Length(3).Type(typeof(string)));
}
}
Ok now when I test this mapping I got an error saying: The data types of the columns in the relationship do not match.
I have also tried this override:
public void Override(AutoMapping<Region> mapping)
{
mapping.CompositeId()
.ComponentCompositeIdentifier(x=>x.Id)
.KeyProperty(x => x.Id.Id, x=> x.ColumnName("Id").Length(3).Type(typeof(string)))
.KeyProperty(x => x.Id.CountryId, x => x.ColumnName("CountryId").Length(3).Type(typeof(string)));
}
And it almost work but it creates a Regions table with a single column key of varbinary(8000) which is not what I want:
CREATE TABLE [hell_Regions] (
[Id] varbinary(8000) NOT NULL
, [Name] nvarchar(50) NULL
, [CountryId] nvarchar(3) NULL
);
GO
ALTER TABLE [hell_Regions] ADD CONSTRAINT [PK__hell_Regions__0000000000000153] PRIMARY KEY ([Id]);
GO
ALTER TABLE [hell_Regions] ADD CONSTRAINT [FK_Regions_Country] FOREIGN KEY ([CountryId]) REFERENCES [hell_Countries]([Id]) ON DELETE NO ACTION ON UPDATE NO ACTION;
GO
I don't have a clue of how to deal with it since it seems to me everythin is ok.
Thanks in advance for your answers
Ok I menaged to solve it: I had to sign the CompositeId class as MAPPED, since it is a component. So this is my new RegionMappingOverride:
public class RegionMappingOverride : IAutoMappingOverride<Region>
{
public void Override(AutoMapping<Region> mapping)
{
mapping.CompositeId(x=>x.Id)
.Mapped()
.KeyProperty(x =>x.Id,x=>x.Length(3))
.KeyProperty(x => x.CountryId, x=>x.Length(3));
}
}
Now the sql created is correct:
create table hell_Countries (
Id NVARCHAR(3) not null,
Name NVARCHAR(50) null,
primary key (Id)
)
create table hell_Regions (
Id NVARCHAR(3) not null,
CountryId NVARCHAR(3) not null,
Name NVARCHAR(50) null,
primary key (Id, CountryId)
)
alter table hell_Regions
add constraint FK_Region_Country
foreign key (CountryId)
references hell_Countries

Make AskedBy_PersonId the foreign column name(instead of the default AskedBy_id) on Fluent NHibernate

I have these tables:
create table Person
(
PersonId int identity(1,1) primary key,
PersonName nvarchar(100) not null
);
create table Question
(
QuestionId int identity(1,1) primary key,
QuestionText nvarchar(100) not null,
AskedBy_PersonId int not null references Person(PersonId),
QuestionModifiedBy_PersonId int not null references Person(PersonId)
);
And I have these models:
public class Question
{
public virtual int QuestionId { get; set; }
public virtual string QuestionText { get; set; }
public virtual Person AskedBy { get; set; }
public virtual Person QuestionModifiedBy { get; set; }
}
public class Person
{
public virtual int PersonId { get; set; }
public virtual string PersonName { get; set; }
}
I'm using automapping with FluentNHibernate, the referencing properties defaults to these database column names:
AskedBy_id
QuestionModifiedBy_id
How can one make FluentNHibernate make the referencing properties be mapped to this style of foreign column name?
AskedBy_PersonId
QuestionModifiedBy_PersonId
As of now, I'm doing it in manual overriding:
.Override<Question>(x =>
{
x.References(y => y.AskedBy).Column("AskedBy_PersonId");
x.References(y => y.QuestionModifiedBy).Column("QuestionModifiedBy_PersonId");
})
I wanted to remove that overriding and want Fluent NHibernate to automatically make the foreign column name follow the naming pattern above
How can I achieved that with Fluent NHibernate?
Should be easy with IReferenceConvention implementation:
public class ReferenceConvention : IReferenceConvention
{
public void Apply(IManyToOneInstance instance)
{
instance.Column(
instance.Name + "_" + instance.Property.PropertyType.Name + "Id");
}
}
NHibernate should be configured to read the conventions (with something like this):
Fluently.Configure()
//... other configuration
.Mappings(m => m.AutoMappings.Add(
AutoMap.AssemblyOf<Person>()
.Conventions.AddFromAssemblyOf<ReferenceConvention>());

Fluent NHibernate generates extra columns

We are using Fluent NHibernate for data object model in the company i work.
A couple of days ago, we encountered an issue that Fluent NHibernate generates an extra column which does exist neither in model nor in mapping. Here is the situation:
My Model: FirstClass.cs
public class FirstClass
{
public virtual int Id { get; private set; }
public virtual SecondClass MyReference { get; set; }
public virtual DateTime DecisionDate { get; set; }
}
My Mapping:
public class FirstClassMap : ClassMap<FirstClass>
{
public FirstClassMap()
{
Id(x => x.Id);
Map(x => x.DecisionDate);
References(x => x.MyReference);
}
}
After building the schema with the following code,
Instance._sessionFactory = Fluently.Configure()
.Database(MySQLConfiguration.Standard
.ConnectionString(connectionString)
.ShowSql())
.ExposeConfiguration(c =>
{
c.Properties.Add("current_session_context_class", ConfigurationHelper.getSetting("SessionContext"));
})
.ExposeConfiguration(BuildSchema)
.Mappings( m => m.FluentMappings.AddFromAssemblyOf<Community>())
.BuildSessionFactory();
An extra column named "SecondClass_id" is produced with index and foreign key to SecondClass table with Id column. Here is the table produced:
CREATE TABLE `FirstClass` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`DecisionDate` datetime DEFAULT NULL,
`MyReference_id` int(11) DEFAULT NULL,
`SecondClass_id` int(11) DEFAULT NULL,
PRIMARY KEY (`Id`),
KEY `MyReference_id` (`MyReference_id`),
KEY `SecondClass_id` (`SecondClass_id`),
CONSTRAINT `FK4AFFB59B2540756F` FOREIGN KEY (`MyReference_id`) REFERENCES `SecondClass` (`Id`),
CONSTRAINT `FK4AFFB59B51EFB484` FOREIGN KEY (`SecondClass_id`) REFERENCES `SecondClass` (`Id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
I found that, if I rename "MyReference" to "SecondClass" (same name as the class type), there is no extra column created. But i want to use my property with the name i specified, not with the class name. Why that extra column is created? How do i fix that? I don't want extra foreign key columns hanging around.
This often happens when you're using FNH and you have a two-way relationship between entities.
public class FirstClass
{
public virtual SecondClass MyReference { get; set; }
}
public class SecondClass
{
public virtual List<FirstClass> ListOfFirstClass { get; set; }
}
public class FirstClassMap : ClassMap<FirstClass>
{
public FirstClassMap()
{
References(x => x.MyReference);
}
}
public class SecondClassMap : ClassMap<SecondClass>
{
public SecondClassMap()
{
HasMany(x => x.ListOfFirstClass);
}
}
To fix this you have to override the column name used in either ClassMap, like so:
public class SecondClassMap : ClassMap<SecondClass>
{
public SecondClasssMap()
{
HasMany(x => x.ListOfFirstClass).KeyColumn("MyReference_id");
}
}
or:
public class FirstClassMap : ClassMap<FirstClass>
{
public FirstClassMap()
{
References(x => x.MyReference).Column("SecondClass_id");
}
}
The reason for this is that FNH treats each mapping as a separate relationship, hence different columns, keys, and indexes get created.