This is very similar to my previous question: FluentNHibernate: How to translate HasMany(x => x.Addresses).KeyColumn("PersonId") into automapping
Say I have these models:
public class Person
{
public virtual int Id { get; private set; }
public virtual ICollection<Address> Addresses { get; private set; }
}
public class Address
{
public virtual int Id { get; private set; }
public virtual Person Owner { get; set; }
}
I want FluentNHibernate to create the following tables:
Person
PersonId
Address
AddressId
OwnerId
This can be easily achieved by using fluent mapping:
public class PersonMapping : ClassMap<Person>
{
public PersonMapping()
{
Id(x => x.Id).Column("PersonId");
HasMany(x => x.Addresses).KeyColumn("OwnerId");
}
}
public class AddressMapping : ClassMap<Address>
{
public AddressMapping()
{
Id(x => x.Id).Column("AddressId");
References(x => x.Person).Column("OwnerId");
}
}
I want to get the same result by using auto mapping. I tried the following conventions:
class PrimaryKeyNameConvention : IIdConvention
{
public void Apply(IIdentityInstance instance)
{
instance.Column(instance.EntityType.Name + "Id");
}
}
class ReferenceNameConvention : IReferenceConvention
{
public void Apply(IManyToOneInstance instance)
{
instance.Column(string.Format("{0}Id", instance.Name));
}
}
// Copied from #Fourth: https://stackoverflow.com/questions/6091290/fluentnhibernate-how-to-translate-hasmanyx-x-addresses-keycolumnpersonid/6091307#6091307
public class SimpleForeignKeyConvention : ForeignKeyConvention
{
protected override string GetKeyName(Member property, Type type)
{
if(property == null)
return type.Name + "Id";
return property.Name + "Id";
}
}
But it created the following tables:
Person
PersonId
Address
AddressId
OwnerId
PersonId // this column should not exist
So I added a AutoMappingOverride:
public class PersonMappingOverride : IAutoMappingOverride<Person>
{
public void Override(AutoMapping<Person> mapping)
{
mapping.HasMany(x => x.Addresses).KeyColumn("OwnerId");
}
}
This correctly solved the problem. But I want to get the same result using attribute & convention. I tried:
public class Person
{
public virtual int Id { get; private set; }
[KeyColumn("OwnerId")]
public virtual ICollection<Address> Addresses { get; private set; }
}
class KeyColumnAttribute : Attribute
{
public readonly string Name;
public KeyColumnAttribute(string name)
{
Name = name;
}
}
class KeyColumnConvention: IHasManyConvention
{
public void Apply(IOneToManyCollectionInstance instance)
{
var keyColumnAttribute = (KeyColumnAttribute)Attribute.GetCustomAttribute(instance.Member, typeof(KeyColumnAttribute));
if (keyColumnAttribute != null)
{
instance.Key.Column(keyColumnAttribute.Name);
}
}
}
But it created these tables:
Person
PersonId
Address
AddressId
OwnerId
PersonId // this column should not exist
Below is the rest of my code:
ISessionFactory sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(connectionString))
.Mappings(m =>
m.AutoMappings.Add(AutoMap.Assemblies(typeof(Person).Assembly)
.Conventions.Add(typeof(PrimaryKeyNameConvention))
.Conventions.Add(typeof(PrimaryKeyNameConvention))
.Conventions.Add(typeof(ReferenceNameConvention))
.Conventions.Add(typeof(SimpleForeignKeyConvention))
.Conventions.Add(typeof(KeyColumnConvention)))
//m.FluentMappings
// .Add(typeof (PersonMapping))
// .Add(typeof (AddressMapping))
)
.ExposeConfiguration(BuildSchema)
.BuildConfiguration()
.BuildSessionFactory();
Any ideas? Thanks.
Update:
The test project can be downloaded from here.
Sigh... Learning NHibernate is really a hair pulling experience.
Anyway I think I finally figured out how to solve this problem: Just remove the SimpleForeignKeyConvention and everything will work fine.
It seems the SimpleForeignKeyConvention conflicts with both ReferenceKeyConvention & KeyColumnConvention. It has higher priority than KeyColumnConvention but lower priority than ReferenceKeyConvention.
public class SimpleForeignKeyConvention : ForeignKeyConvention
{
protected override string GetKeyName(Member property, Type type)
{
if(property == null)
// This line will disable `KeyColumnConvention`
return type.Name + "Id";
// This line has no effect when `ReferenceKeyConvention` is enabled.
return property.Name + "Id";
}
}
I've tested your classes with FHN's auto-mapping feature and it does not create that second PersonId on Address table.
I'm using FHN v1.2.0.721 from here
Related
I am trying to create my own foreign key convention that will name the FK in "FK_SourceTable_TargetTable" format.
However, when I run it I end up with two foreign keys instead of one.
My custom foreign key convention looks like this:
public class OurForeignKeyConvention : ForeignKeyConvention
{
protected override string GetKeyName(Member property, Type type)
{
if (property == null)
return string.Format("FK_{0}Id", type.Name); // many-to-many, one-to-many, join
if (property.Name == type.Name)
return string.Format("FK_{0}_{1}", property.DeclaringType.Name, type.Name);
return string.Format("FK_{0}_{1}_{2}", property.DeclaringType.Name, property.Name, type.Name);
}
}
My code to exercise it:
[TestMethod]
public void ShouldBeAbleToBuildSchemaWithOurConventions()
{
var configuration = new Configuration();
configuration.Configure();
Fluently
.Configure(configuration)
.Mappings(m => m.FluentMappings
.AddFromAssemblyOf<Widget>()
.Conventions.Add<OurForeignKeyConvention>()
)
.BuildSessionFactory();
new SchemaExport(configuration).Create(false, true);
}
My classes and mappings:
public class Widget
{
public virtual int Id { get; set; }
public virtual string Description { get; set; }
public virtual WidgetType Type { get; set; }
public virtual ISet<WidgetFeature> Features { get; set; }
}
public class WidgetFeature
{
public virtual int Id { get; set; }
public virtual Widget Widget { get; set; }
public virtual string FeatureDescription { get; set; }
}
public class WidgetMap : ClassMap<Widget>
{
public WidgetMap()
{
Id(w => w.Id);
Map(w => w.Description);
HasMany(w => w.Features).Cascade.AllDeleteOrphan().Inverse();
}
}
public class WidgetFeatureMap : ClassMap<WidgetFeature>
{
public WidgetFeatureMap()
{
Id(w => w.Id);
Map(w => w.FeatureDescription);
References(w => w.Widget);
}
}
The end result is two foreign keys, one called what I want - FK_WidgetFeature_Widget - and another one called FK_WidgetId.
If I change OurForeignKeyConvention to always return the same name regardless of whether the "property" parameter is null then I correctly get a single FK - but I then cannot get the "SourceTable" part of my FK name.
Can anyone explain what I am doing wrong here? Why is GetKeyName called twice? And why does one of the calls not provide a value for the "property" parameter?
Doh. ForeignKeyConvention provides the name for the FK column. What I should have been using is the IHasManyConvention, which can be used to name the FK constraint itself.
public class OurForeignKeyConstraintNamingConvention : IHasManyConvention
{
public void Apply(IOneToManyCollectionInstance instance)
{
instance.Key.ForeignKey(string.Format("FK_{0}_{1}", instance.Relationship.Class.Name, instance.EntityType.Name));
}
}
I have 2 classes:
public class MyBaseClass
{
public virtual int Id { get; set; }
public virtual string BaseProperty { get; set; }
}
public class MyClass : MyBaseClass
{
public virtual string ChildProperty { get; set; }
}
I want to map each of them to its own table (fluent NH). How to do it with no discriminator type column added to [MyBaseClass] table? So I expect [MyBaseClass] table consists of BaseProperty and Id columns only, MyClass consists of Id, BaseProperty and ChildProperty columns.
Thanks
You can try to put IgnoreBase to on MyBaseClass. It will say for FNH to map those classes independently
I've just found this (http://wiki.fluentnhibernate.org/Fluent_mapping#Components):
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Child : Parent
{
public string AnotherProperty { get; set; }
}
If you wanted to map this as a
table-per-subclass, you'd do it like
this:
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
Id(x => x.Id);
Map(x => x.Name);
}
}
public class ChildMap : SubclassMap<Child>
{
public ChildMap()
{
Map(x => x.AnotherProperty);
}
}
So looks like this approach does not require any Db changes like adding special fields to my tables. The only problem is I do not know how to do the same in AutoMapping with Override statements. We do mapping this way:
public class AutoMappingConfiguration : DefaultAutomappingConfiguration
{
public override bool IsDiscriminated(Type type)
{
return true;
}
public override bool ShouldMap(Type type)
{
return type.In(typeof(MyBaseClass),typeof(MyClass),...)
}
...
}
FluentNHibernate.Automapping.AutoPersistenceModel Instance =
AutoMap.AssemblyOf<MyBaseClass>(new AutoMappingConfiguration())
.Override<MyBaseClass>(m =>
{
...
}
}
So I'm not sure how to apply SubClass instruction in my case. Any advise?
Thanks.
I am having an issue with using Fluent NHibernate automapping with Inheritance. Below is my entity setup (abbreviated for simplicity). I have configured Fluent NHibernate to create 1 class for the hierarchy with a discriminator column. The automapping appears to be working correctly as when I generate a database, one table is created named "AddressBase" with a discriminator column that signals what type of address each row is.
The problem lies in the face that when I call the method "GetPrimaryBillingAddress()" on the UserAccount class, instead of just querying Billing Addresses, NHibernate is creating a query that looks at both Billing and Shipping Addresses. It doesn't take into account the discriminator at all. I am assuming there is some sort of configuration I can set but have not been able to find anything.
public abstract class AddressBase : ActiveRecord<AddressBase>
{
public virtual long Id { get; set; }
public virtual string Address1 { get; set; }
}
public class AddressBilling : AddressBase
{
public class TypedQuery : ActiveRecordQuery<AddressBilling> { }
public virtual bool IsPrimary { get; set; }
}
public class AddressShipping : AddressBase
{
public class TypedQuery : ActiveRecordQuery<AddressShipping> { }
[Display(Name = "Is Primary")]
public virtual bool IsPrimary { get; set; }
}
public class UserAccount : ActiveRecord<UserAccount>
{
public virtual long Id { get; set; }
public virtual IList<AddressBilling> BillingAddresses { get; set; }
public virtual IList<AddressShipping> ShippingAddresses { get; set; }
public UserAccount()
{
BillingAddresses = new List<AddressBilling>();
ShippingAddresses = new List<AddressShipping>();
}
public virtual AddressBilling GetPrimaryBillingAddress()
{
if (BillingAddresses.Any(x => x.IsPrimary))
{
return BillingAddresses.Single(x => x.IsPrimary);
}
return BillingAddresses.FirstOrDefault();
}
public virtual AddressShipping GetPrimaryShippingAddress()
{
if (ShippingAddresses.Any(x => x.IsPrimary)) {
return ShippingAddresses.Single(x => x.IsPrimary);
}
return ShippingAddresses.FirstOrDefault();
}
}
UPDATE:
Here is the Mapping override functions used in the automapping:
private static FluentConfiguration GetFluentConfiguration(string connectionStringName = "CS")
{
var autoMapping = AutoMap
.AssemblyOf<Product>(new Mapping.AutoMappingConfiguration())
.Conventions.Setup(c =>
{
c.Add<Mapping.ForeignKeyConvention>();
c.Add<Mapping.DiscriminatorConvention>();
})
.IgnoreBase<AddressBilling.TypedQuery>()
.IgnoreBase<AddressShipping.TypedQuery>()
.IncludeBase<AddressBase>();
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005.ConnectionString(c => c.FromConnectionStringWithKey(connectionStringName)))
.Mappings(m => m.AutoMappings.Add(autoMapping));
}
public class AutoMappingConfiguration : DefaultAutomappingConfiguration
{
public override bool ShouldMap(Type type)
{
var isStatic = type.IsAbstract && type.IsSealed;
return type.Namespace == typeof(Entities.Product).Namespace && !isStatic;
}
public override bool IsDiscriminated(Type type)
{
if (type == (typeof(Entities.AddressBase))) {
return true;
}
return false;
}
public override string GetDiscriminatorColumn(Type type)
{
return "Type";
}
public class DiscriminatorConvention : ISubclassConvention
{
public void Apply(ISubclassInstance instance)
{
//Address
if (instance.Name == typeof(AddressBilling).AssemblyQualifiedName)
{
instance.DiscriminatorValue(Enums.AddressType.BillingAddress);
}
else if (instance.Name == typeof(AddressShipping).AssemblyQualifiedName)
{
instance.DiscriminatorValue(Enums.AddressType.ShippingAddress);
}
}
}
Thanks!
Please, try to change your class UserAccount like this:
public class UserAccount : ActiveRecord<UserAccount>
{
public virtual IList<AddressBase> Addresses { get; set; }
public virtual IList<AddressBilling> BillingAddresses { get {return this.Addresses.OfType<AddressBilling>();} }
public virtual IList<AddressShipping> ShippingAddresses { get {return this.Addresses.OfType<AddressShipping>();} }
// ...
}
Of course, only Addresses property should be mapped here.
I have a class called Worker
public class Worker : BaseEntity
{
public virtual int WorkerID { get; set; }
public virtual string Name { get; set; }
public virtual IList<Indemnification> Indemnifications { get; set; }
}
public class Indemnification : BaseEntity
{
public virtual int IndemnificationID { get; set; }
public virtual string IndemnificationNumber { get; set; }
//another properties
}
i am using automapping with some conventions
var mappings = new AutoPersistenceModel();
mappings.AddEntityAssembly(typeof(Worker).Assembly).Where(GetAutoMappingFilter);
mappings.Conventions.Setup(GetConventions());
mappings.Setup(GetSetup());
private Action<IConventionFinder> GetConventions()
{
return c =>
{
c.Add<PrimaryKeyConvention>();
c.Add<HasManyConvention>();
c.Add<TableNameConvention>();
c.Add<CustomForeignKeyConvention>();
c.Add<SubClassConvention>();
};
}
public class PrimaryKeyConvention : IIdConvention
{
public void Apply(FluentNHibernate.Conventions.Instances.IIdentityInstance instance)
{
instance.Column(instance.EntityType.Name + "ID");
instance.UnsavedValue("0");
}
}
public class HasManyConvention : IHasManyConvention
{
public void Apply(FluentNHibernate.Conventions.Instances.IOneToManyCollectionInstance instance)
{
instance.Key.Column(instance.EntityType.Name + "ID");
instance.Cascade.AllDeleteOrphan();
}
}
public class TableNameConvention : IClassConvention
{
public void Apply(FluentNHibernate.Conventions.Instances.IClassInstance instance)
{
instance.Table(instance.EntityType.Name);
}
}
public class CustomForeignKeyConvention : ForeignKeyConvention
{
protected override string GetKeyName(Member property, Type type)
{
return type.Name + "ID";
}
}
public class SubClassConvention : IJoinedSubclassConvention
{
public void Apply(FluentNHibernate.Conventions.Instances.IJoinedSubclassInstance instance)
{
instance.Table(instance.EntityType.Name);
instance.Key.Column(instance.EntityType.BaseType.Name + "ID");
}
}
the problem is when i save Worker with a list of Indemnifications:
the worker is saved, and so the Indemnifications but the foreign key (WorkerID) in
the Indemnification table is null????
I figured out the problem:
when you need to save an entity which has (one to many) relationship, you need to open a transaction and commit it :).
Session.BeginTransaction();
Session.Save(entity);
Session.CommitTransaction();
DidnĀ“t you wonder why the automapping allowed foreign keys that are created for a one-to-many relation ship to be null in the first place?
So in your example why does the column "workerId" in the table "Indemnification" not have the not null constraint added to it?
I just came across the the problem and I think even though it can be handled in code, it should not be possible at all to insert a null value, right? Any solution for that?
I am using Fluent NHibernate to map the following classes:
public abstract class DomainObject
{
public virtual int Id { get; protected internal set; }
}
public class Attribute
{
public virtual string Name { get; set; }
}
public class AttributeRule
{
public virtual Attribute Attribute { get; set; }
public virtual Station Station { get; set; }
public virtual RuleTypeId RuleTypeId { get; set; }
}
public class Station : DomainObject
{
public virtual IList<AttributeRule> AttributeRules { get; set; }
public Station()
{
AttributeRules = new List<AttributeRule>();
}
}
My Fluent NHibernate mappings look like this:
public class AttributeMap : ClassMap<Attribute>
{
public AttributeMap()
{
Id(o => o.Id);
Map(o => o.Name);
}
}
public class AttributeRuleMap : ClassMap<AttributeRule>
{
public AttributeRuleMap()
{
Id(o => o.Id);
Map(o => o.RuleTypeId);
References(o => o.Attribute).Fetch.Join();
References(o => o.Station);
}
}
public class StationMap : ClassMap<Station>
{
public StationMap()
{
Id(o => o.Id);
HasMany(o => o.AttributeRules).Inverse();
}
}
I would like to order the AttributeRules list on Station by the Attribute.Name property, but doing the following does not work:
HasMany(o => o.AttributeRules).Inverse().OrderBy("Attribute.Name");
I have not found a way to do this yet in the mappings. I could create a IQuery or ICriteria to do this for me, but ideally I would just like to have the AttributeRules list sorted when I ask for it.
Any advice on how to do this mapping?
I think the OrderBy-method takes in the string that it inserts to the generated SQL-clause. So just doing
HasMany(o => o.AttributeRules).Inverse().OrderBy("Name");
Where the "Name" is the name of the column that contains Attribute's name. It should be in the column list because Attribute is joined to the AttributeRule.
Did you solve this other way? Please share.