TypeConverters not appearing in generated code for Nhibernate + FluentNhibernate w. LLBLGEN Pro 3.1

Posts   
 
    
samy
User
Posts: 18
Joined: 16-Sep-2011
# Posted on: 19-Sep-2011 09:40:17   

I'm trying to sidestep the Decimal .net type that Oracle creates. I have to work with a table from which i create reverse-engineered entities, entities which i have to work on with NHibernate. (it starts here: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=20247)

I decided to use FluentNhibernate for the mappings, and since FluentNhibernate doesn't like entities keys that are not integral, i went with a typeconverter to convert the decimals to long (int64)

I'm working with LLBLGEN Pro v3.1 on .net4. I created an assembly that i compile against .net4 which contains the following code:

using System;
using System.ComponentModel;
using NHibernate.SqlTypes;
using NHibernate.UserTypes;
using System.Data;
using NHibernate;

namespace WorkArea
{
    
    public class IntegralDecimalConverterNHibernate : TypeConverter, IUserType
    {
        public IntegralDecimalConverterNHibernate()
        {
        }

    
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            switch(sourceType.FullName)
            {
                case "System.Decimal":
                    return true;
                default:
                    return false;
            }
        }

        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            switch(destinationType.FullName)
            {
                case "System.Decimal":
                    return true;
                default:
                    return false;
            }
        }

        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            Int64 toReturn = 0;
            switch(value.GetType().FullName)
            {
                case "System.Decimal":
                    toReturn = ((Int64)value);
                    break;
                default:
                    throw new NotSupportedException("Conversion from a value of type '" + value.GetType().ToString() + "' to System.Int64 isn't supported");
            }

            return toReturn;
        }

        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if(value==null)
            {
                throw new ArgumentNullException("value", "Value can't be null");
            }

            if(! (value is Int64))
            {
                throw new ArgumentException("Value isn't of type int64", "value");
            }

                switch(destinationType.FullName)
                {
                    case "System.Decimal":
                        return (decimal)value;
                    default:
                        throw new NotSupportedException("Conversion to a value of type '" + destinationType.ToString() + "' isn't supported");
                }
        }
        
        public override object CreateInstance(ITypeDescriptorContext context, System.Collections.IDictionary propertyValues)
        {
            return ((Int64)0);
        }

        #region IUserType Members

        public object Assemble(object cached, object owner)
        {
            return DeepCopy(cached);
        }

        public object DeepCopy(object value)
        {
            return value;
        }

        public object Disassemble(object value)
        {
            return DeepCopy(value);
        }

        
        public int GetHashCode(object x)
        {
            return x == null? base.GetHashCode() : x.GetHashCode();
        }

        public bool IsMutable
        {
            get { return true; }
        }

        public object NullSafeGet(IDataReader rs, string[] names, object owner)
        {
            var rawValue = NHibernateUtil.Decimal.NullSafeGet(rs, names[0]);
            if(rawValue==null)
            {
                return null;
            }
            return ConvertFrom(null, null, rawValue);
        }

        public void NullSafeSet(System.Data.IDbCommand cmd, object value, int index)
        {
            ((IDataParameter)cmd.Parameters[index]).Value = value == null ? DBNull.Value : ConvertTo(null, null, value, typeof(Decimal));
        }

        
        public object Replace(object original, object target, object owner)
        {
            return original;
        }

        public Type ReturnedType
        {
            get { return typeof(Int64); }
        }

        public NHibernate.SqlTypes.SqlType[] SqlTypes
        {
            get
            {
                return new SqlType[] { NHibernateUtil.Decimal.SqlType};
            }
        }

        bool IUserType.Equals(object x, object y)
        {
            if(ReferenceEquals(x, y))
            {
                return true;
            }
            return (x != null && y != null) && x.Equals(y);
        }
        #endregion

    }
}

The code compiles, i copy the typeconverter to LLBLGEN typeconverter directory, and i can map my fields to the typeconverter to match the Oracle Decimal field. However, when i generate the code, the fluent mappings aren't created with the TypeConverter i defined.

However, if i apply the typeconverter to a field that is not a entity key, the mapping is created with the correct typeconverter. Is there a configuration parameter somewhere to force the use of the type converter on entity keys?

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 19-Sep-2011 10:35:13   

I couldn't reproduce it on Northwind database. I used the defacto NumericBooleanConverter and assigned it to the ProductId (PK) of the Products entity.

The property was generated as boolean, for the LLBLGen Framework as well as for NHibernate. Could you please attach a repro llblgen project file, and the TypeConverter dll.

samy
User
Posts: 18
Joined: 16-Sep-2011
# Posted on: 19-Sep-2011 10:40:22   

Here are both files attached

samy
User
Posts: 18
Joined: 16-Sep-2011
# Posted on: 19-Sep-2011 10:41:32   

I can only attach one file per message, so here's the typeconverter assembly

Attachments
Filename File size Added on Approval
SOMEI.TypeConverters.dll 10,240 19-Sep-2011 13:07.05 Approved
samy
User
Posts: 18
Joined: 16-Sep-2011
# Posted on: 19-Sep-2011 10:49:17   

