[auto-mapping] why is the primary key convention applied only once???

BiBi's Avatar

BiBi

03 May, 2010 02:33 PM via web

Hi all,

I use the simple primary key convention from the examples to define how my PKs are represented on the database:

public class MyPrimaryKeyConvention : IIdConvention
{        
    // specify how the primary key looks like on the database
    public void Apply(IIdentityInstance instance)
    {
        Console.WriteLine("Primary  Key Convention applied:"+ instance.EntityType.Name + "Id");
        instance.Column(instance.EntityType.Name + "Id");
    }
}

To find the Id properties in my classes I use the following line when setting up my auto-mapping:
.Setup(s =>s.FindIdentity = property => property.Name == property.DeclaringType.Name + "Id";)

My domain model is also very simple:

public interface IMyClass1 { }

public abstract class MyClass1 : IMyClass1 {
     public int MyClass1Id  {get; protected set;}
}

public class MyClass2 : MyClass1 {
    public string myText;
}

The database schema:

CREATE TABLE MyClass1( 
     myClass1Id int NOT NULL
);

CREATE TABLE MyClass2( 
     myText ntext,
     myClass2Id int NOT NULL
);

The problem now is that MyClass2 inherits the ID property from MyClass1, which is by convention MyClass1Id. Therefore the apply method I implemented for the primary key convention is called only once. But I also need it to tell Fluent that the primary key on my second table MyClass2 is called now MyClass2Id ... ???


Finally, this is how I configured Fluent:

