My custom ForeignKeyConvention is resulting in two foreign keys being created instead of one - nhibernate

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

Related

Entity Framework Code First Many-to-Many relationship and inheritance

Forgive me if this question has been answered somewhere, I have been having a hard time finding a solution for this problem.
I am trying to set up EF Code First on an MVC4 Project. I have a User and Customer that both inherit from Person. I then have a Template object that has a Many-to-Many relationship with Customer and a One-to-Many relationship with User. Here is how I have it set up:
MODELS
public class Person
{
[Key]
public int PersonID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string FullName
{
get
{
return String.Format("{0} {1}", FirstName, LastName);
}
}
public string Email { get; set; }
public virtual List<Template> Templates { get; set; }
}
public class User : Person
{
....
}
public class Customer : Person
{
....
}
public class Template
{
public int TemplateId { get; set; }
public string TemplateName { get; set; }
public virtual List<Customer> Customers { get; set; }
[ForeignKey("User")]
public int UserId { get; set; }
public virtual User User { get; set; }
}
CONTEXT
public class ProjectContext : DbContext
{
public ProjectContext()
: base("name=ProjectDB")
{
}
public DbSet<Template> Templates { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Person> People { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions
.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Template>()
.HasMany(x => x.Customers)
.WithMany(x => x.Templates)
.Map(x => x.MapLeftKey("TemplateId")
.MapRightKey("PersonId")
.ToTable("TemplateCustomer")
);
}
}
If I remove the Person DBSet out of the context this works fine but sets up TPT inheritance. I would like to use TPH inheritance, but when I enable migrations with the Person DBSet in the context it chokes:
NavigationProperty 'Templates' is not valid. Type 'MvcProject.Models.Customer' of FromRole 'Template_Customers_Target' in AssociationType 'MvcProject.Models.Template_Customers' must exactly match with the type 'MvcProject.Models.Person' on which this NavigationProperty is declared on.
Where am I going wrong here?
You cannot inherit navigation properties from a base entity. They always must be declared in the class the other end of the relationship is refering to.
Template.Customers is refering to Customer (not to Person), hence the inverse navigation property Templates must be declared in Customer (not in Person)
Template.User is refering to User (not to Person), hence the inverse navigation property Templates must be declared in User (not in Person)
So, basically you must move the Templates collection from Person into both derived classes:
public class Person
{
// no Templates collection here
}
public class User : Person
{
//...
public virtual List<Template> Templates { get; set; }
}
public class Customer : Person
{
//...
public virtual List<Template> Templates { get; set; }
}
Then you can define the two relationships with Fluent API like so:
modelBuilder.Entity<Template>()
.HasMany(t => t.Customers)
.WithMany(c => c.Templates) // = Customer.Templates
.Map(x => x.MapLeftKey("TemplateId")
.MapRightKey("PersonId")
.ToTable("TemplateCustomer"));
modelBuilder.Entity<Template>()
.HasRequired(t => t.User)
.WithMany(u => u.Templates) // = User.Templates
.HasForeignKey(t => t.UserId);
Change your HasMany selector to People:
modelBuilder.Entity<Template>()
.HasMany(x => x.People) // here
.WithMany(x => x.Templates)
.Map(x => x.MapLeftKey("TemplateId")
.MapRightKey("PersonId")
.ToTable("TemplateCustomer")
);

NHibernate One-To-One mapping using Mapping by Code

I have an class structure like this:
public class BaseEntity
{
public Guid Id { get; set; }
}
// CREATE TABLE Project (Id, Name)
public class Project : BaseEntity
{
public ProjectProperties Properties { get; set; }
public string Name { get; set; }
}
// CREATE TABLE ProjectProperties (Id, Markup)
// ForeignKey from ProjectProperties.Id -> Project.Id
public class ProjectProperties : BaseEntity
{
public int Markup { get; set; }
}
What is the correct way to map this using NH 3.2 and Mapping By Code? I can't find examples where the 1:1 relationship is through the PKs.
you can use Join since the primary keys match. it doesn't even need an own Id because it is dependant from Project
public class ProjectProperties
{
public int Markup { get; set; }
}
// in ProjectMapping
Join("ProjectProperties", join =>
{
join.Key("Id");
join.Component(x => x.ProjectProperties, c =>
{
c.Property(x => x.Markup);
}
});
I think you should use this code.
OneToOne(x => x.Properties,
x => x.PropertyReference(typeof(ProjectProperties).GetProperty("Properties")));

FluentNhibernate many-to-many and Inverse()

I have the following database tables defined:
Club: Id, Name
Member: Id, Name
ClubMember: ClubId, MemberId
I have the following entity Classes defined:
public class Club() {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Member> Members { get; set; }
}
public class Member() {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Club> Clubs { get; set; }
}
I have the following overrides defined:
public class MemberOverride : IAutoMappingOverride<Member>
{
public void Override(AutoMapping<Member> mapping_)
{
mapping_
.HasManyToMany(x_ => x_.Clubs)
.ParentKeyColumn("MemberId")
.ChildKeyColumn("ClubId")
.Cascade.All()
.Table("ClubMembers");
}
}
public class ClubOverride : IAutoMappingOverride<Club>
{
public void Override(AutoMapping<Club> mapping_)
{
mapping_
.HasManyToMany(x_ => x_.Members)
.ParentKeyColumn("ClubId")
.ChildKeyColumn("MemberId")
.Inverse()
.Table("ClubMembers");
}
}
I can see from my overrides that the Inverse on the ClubOverride means you cannot do the following
session.Save(club.Members.Add(member));
but this works:
session.Save(member.Clubs.Add(club);
But it doesn't make logical sense. I want to be able to save either the club with members or member with clubs.
Am I trying to do something impossible with FluentNhibernate?
TIA
Yes, you're right, that's not possible. But it's not a question of FluentNhibernate, NHibernate works like that.
Only one side is the owner of the relation and on charge of adding elements.
From official documentation:
Changes made only to the inverse end of the association are not persisted. This means that NHibernate has two representations in memory for every bidirectional association, one link from A to B and another link from B to A. This is easier to understand if you think about the .NET object model and how we create a many-to-many relationship in C#:
You can create add or remove methods on your entities that will help accomplish this:
public class Club() {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
private IList<Member> members;
public virtual IEnumerable<Member> Members { get { return members.Select(x => x); } }
public Club() {
members = new List<Member>();
}
public virtual void AddMember(Member member){
if (members.Contains(member))
return;
members.Add(user);
member.AddClub(this);
}
public virtual void RemoveMember(Member member){
if (!members.Contains(member))
return;
members.Remove(member);
member.RemoveClub(this);
}
}

FluentNHibernate: Automapping OneToMany relation using attribute and convention

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

fluent-nhibernate automapping foreign key inserts null.

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?