Fluent NHibernate: How to map many-to-many relations with discriminator - fluent-nhibernate

I have the following tables and entities which need to be mapped in Fluent NHibernate.
Tables:
CREATE TABLE workarea
(
id uuid NOT NULL,
name character varying(255) NOT NULL,
CONSTRAINT pk_workarea PRIMARY KEY (id),
)
CREATE TABLE element
(
id uuid NOT NULL,
name character varying(255) NOT NULL,
CONSTRAINT pk_element PRIMARY KEY (id),
)
CREATE TABLE attachment
(
id uuid NOT NULL,
filename character varying(255) NOT NULL,
CONSTRAINT pk_attachment PRIMARY KEY (id),
)
CREATE TABLE objectattachment
(
id uuid NOT NULL,
attachmentid uuid NOT NULL,
attachmenttype string NOT NULL,
objectid uuid NOT NULL,
CONSTRAINT pk_objectattachment PRIMARY KEY (id),
CONSTRAINT fk_oa_a FOREIGN KEY (attachmentid)
REFERENCES attachment (id) MATCH SIMPLE
ON UPDATE RESTRICT ON DELETE RESTRICT,
CONSTRAINT fk_oa_at FOREIGN KEY (attachmenttypeid)
REFERENCES attachmenttype (id) MATCH SIMPLE
ON UPDATE RESTRICT ON DELETE RESTRICT
)
The idea under this database design is as follows:
A "workarea" or an "element" could have several "attachment" files and an "attachment" file could be referred to by several "workarea"s or "element"s.
A "workarea" or an "element" could refer to the same "attachment" file.
So the relations between "attachment"s and "workarea"s or "element"s are stored in "objectattachment
" table, in which:
"attachmentid" field refers to the identifier of a specific "attachment"s.
"attachmenttype" field (discriminator) defines whether this relation
is between "attachment"s and "workarea"s or between "attachment"s and
"element"s.
"objectid" field refers to the identifier of a specific "workarea"s or "element"s, depending on the value of the above "attachmenttype" field.
Based on the database design, I then define domain model classes as follows:
public class WorkArea
{
private Guid _id = Guid.Empty;
private string _name;
public virtual Guid Id
{
get { return _id ; }
set { _id = value; }
}
public virtual string Name
{
get { return _name ; }
set { _name = value; }
}
}
public class Element
{
private Guid _id = Guid.Empty;
private string _name;
public virtual Guid Id
{
get { return _id ; }
set { _id = value; }
}
public virtual string Name
{
get { return _name ; }
set { _name = value; }
}
}
public class Attachment
{
private Guid _id = Guid.Empty;
private string _fileName;
public virtual Guid Id
{
get { return _id ; }
set { _id = value; }
}
public virtual string FileName
{
get { return _fileName; }
set { _fileName= value; }
}
}
public class WorkAreaAttachment : Attachment
{
private WorkArea _workArea;
public virtual WorkArea WorkArea
{
get { return _workArea; }
set { _workArea = value; }
}
}
public class ElementAttachment : Attachment
{
private Element _element;
public virtual Element Element
{
get { return _element; }
set { _element = value; }
}
}
Now my question is whether I could mapping these domain model classes with the above database design. If yes, then how could I do that? If no, then how do I change the domain model classes to support Fluent NHibernate mapping against the designed database as I don't want to change the current database design (i.e. create separate "attachment" tables for "workarea" and "element").
Regards,
Quan

