Besoin d'aide sur Expression.Field (Linq.Expression)
Bonjour,
J'essaie d'adapter à mes besoins le projet disponible à l'adresse ci-dessous (en particulier le GenericPopulator).
Cet objet permet de remplir automatiquement un IEnumerable<T> à partir d'un SqlDataReader.
http://www.codeproject.com/Articles/...-from-SqlDataR
Mon but est de placer le code du GenericPopulator dans une méthode d'extension au SqlDataReader.
Sauf qu'au lieu de mapper une colonne du SqlDataReader à une propriété d'Objet, je souhaite la mapper au texte d'un attribut placé sur la propriété.
Le code produit compile mais plante à l'exécution sur la ligne suivante :
Code:
MemberExpression DBNullMemberExpression = Expression.Field(DBNullParameterExpression, DBNullField);
Je ne suis vraiment pas à l'aise avec les expressions et j'espère que vous pourrez m'aider
Voici le code complet de cette méthode d'extension :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
| namespace XYZ.Extensions
{
public static class SqlDataReaderExtension
{
public static IEnumerable<T> Populate<T>(this SqlDataReader reader)
{
List<T> result = new List<T>();
if (reader != null)
{
Func<SqlDataReader, T> internalReader = SqlDataReaderExtension.GetReader<T>(reader);
while (reader.Read())
{
result.Add(internalReader(reader));
}
}
return result;
}
static Func<SqlDataReader, T> GetReader<T>(SqlDataReader reader)
{
Delegate result = null;
Type genericType = typeof(T);
Type DBNullType = typeof(DBNull);
Type readerType = typeof(SqlDataReader);
Type populateAttributeType = typeof(PopulateAttribute);
List<MemberBinding> bindings = new List<MemberBinding>();
// Liste des colonnes
List<String> columns = new List<String>();
for (Int32 i = 0; i < reader.FieldCount; i++)
{
String column = reader.GetName(i);
columns.Add(column);
}
// Récupération des informations sur la méthode GetValue
MethodInfo readerGetValueMethodInfo = readerType.GetMethod("GetValue");
ParameterExpression readerParameterExpression = Expression.Parameter(readerType, "reader");
ParameterExpression[] readerParametersExpression = new ParameterExpression[] { readerParameterExpression };
// Récupération des informations sur le type DBNull
FieldInfo DBNullField = DBNullType.GetField("Value");
ParameterExpression DBNullParameterExpression = Expression.Parameter(DBNullType);
MemberExpression DBNullMemberExpression = Expression.Field(DBNullParameterExpression, DBNullField);
// Utile pour gérer le cas où deux attributs correspondent à la colonne
List<String> foundColumns = new List<String>();
foreach (PropertyInfo property in genericType.GetProperties())
{
Object defaultValue;
String field = String.Empty;
Type propertyType = property.PropertyType;
// Récupération des noms de champs physiques placés dans les attributs et comparaison avec les colonnes
PopulateAttribute[] attributes = (Attribute.GetCustomAttributes(property, populateAttributeType) as PopulateAttribute[]);
for (Int32 i = 0; i < attributes.Count(); i++)
{
String attribute = attributes[i].Field;
if ((columns.Contains(attribute)) && (!foundColumns.Contains(attribute)))
{
foundColumns.Add(attribute);
field = attribute;
break;
}
}
// Le but est de construire à la volée une fonction de la forme suivante :
// Property = IIF((reader.GetValue(columnIndex) != DBNull.Value), Convert(reader.GetValue(columnIndex)), Convert(defaultValue)
if (!String.IsNullOrWhiteSpace(field))
{
// Détermine la valeur par défaut selon le type
if (propertyType.IsValueType) defaultValue = Activator.CreateInstance(propertyType);
else if (propertyType.Name.Equals("String", StringComparison.OrdinalIgnoreCase)) defaultValue = String.Empty;
else defaultValue = null;
// Construit l'expression qui va servir à générer le code de lecture d'une données
ConstantExpression columnIndexConstantExpression = Expression.Constant(reader.GetOrdinal(field));
IEnumerable<Expression> columnsIndexesExpressions = new List<Expression> { columnIndexConstantExpression };
MethodCallExpression readerGetValueMethodCallExpression = Expression.Call(readerParameterExpression, readerGetValueMethodInfo, columnsIndexesExpressions);
// Construit les expressions qui va servir à générer le code pour vérifier que la données "IS NOT NULL"
ConstantExpression defaultValueConstantExpression = Expression.Constant(defaultValue);
BinaryExpression testExpression = Expression.NotEqual(DBNullMemberExpression, readerGetValueMethodCallExpression);
UnaryExpression ifTrueExpression = Expression.Convert(readerGetValueMethodCallExpression, propertyType);
UnaryExpression ifFalseExpression = Expression.Convert(defaultValueConstantExpression, propertyType);
ConditionalExpression valueConditionlExpression = Expression.Condition(testExpression, ifTrueExpression, ifFalseExpression);
// Construit l'expression qui va servir à générer le code de mapping entre la valeur et propriété
MemberInfo genericMemberInfo = genericType.GetMember(property.Name)[0];
MemberBinding genricMemberBinding = Expression.Bind(genericMemberInfo, valueConditionlExpression);
bindings.Add(genricMemberBinding);
}
}
// Construit l'expression qui va servir à générer le code de création de l'objet générique
// et l'associer à la liste d'expression créée juste avant.
// Cela donnera une expression de la forme :
// new T() { FirstProperty = IFF(...), SecondProperty = IFF(...), NthProperty = IFF(...) }
NewExpression genricNewExpression = Expression.New(genericType);
MemberInitExpression genricInitExpression = Expression.MemberInit(genricNewExpression, bindings);
// Génère l'expression lambda de la forme suivante :
// reader => new T() { ... }
LambdaExpression lambdaExpression = Expression.Lambda<Func<SqlDataReader, T>>(genricInitExpression, readerParametersExpression);
result = lambdaExpression.Compile();
return (result as Func<SqlDataReader, T>);
}
}
} |
L'exception déclenchée est la suivante :
Citation:
{System.ArgumentException: Static field requires null instance, non-static field requires non-null instance.
Parameter name: expression
at System.Linq.Expressions.Expression.Field(Expression expression, FieldInfo field)
at XYZ.Extensions.SqlDataReaderExtension.GetReader[T](SqlDataReader reader) in c:\Users\popo\Documents\Visual Studio 2013\Projects\XYZ\Extensions\Extension.cs:line 57
at XYZ.Extensions.Mechanics.SqlDataReaderExtension.Populate[T](SqlDataReader reader) in c:\Users\popo\Documents\Visual Studio 2013\Projects\XYZ\Extensions\Extension.cs:line 19
at XYZ.FormTest.button1_Click(Object sender, EventArgs e) in c:\Users\popo\Documents\Visual Studio 2013\Projects\XYZ\FormTest.cs:line 47
at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.ButtonBase.WndProc(Message& m)
at System.Windows.Forms.Button.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
at XYZ.Program.Main() in c:\Users\popo\Documents\Visual Studio 2013\Projects\XYZ\Program.cs:line 18
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()}