Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Abonnements

Génération de code à la volée

J'ai récemment eu la question suivante : comment, à partir d'une System.Linq.Table<T> générer une DataTable.

Ma réponse a tout de suite était : par reflection.

Le problème de la reflection c'est que c'est long.

Du coup la meilleure façon d'optimiser le traitement est de générer du code fortement typé à la volée.

A ce propos, je vous invite à regarder la méthode de Mitsu présentée aux techdays, en utilisant les Expression Tree.

Pour ma part, j'ai utilisé la méthode Emit de la class ILGenerator.

Voici ce que ça donne :

namespace ConsoleApplication81

{

    class Program

    {

        static void Main(string[] args)

        {

            using (var context = new DataClasses1DataContext())

            {

                DataTable table = LinqTableToDataTableHelper.GetDataTableFromLINQTable(context.Products);

            }

        }

    }

    public static class LinqTableToDataTableHelper

    {

        public static DataTable GetDataTableFromLINQTable<LTT>(Table<LTT> linqTable)

            where LTT : class

        {

            return GetGeneratedType<LTT>().GetMethod("Convert").Invoke(null, new object[]{linqTable}) as DataTable;

        }

 

        private static Dictionary<Type, Type> _generatedTypes = new Dictionary<Type, Type>();

 

        private static Type GetGeneratedType<LTT>()

            where LTT : class

        {

            if (_generatedTypes.ContainsKey(typeof(LTT)))

                return _generatedTypes[typeof(LTT)];

            Type generatedType = GenerateTableConverter<LTT>();

            _generatedTypes.Add(typeof(LTT), generatedType);

            return generatedType;

        }

 

        public static Type GenerateTableConverter<LTT>()

            where LTT : class

        {

            var assemblyName = new AssemblyName { Name = typeof(LTT).Name + "Converter" };

            var assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

            var module = assemblyBuilder.DefineDynamicModule(typeof(LTT).Name + ".dll");

            var typeBuilder = module.DefineType("Converter", TypeAttributes.Public | TypeAttributes.Class);

 

            var convertMethodBuilder = typeBuilder.DefineMethod("Convert", MethodAttributes.Public | MethodAttributes.Static, typeof(DataTable), new Type[] { typeof(Table<LTT>) });

            var convertIlGenerator = convertMethodBuilder.GetILGenerator();

            convertIlGenerator.DeclareLocal(typeof(DataTable));

            convertIlGenerator.DeclareLocal(typeof(IEnumerator<LTT>));

            convertIlGenerator.DeclareLocal(typeof(LTT));

            convertIlGenerator.DeclareLocal(typeof(DataRow));

            var propertyValue = convertIlGenerator.DeclareLocal(typeof(object));

            convertIlGenerator.Emit(OpCodes.Ldarg_0);

            var convertIlGeneratorArgOkLabel = convertIlGenerator.DefineLabel();

            convertIlGenerator.Emit(OpCodes.Brtrue_S, convertIlGeneratorArgOkLabel);

            convertIlGenerator.Emit(OpCodes.Newobj, typeof(ArgumentException).GetConstructor(new Type[0]));

            convertIlGenerator.Emit(OpCodes.Throw);

            convertIlGenerator.MarkLabel(convertIlGeneratorArgOkLabel);

 

            convertIlGenerator.Emit(OpCodes.Newobj, typeof(DataTable).GetConstructor(new Type[0]));

            convertIlGenerator.Emit(OpCodes.Stloc_0);

            var properties = typeof(LTT).GetProperties().Where(p => p.GetAttribute<ColumnAttribute>() != null);

            foreach (var pi in properties)

            {

                convertIlGenerator.Emit(OpCodes.Ldloc_0);

                convertIlGenerator.Emit(OpCodes.Callvirt, typeof(DataTable).GetMethod("get_Columns"));

                convertIlGenerator.Emit(OpCodes.Ldstr, pi.Name);

                if (pi.PropertyType.IsGenericType && pi.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))

                    convertIlGenerator.Emit(OpCodes.Ldtoken, pi.PropertyType.GetGenericArguments()[0]);

                else

                    convertIlGenerator.Emit(OpCodes.Ldtoken, pi.PropertyType);

                convertIlGenerator.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle", new Type[] { typeof(RuntimeTypeHandle) }));

                convertIlGenerator.Emit(OpCodes.Callvirt, typeof(DataColumnCollection).GetMethod("Add", new Type[] { typeof(string), typeof(Type) }));

                convertIlGenerator.Emit(OpCodes.Pop);

            }

            var convertIlGeneratorEntitiesLoopLabel = convertIlGenerator.DefineLabel();

            convertIlGenerator.Emit(OpCodes.Ldarg_0);

            convertIlGenerator.Emit(OpCodes.Callvirt, typeof(IEnumerable<LTT>).GetMethod("GetEnumerator"));

            convertIlGenerator.Emit(OpCodes.Stloc_1);

            convertIlGenerator.MarkLabel(convertIlGeneratorEntitiesLoopLabel);

            convertIlGenerator.Emit(OpCodes.Ldloc_1);

            convertIlGenerator.Emit(OpCodes.Callvirt, typeof(IEnumerator).GetMethod("MoveNext"));

