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.
Related
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");
}
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
I have 3 classes that I'm trying to map using fluent nHibernate, but I've hit a wall. I have a collection in class A that refers to class B, B also has a reference to A. So that is a Many-to-one relationship. My problem is that A also has a reference to C (which extends B), and because it already has a reference to A (through B) I don't want to have to create a new property to make the one-to-many relationship between A and C. Is this possible or do I have to make a second property in C?
public class A
{
public virtual IList<B> AllBInstances { get; set; }
public virtual C ActiveC { get; set; }
}
public class B
{
public virtual A Parent { get; set; }
}
public class C : B
{}
for the given example here are the mappings
class AMap : ClassMap<A>
{
public AMap()
{
Id(x => x.Id);
HasMany(x => x.AllBInstances)
.KeyColumn("A_id")
.Cascade.All();
References(x => x.ActiveC);
}
}
class BMap : ClassMap<B>
{
public BMap()
{
Id(x => x.Id);
References(x => x.Parent, "A_id");
}
}
class CMap : SubclassMap<C>
{
public CMap()
{
}
}
which would give you
table "A" (
Id integer,
ActiveC_id INTEGER,
primary key (Id)
)
table "B" (
Id integer,
A_id INTEGER,
primary key (Id)
)
table "C" (
B_id INTEGER not null,
primary key (B_id)
)
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");
}
}
My automapping:
return Fluently.Configure()
.Database(config)
.Mappings(m =>
m.AutoMappings.Add(
AutoMap.AssemblyOf<Company>()
.Where(
t => t.Namespace == "DAL.DomainModel" && t.IsClass)
.IgnoreBase<ReferenceEntity>()))
.BuildSessionFactory();
So ReferenceEntity is an abstract class containing a string Name, and all my reference entities inherit from this class. I'd like to modify my automapping to add a unique constraint to the Name field for all entities that inherit from ReferenceEntity.
I've gathered it has something to do with .Setup but I'm a bit lost on how to proceed.
note: I'm using the Fluent NHibernate v1.0 RTM so conventions will be with the new style if that is relavent to my goal.
If all your entities inherit from ReferenceEntity, wouldn't you want to create the unique constraint for the Name property on all the entities that are mapped?
But if you want to filter by entity base class, you can do it. Use a convention to add the unique constraint to your mappings:
public class NameConvention : IPropertyConvention
{
public void Apply(IPropertyInstance instance)
{
// Check the entity base class type
if (instance.EntityType.BaseType.Name == "ReferenceEntity")
{
// Only add constraint to the .Name property
if (instance.Name == "Name")
{
instance.Unique();
}
}
}
}
To get the convention (and all other conventions in the assembly) picked up by FNH, just add this line the AutoMap setup you have above:
.Conventions.AddFromAssemblyOf<NameConvention>()
Alex,
No the answer doesn't change. Here is an example, using the convention above.
public abstract class ReferenceEntity
{
public virtual int Id { get; protected set; }
public virtual string Name { get; set; }
}
public class User : ReferenceEntity
{
public virtual string Email { get; set; }
}
public class Item : ReferenceEntity
{
public virtual string Description { get; set; }
}
This creates sql of:
create table Users (
Id INTEGER not null,
Email TEXT not null,
Name TEXT not null unique,
primary key (Id)
)
create table Items (
Id INTEGER not null,
Description TEXT,
Name TEXT not null unique,
primary key (Id)
)
As long as these are separate entities, it will create a unique constraint on the .Name property for each entity.