Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Matthieu MEZIL

I love .Net

Abonnements

Actualités

Locations of visitors to this page English blog
Locations of visitors to this blog
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

- Power Tools 2008 CTP Juillet par Noham Choulant le il y a 24 minutes

- Disparition de variables de session PHP après une redirection ? par MadMatt le il y a 10 heures et 21 minutes

- [MOSS 2007] Publier ses formulaires InfoPath via feature par Adrien Siffermann le il y a 13 heures et 28 minutes

- Imagine Cup 2008 - Paris - Les résultats par TheSaib .NET blog le il y a 14 heures et 50 minutes

- L'Egypte accueille Imagine Cup 2009 par Code is poetry le il y a 15 heures et 2 minutes

- PowerShell : Mise en ligne de fonctions intéressantes pour SharePoint par Blog Technique de Romelard Fabrice le il y a 16 heures et 10 minutes

- Raccourcis clavier et CRM 4 par Clark, C#, MSCRM, SBS le il y a 20 heures et 15 minutes

- [Silverlight] Comment échanger des données entre une application Silverlight et une page ASP.NET via cookies ? par Thomas Lebrun le il y a 20 heures et 51 minutes

- SharePoint 2007 : Trouver les fichiers CheckOut dans une librairie de document par Philippe Sentenac [MVP SharePoint] le il y a 23 heures et 19 minutes

- [Open XML] Travailler avec Open XML : Linq To XML (Partie 2 - Requêtes/XPath) par Julien Chable le 07-08-2008, 02:05