            var convertIlGeneratorEntitiesEndLoopLable = convertIlGenerator.DefineLabel();

            convertIlGenerator.Emit(OpCodes.Brfalse, convertIlGeneratorEntitiesEndLoopLable);

            convertIlGenerator.Emit(OpCodes.Ldloc_1);

            convertIlGenerator.Emit(OpCodes.Callvirt, typeof(IEnumerator<LTT>).GetMethod("get_Current"));

            convertIlGenerator.Emit(OpCodes.Stloc_2);

            convertIlGenerator.Emit(OpCodes.Ldloc_0);

            convertIlGenerator.Emit(OpCodes.Callvirt, typeof(DataTable).GetMethod("get_Rows"));

            convertIlGenerator.Emit(OpCodes.Ldloc_0);

            convertIlGenerator.Emit(OpCodes.Callvirt, typeof(DataTable).GetMethod("NewRow"));

            convertIlGenerator.Emit(OpCodes.Stloc_3);

            foreach (var pi in properties)

            {

                convertIlGenerator.Emit(OpCodes.Ldloc_2);

                convertIlGenerator.Emit(OpCodes.Callvirt, typeof(LTT).GetMethod("get_" + pi.Name, new Type[0]));

                if (pi.PropertyType.IsValueType)

                    convertIlGenerator.Emit(OpCodes.Box, pi.PropertyType);

                convertIlGenerator.Emit(OpCodes.Stloc, propertyValue);

                convertIlGenerator.Emit(OpCodes.Ldloc, propertyValue);

                var convertIlGeneratorNextPropertyLabel = convertIlGenerator.DefineLabel();

                convertIlGenerator.Emit(OpCodes.Brfalse_S, convertIlGeneratorNextPropertyLabel);

                convertIlGenerator.Emit(OpCodes.Ldloc_3);

                convertIlGenerator.Emit(OpCodes.Ldstr, pi.Name);

                convertIlGenerator.Emit(OpCodes.Ldloc, propertyValue);

                convertIlGenerator.Emit(OpCodes.Callvirt, typeof(DataRow).GetMethod("set_Item", new Type[] { typeof(string), typeof(object) }));

                convertIlGenerator.MarkLabel(convertIlGeneratorNextPropertyLabel);

            }

            convertIlGenerator.Emit(OpCodes.Ldloc_3);

            convertIlGenerator.Emit(OpCodes.Callvirt, typeof(DataRowCollection).GetMethod("Add", new Type[] { typeof(DataRow) }));

            convertIlGenerator.Emit(OpCodes.Br, convertIlGeneratorEntitiesLoopLabel);

            convertIlGenerator.MarkLabel(convertIlGeneratorEntitiesEndLoopLable);

            convertIlGenerator.Emit(OpCodes.Ldloc_0);

            convertIlGenerator.Emit(OpCodes.Ret);

 

            return typeBuilder.CreateType();

        }

    }

}

namespace System.Reflection

{

    public static class PropertyInfoExtension

    {

        public static T GetAttribute<T>(this PropertyInfo pi) where T : Attribute

        {

            object[] attributes = pi.GetCustomAttributes(typeof(T), true);

            if (attributes.Length == 0)

                return null;

            return attributes[0] as T;

        }

    }

}

Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Publié lundi 31 mars 2008 03:22 par Matthieu MEZIL

Classé sous : , , , ,

Commentaires

# re: Génération de code à la volée @ lundi 31 mars 2008 09:38

Le problème avec cette approche c'est qu'elle pollue l'AppDomain avec des nouvelles assemblies (qui restent en mémoire pour la durée de vie de l'AppDomain).

Puisqu'il n'y a qu'une méthode Convert à générer pour chaque cas, autant passer directement par une DynamicMethod, qui elle a l'avantage de pouvoir être collectée au besoin.

Jb Evain

Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- Merci par Blog de Jérémy Jeanson le 10-01-2019, 20:47

- Office 365: Script PowerShell pour auditer l’usage des Office Groups de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 11:02

- Office 365: Script PowerShell pour auditer l’usage de Microsoft Teams de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 10:39

- Office 365: Script PowerShell pour auditer l’usage de OneDrive for Business de votre tenant par Blog Technique de Romelard Fabrice le 04-25-2019, 15:13

- Office 365: Script PowerShell pour auditer l’usage de SharePoint Online de votre tenant par Blog Technique de Romelard Fabrice le 02-27-2019, 13:39

- Office 365: Script PowerShell pour auditer l’usage d’Exchange Online de votre tenant par Blog Technique de Romelard Fabrice le 02-25-2019, 15:07

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Stream Portal par Blog Technique de Romelard Fabrice le 02-21-2019, 17:56

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Video Portal par Blog Technique de Romelard Fabrice le 02-18-2019, 18:56

- Office 365: Script PowerShell pour extraire les Audit Log basés sur des filtres fournis par Blog Technique de Romelard Fabrice le 01-28-2019, 16:13

- SharePoint Online: Script PowerShell pour désactiver l’Option IRM des sites SPO non autorisés par Blog Technique de Romelard Fabrice le 12-14-2018, 13:01