Fluently.Configure()
                        .Database(
                            MsSqlConfiguration.MsSql2005
                                .ConnectionString("Server=sql123;Initial catalog=test;User ID=user;Password=pass;")
                       ).Mappings(m =>
                            m.AutoMappings
                                .Add(AutoMap.AssemblyOf<IMyClass1>()
                                    .IncludeBase<MyClass1>()
                                    .Where(t => t.GetInterface("IMyClass1") == typeof(IMyClass1))
                                    .Conventions.Add<MyPrimaryKeyConvention >()
                                    .Conventions.Add<MyForeignKeyConvention>()
                                    .Conventions.Add(DefaultCascade.All())
                                    .Conventions.Add(DefaultLazy.Never())
                                    .Setup(s =>    {
                                                    s.FindIdentity = property => property.Name == property.DeclaringType.Name + "Id";                                                                  
                                                }
                                        )
                                    ))

                        .BuildSessionFactory();`

Hope somebody can help me with that... :-(

  1. 2 Posted by Devon Lazarus on 04 May, 2010 02:40 PM

    Devon Lazarus's Avatar

    Your primary key convention is being applied as expected, however, you're going to have to look into implementing IJoinedSubclassConvention.

    Take a look at this post for an example:

    http://support.fluentnhibernate.org/discussions/help/63-ijoinedsubc...

    Note also, that this won't solve your problem. There appears to be a bug or a misunderstanding by several developers on how to create a convention for the PK of a joined subclass entity.

    Hopefully a final answer will be coming soon.

    -devon

  2. 3 Posted by Devon Lazarus on 04 May, 2010 04:48 PM

    Devon Lazarus's Avatar

    Okay, after recompiling my code, IJoinedSubclassConvention is working as we expect.

    To answer your question more thoroughly then, you'll need to implement that interface for for your subclassed entities. Here is an example using your code above:

    public class MyJoinedSubclassConvention : IJoinedSubclassConvention
    {
        #region IConvention<IJoinedSubclassInspector,IJoinedSubclassInstance> Members
    
        public void Apply(IJoinedSubclassInstance instance)
        {
            instance.Table(instance.EntityType.Name);
            instance.Key.Column(instance.EntityType.Name + "Id");
        }
    
        #endregion
    }
    

    Then you'll just need to import the IConvention during the fluent configuration:

    ...
    .Conventions.Add<MyPrimaryKeyConvention >()
    .Conventions.Add<MyForeignKeyConvention>()
    .Conventions.Add<MyJoinedSubclassConvention>()
    ...
    

    And you should be up and running.

    hth,

    -devon

  3. 4 Posted by BiBi on 04 May, 2010 11:58 PM

    BiBi's Avatar

    Thanks for your reply!

    I've added the convention and imported it in the fluent configuration as you said. But it still produced wrong INSERTS with MyClass1Id for MyClass2 table instead of MyClass2Id. So I checked if the new convention is called what has been the case. So I updated my Fluent Nhibernate binaries from build 633 to the latest #647

    Now I get a quite new exception:

    Inner Exception: FluentNHibernate.Cfg.FluentConfigurationException: An invalid or incomplete configuration was used while creating a SessionFactory. Check PotentialReasons collection, and InnerException for more detail.
    ---> System.InvalidOperationException: Tried to add property 'MyClass1Id' when already added. at FluentNHibernate.MappingModel.MappedMembers.AddProperty(PropertyMapping property) in d:\Builds\FluentNH\src\FluentNHibernate\MappingModel\MappedMembers.c s:Line 91.
    at FluentNHibernate.MappingModel.ClassBased.ClassMappingBase.AddProperty(PropertyMapping property) in d:\Builds\FluentNH\src\FluentNHibernate\MappingModel\ClassBased\ClassMappingBase.cs:Line 84

    Remarkably this configuration line is marked as deprecated now:
    .Setup(s =>s.FindIdentity = property => property.Name == property.DeclaringType.Name + "Id";

    What happened?

  4. 5 Posted by Devon Lazarus on 05 May, 2010 02:26 PM

    Devon Lazarus's Avatar

    We are not using the latest builds, we are using 1.0-RTM so there might be some differences.

    As for why that Setup() method is deprecated, you'll probably have to post a different question so James, Paul, or Hudson can see it.

    However, I will tell you that lambda (which is straight from the documentation) searches through all properties to find the identity. I use this one instead:

    c.FindIdentity = type => type.Name == "Id";

    This way I'm not searching through the entire properties collection just to find the identifier, I'm following my convention instead.

    Also, I don't think these two problems are related. I think the insert of MyClassId1 into MyClass2 table is related to your conventions.

    My recommendation, and what I do in this situation, is to write an Integration Test that allows you to call the .Export() method in the FluentConfiguration.Mappings() method and Export your mappings to .hbm.xml files. See the documentation here:

    http://wiki.fluentnhibernate.org/Fluent_configuration#Exporting_map...

    This way, you can play around with the conventions and see if they get applied. Producing the .hbm.xml file is the easiest way for me to check and see that my mappings are working properly.

    If you can produce a hbm.xml file, post it here and I'll take a quick look. Sorry I can't be of more help here.

    -devon

  5. 6 Posted by Devon Lazarus on 05 May, 2010 02:28 PM

    Devon Lazarus's Avatar

    This may be related:

    http://support.fluentnhibernate.org/discussions/help/128-breaking-c...

    Another user is reporting the same problem after updating from one build to the next.

    -devon

  6. Support Staff 7 Posted by James Gregory on 05 May, 2010 02:37 PM

    James Gregory's Avatar

    I think Devon may be right that this is related to the other support question. I'll be investigating this issue as soon as possible.

    As for the depreciation of the method, don't worry about it. The Automapper is going to be gently pushed away from the big method-chain lump that it can grow into with the current design, but there won't be any breaking changes until 2.0 (which is quite a way off yet). On release of 1.1 these changes will be documented and explained, until then just ignore it.

  7. 8 Posted by BiBi on 05 May, 2010 05:04 PM

    BiBi's Avatar

    Thanx a lot for your answers! The link you posted sounds exactly like the problem I have.

    So, I'll test the joined subclass convention with 1.0-RTM tomorrow, just to verify that it works for me too. Also I will dig into Integration Testing (never did that before) as soon as possible, thanks for the link!

    Just to be sure that there is no 'design bug' in my little domain model, I've attached the class and table diagrams. I'd be happy if you could quickly review it :-)

  8. 9 Posted by BiBi on 07 May, 2010 04:55 PM

    BiBi's Avatar

    Thanx for the patch James! Your latest build did fix the "already added" exception. So after trying around, the situation is now the following:

    The JoinedSubclassConvention works now, but I still get an INSERT error because the value for a referenced aggregaton class is NULL. I guess this is because I needed to remove my ForeignKeyConvention. It seems that it can be used only with a fixed primary key name in this case, because when one must imlement the GetKeyName method there is only the Member and the type parameter. In case of inheritance the member parameter is NULL and then I cannot access the class I'm currently in. I can only use type.DeclaringType in this case but this gives me only the baseclass where the key property has been defined (so I would get again the wrong name for the PK column).

    After 10 hours of trying I feel really stuck now. I tried several conventions to add but nothing worked really. Please help me to find the last piece in the puzzle, I really believe I can't make it alone... :-(

    Here is the failing query:
    INSERT INTO UserIdentity (userName, password, anotherClassId, exchan geableBusinessObjectId) VALUES (@p0, @p1, @p2, @p3);@p0 = 'TEST150', @p1 = '1234
    ', @p2 = NULL, @p3 = 76687eb0-05ce-486d-811d-9d6f0135880b

    And here all the conventions I apply(including the problematic foreign key convention):
    ' public class XBOPrimaryKeyConvention : IIdConvention

    {        
        // Camel Case primary key property
        public void Apply(IIdentityInstance instance)
        {
            instance.Column(XBOConventionHelper.XBOColumnCamelCaseName(instance.Name));
        }
    }
    
    public class CustomForeignKeyConvention : ForeignKeyConvention
    {
        protected override string GetKeyName(FluentNHibernate.Member property, Type type)
        {
            // what should I do here??? I cannot return classname+"Id" because I have nothing else than type now???**
            if (property == null)
            {
                Console.WriteLine("property = NULL");
                return XBOConventionHelper.XBOKeyColumnName(type.Name);
            }
            else
            {
                Console.WriteLine("property = " + property.Name);
                return XBOConventionHelper.XBOKeyColumnName(type.Name);
            }
        }
    }
    
    // defines how foreign key columns looks like on the database
    public class ReferenceConvention : IReferenceConvention
    {
        public void Apply(IManyToOneInstance instance)
        {
            instance.Column(XBOConventionHelper.XBOKeyColumnName(instance.Property.PropertyType.Name));
        }
    }
    
    // defines how joined subclasses are represented on the database
    public class XBOJoinedSubclassConvention : IJoinedSubclassConvention
    {
        #region IConvention<IJoinedSubclassInspector,IJoinedSubclassInstance> Members
    
        /*
         * joined subclasses are represented with 
         * - an own table for each class (class name in pascal case naming)
         * - own primary key (class name in camel case +"Id")
        */
        public void Apply(IJoinedSubclassInstance instance)
        {
            instance.Table(instance.EntityType.Name);
            instance.Key.Column(XBOConventionHelper.XBOKeyColumnName(instance.EntityType.Name));
        }
    
        #endregion
    }
    
    // define how class properties are represented on the database
    public class XBOPropertyConvention : IPropertyConvention, IPropertyConventionAcceptance
    {
        // apply to all properties of a class (filters based on type or name can be added here)
        public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
        {
            criteria.Expect(x => true);
        }
    
        // properties are camel case columns on the database
        public void Apply(IPropertyInstance instance)
        {
            instance.Column(XBOConventionHelper.XBOColumnCamelCaseName(instance.Name));
        }
    }
    
    // Offers various helper methods for naming conventions
    public abstract class XBOConventionHelper
    {
        // converts a class name to a (pascal case) primary key
        public static string XBOKeyColumnName(string name)
        {
            return XBOColumnCamelCaseName(name) + "Id";
        }
    
        // converts a property name to camel case notation (first character in lowerCase)
        public static string XBOColumnCamelCaseName(string name)
        {
            int len = name.Length - 1;
            return (name.Substring(0, 1).ToLower() + name.Substring(1, len));
        }
    }'
    

Reply to this discussion

Preview Comments are parsed with Markdown. Help with syntax

Attached Files

    You can attach files up to 10MB

    Ten divided by two is what?