- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
NHibernate enum converter with linq
Joined: 07-Dec-2010
Hi, I'm using LLBL to generate Nhibernate mappings. I'm dealing with a legacy system that use check constraint in some table columns to limit the allowed values - string values. I created a custom type converter to use enums in the .NET project taking the attribute Description to "translate" the enum value to DB value and vice versa. The converter is working fine with NH operations: load & save. But, with linq it seems not use the converter, it tries to execute the query getting the int value of the enum. Any help will be appreciated.
My enum is:
public enum eDataType
{
[Description("NUMBER")]
Numeric = 0,
[Description("DATE")]
DateTime = 1,
[Description("TEXT")]
String = 2
}
The NH mapping:
<hibernate-mapping ..... <property name="DataType" column="DATA_TYPE" access="field.camelcase-underscore" type="Enums.DataTypeVarcharConverter, Enums, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/> ..... </hibernate-mapping>
Linq query:
using (ISession vObjSesion = Factory.OpenSession())
{
using (ITransaction vTransaccion = vObjSesion.BeginTransaction())
{
var vQuery = from vEntityX in vObjSesion.Query<EntityX>()
where vEntityX.DataType == eDataType.Numeric
select vEntityX;
IList<TableX> listObj = vQuery.ToList();
listObj.Count();
}
}
In debug the exception shows up on the method public void NullSafeSet(IDbCommand cmd, object value, int index) The parameter value is set to zero - int value for eDataType.Numeric enum
Error:
NHibernate.Exceptions.GenericADOException was unhandled by user code
Message=could not execute query
[ select table0_.col1 as ID1_78_, table0_.col 2 as col22_78_, table0_.col3 as col378_ from TABLEX table0_ where table0_.DATA_TYPE=? ]
Name:p1 - Value:0
Source=NHibernate
SqlString=select table0_.col1 as ID1_78_, table0_.col 2 as col22_78_, table0_.col3 as col378_ from TABLEX table0_ where table0_.DATA_TYPE=?
StackTrace:
at NHibernate.Loader.Loader.DoList(ISessionImplementor session, QueryParameters queryParameters)
at NHibernate.Loader.Loader.ListIgnoreQueryCache(ISessionImplementor session, QueryParameters queryParameters)
at NHibernate.Loader.Loader.List(ISessionImplementor session, QueryParameters queryParameters, ISet`1 querySpaces, IType[] resultTypes)
at NHibernate.Hql.Ast.ANTLR.Loader.QueryLoader.List(ISessionImplementor session, QueryParameters queryParameters)
at NHibernate.Hql.Ast.ANTLR.QueryTranslatorImpl.List(ISessionImplementor session, QueryParameters queryParameters)
at NHibernate.Engine.Query.HQLQueryPlan.PerformList(QueryParameters queryParameters, ISessionImplementor session, IList results)
at NHibernate.Impl.SessionImpl.List(IQueryExpression queryExpression, QueryParameters queryParameters, IList results)
at NHibernate.Impl.SessionImpl.List(IQueryExpression queryExpression, QueryParameters parameters)
at NHibernate.Impl.ExpressionQueryImpl.List()
at NHibernate.Linq.NhQueryProvider.ExecuteQuery(NhLinqExpression nhLinqExpression, IQuery query, NhLinqExpression nhQuery)
at NHibernate.Linq.NhQueryProvider.Execute(Expression expression)
at NHibernate.Linq.NhQueryProvider.Execute[TResult](Expression expression)
at Remotion.Data.Linq.QueryableBase`1.GetEnumerator()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at MyNamespace.Negocio.Autorizacion.GestorModulo.ObtenerModulosPorPlataforma(ePlataforma pPlataforma)
InnerException: System.InvalidCastException
Message=Unable to cast object of type 'System.Int32' to type 'System.Enum'.
Source=MyNamespace.Modelo.Enums
StackTrace:
at MyNamespace.Modelo.Enums.Base.StringEnumConverterBase.ConvertTo(ITypeDescriptorContext context, CultureInfo culture, Object value, Type destinationType)
at MyNamespace.Modelo.Enums.Base.StringEnumConverterBase.NullSafeSet(IDbCommand cmd, Object value, Int32 index)
at NHibernate.Type.CustomType.NullSafeSet(IDbCommand cmd, Object value, Int32 index, ISessionImplementor session)
at NHibernate.Engine.QueryParameters.BindParameters(IDbCommand command, Int32 start, ISessionImplementor session)
at NHibernate.Loader.Loader.BindParameterValues(IDbCommand statement, QueryParameters queryParameters, Int32 startIndex, ISessionImplementor session)
at NHibernate.Loader.Loader.PrepareQueryCommand(QueryParameters queryParameters, Boolean scroll, ISessionImplementor session)
at NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
at NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
at NHibernate.Loader.Loader.DoList(ISessionImplementor session, QueryParameters queryParameters)
You don't need a typeconverter for enums in NHibernate, they're natively supported. Look into typeimports for the designer to import your enum type into the designer so you can natively assign it as a type to a field.
It might be a bug in NHibernate's linq provider that it forgets to call into IUserType implementations in these situations (in which case we can't fix it for you). Your type converter does implement IUserType correctly? If you set a breakpoint in the typeconverter, does it get called during the linq query?
Joined: 07-Dec-2010
You're right, I don't need a typeconverter. Since table columns are VARCHAR2 I can define enums with the allowed values and use them directly. To do so, the hbm mapping for the table column has to be like:
<property name="DataType" column="DATA_TYPE" access="field.camelcase-underscore" type="NHibernate.Type.EnumStringType`1[[MyNamespace.Enums.eDataType, MyNamespace.Enums]], NHibernate"/>
In my LLBL project i can specify the enum for de entity property, but the designer is asking for a type converter. I understand I have to specify NHibernate.Type.EnumStringType. How can i do that in order to generate the hbm mapping above?
Help please!
Hmm... the designer only supports numeric <-> enum conversions, not string <-> enum conversions... For the varchar2 column you therefore indeed need the typeconverter / IUserType... We didn't add this in 3.0 as it's extremely fragile to use this conversion. But alas, you're stuck with it, so we've to find a solution.
You can change this in the template, as the typeconverter knows the enum type. The best way is to create a new templatebindings file and bind your copy of the generalTemplateUtils.lpt template to the templateID SD_NHibernateGeneralUtils. (see manual) Then in the generator configuration dialog, at tab 2, you place your templatebindings file above the SD.NHibernate one.
Create the templatebindings file in a folder which is reachable by the designer, so e.g. the folder you've specified as 'AdditionalTemplatesFolder' in the project properties. After you've defined the templatebindings file and copied the generalTemplateUtils.lpt (stored in Frameworks\NHibernate\Templates\Shared\Shared )
In your copy of generalTemplateUtils.lpt, we're going to alter the method ProduceTypeConverterTypeString, which is located at line 361.
At line 367, the method simply returns the type of the typeconverter. We can however test there if the CoreType is an enum type. if so, we can return a different piece of text:
internal static string ProduceTypeConverterTypeString(FieldMapping mapping)
{
if(mapping.TypeConverterToUse==null)
{
return string.Empty;
}
if(mapping.TypeConverterToUse.CoreType.IsEnum)
{
return string.Format(" type=\"NHibernate.Type.EnumStringType`1[[{0}, {1}]], NHibernate\"", mapping.TypeConverterToUse.CoreType.ToString(),
mapping.TypeConverterToUse.CoreType.Assembly.GetName().Name);
}
else
{
return string.Format(" type=\"{0}, {1}\"", mapping.TypeConverterToUse.TypeFullName, mapping.TypeConverterToUse.AssemblyName);
}
}
Here, it checks whether the type of the typeconverter is an enum, and if so, it will emit the specific string type. I haven't tested this, but it should work for you. Of course if you use enums as well for numeric types, you have to adjust the code abit to filter out those types. I'm not sure if this will work though, I think the full assembly name has to be present, e.g. when you sign your own code. In that case, you have to adjust the CoreType.Assembly.GetName().Name fragment to emit the full typename of course.
hope this helps.
Joined: 07-Dec-2010
Otis, i'm very grateful. It works better than expected. In projects where you can define the data's domains it's more than enough the enum <-> int auto-conversion. But when you deal with legacy databases you can face with scenarios of this kind. Maybe you can add this feature in LLBL designer in the future. Again, thank you very much for your help.