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 :
Je ne suis vraiment pas à l'aise avec les expressions et j'espère que vous pourrez m'aider
Code : Sélectionner tout - Visualiser dans une fenêtre à part MemberExpression DBNullMemberExpression = Expression.Field(DBNullParameterExpression, DBNullField);
Voici le code complet de cette méthode d'extension :
L'exception déclenchée est la suivante :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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>); } } }
{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()}
Partager