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 : Sélectionner tout - Visualiser dans une fenêtre à part
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 : 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>);
        }
    }
}
L'exception déclenchée est la suivante :
{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()}