Walaa, regarding your answer, the Id property is generated for my entities, but the mapping doesn't take the TypeConverter into account. What i expect to see is for example a generated EntityMap looking like this

/// <summary>Initializes a new instance of the <see cref="LibelleMap"/> class.</summary>
        public LibelleMap()
        {
            Table("\"ENVIRO\".\"LIBELLE\"");
            OptimisticLock.None();
            LazyLoad();

            Id(x=>x.Id)
                .CustomType(typeof(SOMEI.IntegralDecimalConverter))
                .Access.CamelCaseField(Prefix.Underscore)
                .Column("\"ID\"")
                .GeneratedBy.Assigned();
            Map(x=>x.Lib).Column("\"LIBELLE\"").Access.CamelCaseField(Prefix.Underscore);

            HasMany(x=>x.Tarifs)
                .Access.CamelCaseField(Prefix.Underscore)
                .Cascade.AllDeleteOrphan()
                .Fetch.Select()
                .AsSet()
                .Inverse()
                .LazyLoad()
                .KeyColumns.Add("\"LIBELLE\"");

            AdditionalMappingInfo();
        } 

But the

.CustomType(typeof(SOMEI.IntegralDecimalConverter))

doesn't appear

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 19-Sep-2011 11:04:03   

I can't open your llblgen project file. (It's looking for another typeConverter).

But anyway, I see what you mean, let me check and get back to you.

samy
User
Posts: 18
Joined: 16-Sep-2011
# Posted on: 19-Sep-2011 11:08:45   

Sorry, it was a remnant of an old typeconverter definition. Can you try this file, it should load without problems

Attachments
Filename File size Added on Approval
SOMEI.Final.llblgenproj 167,036 19-Sep-2011 11:09.09 Approved
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39908
Joined: 17-Aug-2003
# Posted on: 19-Sep-2011 12:44:25   

Looks like a bug, we'll look into it (the type converter should show up in the mapping file).

(drats... I removed the wrong attached file (meant to remove the wrong project, removed the typeconverter dll flushed . Not that important though, the issue is easy to reproduce with our own typeconverter)

(edit) thanks for adding it back simple_smile

(edit) Hbm mappings do get the typeconverter usage btw, only the fluent templates don't seem to emit them for PK fields.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39908
Joined: 17-Aug-2003
# Posted on: 19-Sep-2011 14:23:16   

There's a problem with FluentNHibernate (which also seems the case with NHibernate) and composite IDs and user types: these don't seem to be supported. Your project has at least 1: Frequencecontrat. This entity has a composite id build from two fields which have a type converter (IUsertype) however this forms a m:n relationship. In other words, this entity can't be mapped with user types, as that's not supported by NHibernate. You have to define an m:n relationship between frequencereporting and site. When you do that, the entity Frequencecontract won't be generated as it's a pure m:n relationship representing entity, similar to ContractSite.

So even when we fix this, composite id's with type converters won't work with NHibernate. It would be best if the ODP.NET problem gets resolved so you can ditch the MS Oracle provider (as that one is deprecated by MS anyway)

(edit) there are more entities in the output which are actually objectified m:n relationships, and thus you have to define m:n relationships between the two entities these entities relate to so they don't show up in the generated code.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39908
Joined: 17-Aug-2003
# Posted on: 19-Sep-2011 14:36:28   

Fixed. See attached template. Place this template in the folder: <llblgen pro installation folder>\Frameworks\NHibernate\Templates\Net3.5\C#

as administrator if you installed llblgen pro in the program files folder on win vista / 7

Attachments
Filename File size Added on Approval
entityFluentMapping.lpt 35,041 19-Sep-2011 14:36.34 Approved
Frans Bouma | Lead developer LLBLGen Pro
samy
User
Posts: 18
Joined: 16-Sep-2011
# Posted on: 19-Sep-2011 14:40:16   

I understand that this is a problem. However, my investigations show that i don't need this class in my developments, so my first workaround would be to get rid of it in the mappings. I'll notify my client though, since he is working on some code that uses this table.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39908
Joined: 17-Aug-2003
# Posted on: 19-Sep-2011 14:42:38   

samy wrote:

I understand that this is a problem. However, my investigations show that i don't need this class in my developments, so my first workaround would be to get rid of it in the mappings. I'll notify my client though, since he is working on some code that uses this table.

The easiest is to simply define a m:n relationship between the two entities which these 'objectified m:n relationships' relate to. This will automatically make the entity not show up in the generated code. To find these entities, use the element search in the designer, select 'entity' and specify:

return p.EntityModel.Vertices.Where(e=>e.IdentifyingFields.Count()==2);

Frans Bouma | Lead developer LLBLGen Pro
samy
User
Posts: 18
Joined: 16-Sep-2011
# Posted on: 20-Sep-2011 14:07:58   

Thank you Frans! The fix works as advertised.

You're right about the composite key. I'm going to try your solution if we have this problem. However the solution you describe may not be sufficient if my joining table needs some data too (with an extra column to describe some detail of the relationship)

Does it fall to me to close the thread?