Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Abonnements

LINQ To SQL Table -> DataTable, génération de code à la volée, V2

C'est pas parce que je suis au MVP Summit que ça va m'empêcher de coder la nuit. Wink

J'avais précédemment blogué sur la génération de code à la volée qui permettait de convertir une Table LINQ To SQL en DataTable.

Suite à

  • la remarque de Jean-Baptiste
  • le fait que ça m'embêtait de garder un appel par réflection et que je voulais utiliser un expression tree à la place
  • le fait que je me suis dit que plutôt que de mettre les méthodes génériques et d'avoir un dictionnaire avec le type en clé, je ferais mieux de mettre la généricité au niveau de la classe

J'ai repris mon code pour ceci :

namespace ConsoleApplication81

{

    class Program

    {

        static void Main(string[] args)

        {

            using (var context = new DataClasses1DataContext())

            {

                var dt = LinqTableToDataTableHelper<Category>.GetDataTableFromLINQTable(context.Categories);

            }

        }

    }

    public static class LinqTableToDataTableHelper<LTT>

            where LTT : class

    {

        private static DynamicMethod _generatedType;

        private static Delegate _lambdaExprCompiled;

 

        public static DataTable GetDataTableFromLINQTable(Table<LTT> linqTable)

        {

            if (_lambdaExprCompiled == null)

            {

                var parameterExpression = Expression.Parameter(typeof(Table<LTT>), "linqTable");

                var lambdaExpression = Expression.Lambda(

                                        Expression.Call(GetGeneratedMethod(), new ParameterExpression[] { parameterExpression }),

                                        parameterExpression);

                _lambdaExprCompiled = lambdaExpression.Compile();

            }

            return _lambdaExprCompiled.DynamicInvoke(linqTable) as DataTable;

        }

 

        private static DynamicMethod GetGeneratedMethod()

        {

            if (_generatedType != null)

                return _generatedType;

            return (_generatedType = GenerateTableConverter());

        }

 

        public static DynamicMethod GenerateTableConverter()

        {

            var dynamicMethod = new DynamicMethod("Convert", typeof(DataTable), new Type[] { typeof(Table<LTT>) });

            var convertIlGenerator = dynamicMethod.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 dynamicMethod;

        }

    }

}

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 14 avril 2008 09:00 par Matthieu MEZIL

Classé sous : , , , ,

Commentaires

# re: LINQ To SQL Table -> DataTable, génération de code à la volée, V2 @ lundi 14 avril 2008 09:46

Tu peux vachement simplifier, maintenant que tu utilises une DynamicMethod, tu n'as pas besoin de passer par un ET simplement pour l'appeler (ça fait deux dynamic methods compilées alors qu'il n'y a besoin que d'une):

http://monoport.com/9262

(C'est ni testé ni même compilé, mais dans les grandes lignes, ça marche).

Et aussi, dans GenerateTableConverter, au lieu d'utiliser des stloc_N et ldloc_N, tu devrais recupérer ce que te retourne DeclareLocal, et les passer à des sltloc/ldloc. Tu vas pas perdre en performance, mais tu vas gagner en lisibilité.

Jb Evain

# re: LINQ To SQL Table -> DataTable, génération de code à la volée, V2 @ lundi 14 avril 2008 17:49

CreateDelegate je prend. Ca donne donc ceci :

http://blogs.codes-sources.com/matthieu/archive/2008/04/14/linq-to-sql-table-datatable-g-n-ration-de-code-la-vol-e-v3.aspx

Par contre, le but étant de faire un code optimal, je ne prend pas la deuxième remarque même si, comme tu le dis justement, on gagnerait en lisibilité.

Matthieu MEZIL

# re: LINQ To SQL Table -> DataTable, génération de code à la volée, V2 @ lundi 14 avril 2008 18:00

Tu ne perds rien du tout en performance entre un ldloc.0 et un ldloc. Si le but est du faire du code «optimal», y a sûrement d'autres choses à optimiser avant de penser à ça.

Jb Evain

# re: LINQ To SQL Table -> DataTable, génération de code à la volée, V2 @ mardi 15 avril 2008 08:08

"Si le but est du faire du code «optimal», y a sûrement d'autres choses à optimiser avant de penser à ça"

Tu penses à quoi ?

Matthieu MEZIL

# re: LINQ To SQL Table -> DataTable, génération de code à la volée, V2 @ mardi 15 avril 2008 11:05

Je ne pensais à rien de particulier au moment où j'ai écris ça. Je réagissais juste à la remarque du code optimal.

Et se dire que des ldloc.0 c'est plus rapide que des ldloc, dès le départ, c'est une approche:

1) pré-mature (l'approche, pas toi :),

2) de micro-optimisation,

3) surtout non testée.

Bref, rien de bon. Pour la petite histoire, si il existe des ldloc.0 au lieu de juste un ldloc n, c'est pour réduire la taille des assemblies sur le disque, vu qu'un ldloc.0 c'est 1 byte, et un ldloc n, c'est 1 + 4. Si tu veux trouver un compromis, tu peux utiliser un ldloc_s qui s'utilise comme un ldloc, et t'as du 1 + 1, si la variable a un index &lt; 256.

Alors d'accord ce code va se retrouver en mémoire pour la durée de vie du ILGenerator de la DynamicMethod, mais sacrifier 4 bytes par méthode juste pour faire «optimal», et perdre en lisibilité, moi, ça ne me servirais absolument à rien dans ce cas précis.

Mais du coup pour répondre à la question, qu'est ce qu'on pourrait optimiser dans le snippet, ça dépend. Si le code de génération de méthode est appellé souvent, pour pleins de LTT différents, je cacherais les MethodInfo pour éviter les GetMethod. Après je remplacerais les new Type [0] par des Type.EmptyTypes.

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