public class AttachmentLink
{
private Attachment _attachment;
public virtual Attachment Parent
{
get { return _attachment; }
set { _attachment = value; }
}
private IHasAttachments _linkedTo;
public virtual IHasAttachments LinkedTo
{
get { return _linkedTo; }
set { _linkedTo = value; }
}
}
// in AttachmentMap
HasMany(x => x.Links)
.Table("objectattachment");
// map the component
sealed class AttachmentLinkMap : ComponentMap<AttachmentLink>
{
public AttachmentLinkMap()
{
References(x => x.Attachment, "attachmentid");
ReferencesAny(x => x.LinkedTo)
.IdentityType<Guid>()
.EntityIdentifierColumn("objectid")
.EntityTypeColumn("attachmenttype")
.AddMetaValue<WorkArea>(typeof(WorkArea).Name.ToLower())
.AddMetaValue<Element>(typeof(Element).Name.ToLower())
.Not.LazyLoad(); // to prevent false proxies
}
}
// in ElementMap, and almost the same in WorkAreaMap
HasManyToMany(x => x.Attachments)
.Where("attachmenttype='element'")
Note: you don't need an Id column in the link table

Related

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

Castle Active Record fails to initialize

I am implementing a relatively simple model of user management using Castle Active Record with NHibernate on top of MySql, and I have ran into an issue.
Let us say, I have two tables _users and _passwords described by the following SQL create statements
CREATE TABLE _users (
id bigint(20) NOT NULL AUTO_INCREMENT,
username char(32) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY username_UQ (username),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE _passwords (
id bigint(20) NOT NULL AUTO_INCREMENT,
creation_date datetime NOT NULL,
user_id bigint(20) NOT NULL,
password_hash char(64) NOT NULL,
valid_end_date datetime NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY user_id_password_UQ (user_id,password_hash),
KEY user_passwords_FK (user_id),
CONSTRAINT user_passwords_FK FOREIGN KEY (user_id) REFERENCES _users (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
The idea is to keep a primitive password history, therefore, the passwords are kept in a separate table _passwords, which has many-to-one relation with the _users table.
Now, the following C# code models this structure using Castle Active Records
namespace DataEntities
{
[ActiveRecord("_users")]
public class User : ActiveRecordBase<User>
{
[PrimaryKey(PrimaryKeyType.Identity, "id", Access = PropertyAccess.NosetterLowercase)]
public ulong Id
{
get;
set;
} // Id
[Property("username", ColumnType = "String", NotNull = true, Unique = true)]
[ValidateIsUnique]
public string Username
{
get;
set;
} // Username
[HasMany(typeof(Password))]
public IList<Password> Passwords
{
get;
set;
} // Passwords
public string ValidPasswordHash
{
get
{
DateTime l_dtNow = DateTime.Now;
if (Passwords.Count != 1 || Passwords[0].ValidFrom >= l_dtNow || Passwords[0].ValidUntil <= l_dtNow)
{
throw new Exception();
}
return Encoding.UTF8.GetString(Passwords[0].PasswordHash);
}
} // ValidPasswordHash
public static User FindByCredentials(string i_sUsername, string i_sHashedPassword)
{
return FindOne(Restrictions.Eq("Username", i_sUsername), Restrictions.Eq("ValidPasswordHash", i_sHashedPassword));
} // FindByCredentials
} // User
[ActiveRecord("_passwords")]
public class Password : ActiveRecordBase<Password>
{
[PrimaryKey(PrimaryKeyType.Identity, "id", Access = PropertyAccess.NosetterLowercase)]
public ulong Id
{
get;
set;
} // Id
[BelongsTo("user_id", NotNull = true, UniqueKey = "_passwords_UQ1")]
public ulong UserId
{
get;
set;
} // UserId
[Property("password_hash", ColumnType = "UInt64", NotNull = true, UniqueKey = "_passwords_UQ1")]
public byte[] PasswordHash
{
get;
set;
} // PasswordHash
[Property("creation_date", ColumnType = "DateTime", NotNull = true)]
public DateTime ValidFrom
{
get;
set;
} // ValidFrom
[Property("valid_end_date", ColumnType = "DateTime", NotNull = true)]
public DateTime ValidUntil
{
get;
set;
} // ValidUntil
} // Password
} // DataEntities
and on my application start the framework is initialized
try
{
ActiveRecordStarter.Initialize(ActiveRecordSectionHandler.Instance, typeof(Password),
typeof(User));
}
catch (Exception l_excpt)
{
// handle exception
}
At the end, when this code runs, it generates the following exception:
Castle.ActiveRecord.Framework.ActiveRecordException: ActiveRecord tried to infer details about the relation User.Passwords but it could not find a 'BelongsTo' mapped property in the target type DataEntities.Password
at Castle.ActiveRecord.Framework.Internal.SemanticVerifierVisitor.VisitHasMany(HasManyModel model) in c:\daten\dev\External\Castle\AR2.0\ActiveRecord\Castle.ActiveRecord\Framework\Internal\Visitors\SemanticVerifierVisitor.cs:line 544
at Castle.ActiveRecord.Framework.Internal.AbstractDepthFirstVisitor.VisitNodes(IEnumerable nodes) in c:\daten\dev\External\Castle\AR2.0\ActiveRecord\Castle.ActiveRecord\Framework\Internal\Visitors\AbstractDepthFirstVisitor.cs:line 45
at Castle.ActiveRecord.Framework.Internal.AbstractDepthFirstVisitor.VisitModel(ActiveRecordModel model) in c:\daten\dev\External\Castle\AR2.0\ActiveRecord\Castle.ActiveRecord\Framework\Internal\Visitors\AbstractDepthFirstVisitor.cs:line 59
at Castle.ActiveRecord.Framework.Internal.SemanticVerifierVisitor.VisitModel(ActiveRecordModel model) in c:\daten\dev\External\Castle\AR2.0\ActiveRecord\Castle.ActiveRecord\Framework\Internal\Visitors\SemanticVerifierVisitor.cs:line 122
at Castle.ActiveRecord.Framework.Internal.AbstractDepthFirstVisitor.VisitNodes(IEnumerable nodes) in c:\daten\dev\External\Castle\AR2.0\ActiveRecord\Castle.ActiveRecord\Framework\Internal\Visitors\AbstractDepthFirstVisitor.cs:line 45
at Castle.ActiveRecord.ActiveRecordStarter.RegisterTypes(ISessionFactoryHolder holder, IConfigurationSource source, IEnumerable`1 types, Boolean ignoreProblematicTypes) in c:\daten\dev\External\Castle\AR2.0\ActiveRecord\Castle.ActiveRecord\Framework\ActiveRecordStarter.cs:line 927
at Castle.ActiveRecord.ActiveRecordStarter.Initialize(IConfigurationSource source, Type[] types) in c:\daten\dev\External\Castle\AR2.0\ActiveRecord\Castle.ActiveRecord\Framework\ActiveRecordStarter.cs:line 202
at Global.Application_Start(Object sender, EventArgs e) in C:\Projects Code\xyz\Global.asax.cs:line 22
Well, I have stared endlessly at the UserId property of the Password class, I have googled, and now I am quite lost. So the community is my last hope... Can anybody help me understanding what causes this exception and how to fix it?
Thank you all in advance for your replies and comments.
You should have a User User { get; set; } reference property instead of a foreign key.
The official docs are a good place to start.

Inserting records in postgres using NHibernate 3.2 (loquacious) and bycode mappings

I am working on a very basic NHibernate 3.2 task, inserting records into existing Postgres tables. I am using very simple object so that they make sense for this question.
The postgres tables are as follows:
CREATE TABLE cat
(
id serial NOT NULL,
"name" character varying(50) NOT NULL,
sex_id integer NOT NULL,
CONSTRAINT cat_pkey PRIMARY KEY (id),
CONSTRAINT cat_sex_id_fkey FOREIGN KEY (sex_id)
REFERENCES sex (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
OIDS=FALSE
);
CREATE TABLE sex
(
id serial NOT NULL,
"name" character varying(10) NOT NULL,
CONSTRAINT sex_pkey PRIMARY KEY (id),
CONSTRAINT sex_name_key UNIQUE (name)
)
WITH (
OIDS=FALSE
);
My mapping classes are as follows:
public class CatMap : ClassMapping<Cat>
{
public CatMap()
{
Table("cat");
Id(x => x.Id, map =>
{
map.Column("id");
map.Generator(NHibernate.Mapping.ByCode.Generators.Native);
});
Property(x => x.Name, map =>
{
map.Column("name");
map.Length(50);
map.NotNullable(true);
});
ManyToOne(x => x.Sex, map =>
{
map.Column("Sex");
map.Unique(true);
map.ForeignKey("cat_sex_id_fkey");
});
}
}
public class SexMap : ClassMapping<Sex>
{
public SexMap()
{
Table("sex");
Id(x => x.Id, map =>
{
map.Column("id");
map.Generator(Generators.Native);
});
Property(x => x.Name, map =>
{
map.Column("name");
map.Unique(true);
map.Length(10);
map.NotNullable(true);
});
}
}
My data classes are as follows:
public class Sex
{
public Sex()
{
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class Cat
{
public Cat()
{
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Sex Sex { get; set; }
}
Finally, the class containing the code where I am actually attempting to use all of the above.
public class Class1
{
public string DoSomething()
{
var sessionFactory = CreateSessionFactory();
using (var session = sessionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
Postgres.Tables.Sex sex1 = new Postgres.Tables.Sex() { Name = "Male" };
Postgres.Tables.Sex sex2 = new Postgres.Tables.Sex() { Name = "Female" };
Postgres.Tables.Cat cat1 = new Postgres.Tables.Cat() { Name = "Cat1" };
cat1.Sex = sex1;
Postgres.Tables.Cat cat2 = new Postgres.Tables.Cat() { Name = "Cat2" };
cat2.Sex = sex2;
session.SaveOrUpdate(sex1);
session.SaveOrUpdate(sex2);
session.SaveOrUpdate(cat1);
session.SaveOrUpdate(cat2);
transaction.Commit();
}
}
return "I created the cats.";
}
private static ISessionFactory CreateSessionFactory()
{
NHibernate.Mapping.ByCode.ModelMapper modelMapper = new NHibernate.Mapping.ByCode.ModelMapper();
System.Type[] mappingTypes = typeof(Postgres.Tables.Mappings.CatMap).Assembly.GetExportedTypes().Where(t => t.Name.EndsWith("Map")).ToArray();
modelMapper.AddMappings(mappingTypes);
Configuration cfg = new Configuration();
cfg.Proxy(p => p.ProxyFactoryFactory<NHibernate.Bytecode.DefaultProxyFactoryFactory>());
cfg.DataBaseIntegration(d =>
{
d.ConnectionString = "server=192.168.1.126;Port=5432;Database=simple;User Id=postgres;Password=postgres;";
d.Dialect<NHibernate.Dialect.PostgreSQL82Dialect>();
});
cfg.AddMapping(modelMapper.CompileMappingForAllExplicitlyAddedEntities());
return cfg.BuildSessionFactory();
}
}
I receive a GenericADOException at "session.SaveOrUpdate(cat1)" with the message "could not insert: [DAL.Postgres.Tables.Cat][SQL: INSERT INTO cat (name, Sex) VALUES (?, ?); select lastval()]". The InnerException is "{"ERROR: 42703: column \"sex\" of relation \"cat\" does not exist"}".
I am a bit stumped at how to properly assign "sex1" to "cat1" and "sex2" to "cat2" so that the first is Male and the second is Female.
Thank you for any input.
m.Column("Sex") as the name suggests denote column name and not the property name as you specified. So you should write m.Column("sex_id"). map.ForeignKey("cat_sex_id_fkey") is used by NH as a FK name when NH create db schema from your mapping.

Fluent NH - illegal access to loading collection

In the CategoriesTranslated collection i have this error: illegal access to loading collection.
public class Category : Entity
{
public Category()
{
CategoriesTranslated = new List<CategoryTranslated>();
}
public virtual Category Parent { get; set; }
public virtual string Name { get; set; }
public virtual IList<CategoryTranslated> CategoriesTranslated { get; set; }
}
public class CategoryTranslated : Entity
{
public CategoryTranslated()
{
}
public virtual Category Category { get; set; }
public virtual LanguageType Language { get; set; }
public virtual string Name { get; set; }
}
public void Override(AutoMapping<Category> mapping)
{
mapping.HasMany(x => x.CategoriesTranslated)
.Inverse()
.Cascade.All();
}
public void Override(AutoMapping<CategoryTranslated> mapping)
{
mapping.References(x => x.Category);
}
The SQL:
CREATE TABLE Category(
[Id] smallint primary key identity(1,1),
[Parent] smallint null,
[Name] varchar(50) not null unique,
)
alter table [Category] add CONSTRAINT fk_Category_Category
FOREIGN KEY(Parent) references Category (Id)
go
CREATE TABLE CategoryTranslated(
[Id] smallint primary key identity(1,1),
[Category] smallint not null,
[Language] tinyint not null,
[Name] varchar(50) not null,
)
alter table [CategoryTranslated] add CONSTRAINT fk_CategoryTranslated_Category
FOREIGN KEY(Category) references Category (Id)
go
Where is it wrong?
UPDATE
The links to the hbm generater:
Category:
http://uploading.com/files/fmb71565/SubmitSiteDirectory.Core.Category.hbm.xml/
Category translated:
http://uploading.com/files/9c9aaem9/SubmitSiteDirectory.Core.CategoryTranslated.hbm.xml/
I am guessing it has to do with the creation of the list inside the constructor, especially if you left a default ctor for NHib. And that NHib is trying to set the list before it's created. The other complication here is that you have a bi-directional relationship, and CategoryTranslated may be trying to get at the list before its created too.
I doubt this is the only solution, but here is a pattern I use that should solve the error:
/// <summary>Gets the ....</summary>
/// <remarks>This is what is available to outside clients of the domain.</remarks>
public virtual IEnumerable<CategoryTranslated> CategoriesTranslated{ get { return _InternalCategoriesTranslated; } }
/// <summary>Workhorse property that maintains the set of translated categories by:
/// <item>being available to <see cref="Category"/> to maintain data integrity.</item>
/// <item>lazily instantiating the <see cref="List{T}"/> when it is needed.</item>
/// <item>being the property mapped to NHibernate, the private <see cref="_categoriesTranslated"/> field is set here.</item>
/// </list>
/// </summary>
protected internal virtual IList<Category> _InternalCategoriesTranslated
{
get { return _categoriesTranslated?? (_categoriesTranslated= new List<Category>()); }
set { _categoriesTranslated= value; }
}
private IList<StaffMember> _categoriesTranslated;
Now you need to set your mapping to access the private field, so assuming you use my casing preferences here, you'd have:
public void Override(AutoMapping<Category> mapping)
{
mapping.HasMany(x => x.CategoriesTranslated)
.Inverse()
.Access.CamelCaseField(CamelCasePrefix.Underscore)
.Cascade.All();
}
HTH,
Berryl
EDIT ============
The _Internal collection also gives the child of of the bi-directional relationship, CategoryTranslated in this case, a hook, as shown in the code below:
public virtual CategoryTranslated CategoryTranslated
{
get { return _categoryTranslated; }
set
{
if (_categoryTranslated!= null)
{
// disassociate existing relationship
_categoryTranslated._InternalCategoryTranslated.Remove(this);
}
_categoryTranslated= value;
if (value != null)
{
//make the association
_categoryTranslated._InternalCategoryTranslated.Add(this);
}
}
}
private CategoryTranslated _categoryTranslated;

Specify a Fluent NHibernate automapping to add a unique constraint to all entities

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.