39 votes

Pourquoi un code de rupture ExpandoObject qui fonctionne normalement très bien?

Voici la configuration: j'ai un projet Open Source appelé "Massive" (github/robconery/massif) et je suis d'élingage autour de la dynamique comme un moyen de création de SQL à la volée, et de la dynamique des ensembles de résultats à la volée.

Pour faire la base de données de la fin des choses, je suis en utilisant le Système.Les données.Commune et de la ProviderFactory choses. Voici un exemple qui fonctionne très bien (il est statique de sorte que vous pouvez exécuter dans une Console):

    static DbCommand CreateCommand(string sql) {
        return DbProviderFactories.GetFactory("System.Data.SqlClient")
                                  .CreateCommand();
    }
    static DbConnection OpenConnection() {
        return DbProviderFactories.GetFactory("System.Data.SqlClient")
                                  .CreateConnection();
    }
    public static dynamic DynamicWeirdness() {
        using (var conn = OpenConnection()) {
            var cmd = CreateCommand("SELECT * FROM Products");
            cmd.Connection = conn;
        }
        Console.WriteLine("It worked!");
        Console.Read();
        return null;
    }

Le résultat de l'exécution de ce code est "Ça marche!"

Maintenant, si je change l'argument de chaîne dynamique - plus précisément un ExpandoObject (faire semblant qu'il y a une routine de quelque part qui croque la Expando en SQL) - une étrange erreur est renvoyée. Voici le code:

Dynamic Error

Ce qui a fonctionné avant maintenant échoue avec un message qui n'a aucun sens. Une occurrence de SqlConnection est un DbConnection - d'ailleurs si vous passer la souris sur le code de débogage, vous pouvez voir que les types sont tous les types SQL. "conn" est une occurrence de SqlConnection, "cmd" est un SqlCommand.

Cette erreur fait absolument aucun sens - mais le plus important c'est cause par la présence d'un ExpandoObject qui ne touche pas toute la mise en œuvre du code. Les différences entre les deux routines sont: 1 - j'ai changé l'argument en CreateCommand() pour accepter les "dynamique" au lieu de string 2 - j'ai créé un ExpandoObject et de définir une propriété.

Il devient encore plus bizarre.

Si il suffit d'utiliser une chaîne de caractères au lieu de la ExpandoObject - tout cela fonctionne très bien!

    //THIS WORKS
    static DbCommand CreateCommand(dynamic item) {
        return DbProviderFactories.GetFactory("System.Data.SqlClient").CreateCommand();
    }
    static DbConnection OpenConnection() {
        return DbProviderFactories.GetFactory("System.Data.SqlClient").CreateConnection();
    }
    public static dynamic DynamicWeirdness() {
        dynamic ex = new ExpandoObject();
        ex.TableName = "Products";
        using (var conn = OpenConnection()) {
            //use a string instead of the Expando
            var cmd = CreateCommand("HI THERE");
            cmd.Connection = conn;
        }
        Console.WriteLine("It worked!");
        Console.Read();
        return null;
    }

Si je remplace les arguments pour CreateCommand() pour être mon ExpandoObject ("ex") -, il provoque tout le code à une "dynamique de l'expression" qui est évaluée au moment de l'exécution.

Il semble que le moteur d'exécution de l'évaluation de ce code est différent de celui de la compilation d'évaluation en temps... ce qui n'a pas de sens.

**EDIT: je dois ajouter ici que si je code en dur tout à l'utilisation de SqlConnection et SqlCommand explicitement, il fonctionne :) - voici une image de ce que je veux dire:

enter image description here

32voto

Frank Krueger Points 27508

Lorsque vous transmettez la dynamique à CreateCommand , le compilateur traite son type de retour comme une dynamique qu'il doit résoudre au moment de l'exécution. Malheureusement, vous rencontrez des problèmes particuliers entre ce résolveur et le langage C #. Heureusement, il est facile de contourner le problème en supprimant votre utilisation de var obligeant le compilateur à faire ce que vous attendez:

 public static dynamic DynamicWeirdness() {
    dynamic ex = new ExpandoObject ();
    ex.Query = "SELECT * FROM Products";
    using (var conn = OpenConnection()) {
        DbCommand cmd = CreateCommand(ex); // <-- DON'T USE VAR
        cmd.Connection = conn;
    }
    Console.WriteLine("It worked!");
    Console.Read();
    return null;
}
 

Cela a été testé sur Mono 2.10.5, mais je suis sûr que cela fonctionne aussi avec MS.

18voto

Alexander Kahoun Points 1632

C'est agir comme si vous êtes en essayant de passer de la dynamique des types anonymes à travers des assemblées, ce qui n'est pas pris en charge. Passage d'un ExpandoObject est pris en charge si. Le travail que j'ai utilisé lorsque j'en ai besoin pour passer à travers les assemblées, et je l'ai testé avec succès, est de jeter la dynamique de la variable d'entrée comme un ExpandoObject lorsque vous passer de:

public static dynamic DynamicWeirdness()
{
    dynamic ex = new ExpandoObject();
    ex.TableName = "Products";
    using (var conn = OpenConnection()) {
        var cmd = CreateCommand((ExpandoObject)ex);
        cmd.Connection = conn;
    }
    Console.WriteLine("It worked!");
    Console.Read();
    return null;
}

EDIT: Comme l'a souligné dans les commentaires, vous POUVEZ passer de la dynamique à travers des assemblées, vous ne POUVEZ PAS passer les types anonymes à travers les assemblées sans premier casting eux.

La solution ci-dessus est valable pour la même raison que Frank Krueger états ci-dessus.

Lorsque vous passez la dynamique de CreateCommand, le compilateur est le traitement de l' son type de retour comme une dynamique qu'il a à résoudre lors de l'exécution.

15voto

Shane S. Anderson Points 437

Parce que vous êtes à l'aide de dynamic comme argument d' CreateCommand(), l' cmd variable est également dynamique, ce qui signifie que son type est résolu à l'exécution pour être SqlCommand. En revanche, l' conn variable n'est pas dynamique et est compilé pour être de type DbConnection.

Fondamentalement, SqlCommand.Connection est de type SqlConnection, de sorte que l' conn variable, qui est de type DbConnection, est une valeur non valide pour définir Connection de. Vous pouvez résoudre ce problème en soit coulée conn d'un SqlConnection, ou à faire de l' conn variable dynamic.

La raison pour laquelle il a bien fonctionné avant était parce que cmd était en fait un DbCommand variable (même si elle a fait pour le même objet), et l' DbCommand.Connection de la propriété est de type DbConnection. c'est à dire l' SqlCommand classe a un new définition de l' Connection de la propriété.

Source annoté:

 public static dynamic DynamicWeirdness() {
    dynamic ex = new ExpandoObject();
    ex.TableName = "Products";
    using (var conn = OpenConnection()) { //'conn' is statically typed to 'DBConnection'
        var cmd = CreateCommand(ex); //because 'ex' is dynamic 'cmd' is dynamic
        cmd.Connection = conn; 
        /*
           'cmd.Connection = conn' is bound at runtime and
           the runtime signature of Connection takes a SqlConnection value. 
           You can't assign a statically defined DBConnection to a SqlConnection
           without cast. 
        */
    }
    Console.WriteLine("It will never get here!");
    Console.Read();
    return null;
}

Options pour la fixation de la source (ne choisir qu'1):

  1. Fonte à déclarer statiquement conn comme une occurrence de SqlConnection: using (var conn = (SqlConnection) OpenConnection())

  2. L'utilisation d'exécution type d' conn: using (dynamic conn = OpenConnection())

  3. Ne pas dynamique lier CreateCommand: var cmd = CreateCommand((object)ex);

  4. Statiquement définir cmd: DBCommand cmd = CreateCommand(ex);

5voto

Doug Rohrer Points 856

À la recherche, à l'exception levée, il semble que, même si OpenConnection retourne un type statique (DbConnection) et CreateCommand retourne un type statique (DbCommand), parce que le paramètre passé à DbConnection est de type dynamique, c'est essentiellement le traitement le code suivant en tant que dynamique de site de liaison:

 var cmd = CreateCommand(ex);
    cmd.Connection = conn;

De ce fait, le moteur d'exécution-le classeur est en essayant de trouver le plus spécifique de la liaison possible, ce qui serait pour lancer la connexion à SqlConnection. Même si l'instance est techniquement une occurrence de SqlConnection, c'est statiquement typé comme DbConnection, c'est ce que le liant les tentatives de sorts. Car il n'y a pas de direct en fonte à partir de DbConnection de SqlConnection, il échoue.

Ce qui semble fonctionner, prises à partir de ce S. O. répondre traitant du sous-jacent type d'exception, c'est de le déclarer conn comme dynamique, plutôt que d'utiliser le var, dans ce cas, le liant trouve de l'occurrence de SqlConnection -> SqlConnection setter et fonctionne, tout simplement, comme suit:

public static dynamic DynamicWeirdness()
    {
        dynamic ex = new ExpandoObject();
        ex.TableName = "Products";
        using (dynamic conn = OpenConnection())
        {
            var cmd = CreateCommand(ex);
            cmd.Connection = conn;
        }
        Console.WriteLine("It worked!");
        Console.Read();
        return null;
    }

Cela étant dit, compte tenu du fait que vous typé statiquement le type de retour de CreateCommand à DbConnection, on aurait pu penser, le liant l'aurait fait un meilleur travail de "faire la bonne chose" dans ce cas, et c'est peut-être un bug dans la dynamique liant la mise en œuvre en C#.

3voto

InfinitiesLoop Points 4005

Il semble que le moteur d'exécution de l'évaluation de ce code est différent de celui au moment de la compilation d'évaluation... qui n'a pas de sens.

C'est ce qu'il se passe. Si une partie quelconque d'une invocation est dynamique, l'ensemble de l'invocation dynamique. Passage d'une dynamique argument à une méthode provoque l'intégralité de la méthode à invoquer dynamiquement. Et que fait le type de retour dynamique, et ainsi de suite et ainsi de suite. C'est pourquoi il fonctionne lorsque vous passez une chaîne, vous n'êtes plus l'invoquer dynamiquement.

Je ne sais pas précisément pourquoi le message d'erreur se produit, mais je suppose que les conversions implicites ne sont pas gérées automatiquement. Je sais qu'il y a d'autres cas, la dynamique de l'invocation qui se comportent un peu différemment que d'habitude parce que nous avons frappé l'un d'eux lors d'un de la dynamique de POM (page object model) des trucs dans le Verger de la CMS. C'est un exemple extrême mais, Verger bouchons assez profondément dans la dynamique de l'invocation et peut-être simplement de faire des choses qu'il n'a pas été conçu pour.

Comme pour "il ne fait aucun sens" -- d'accord que c'est inattendu, et j'espère que améliorées dans le futur régime. Je parie qu'il y quelques subtiles raisons-dessus de ma tête que les experts de la langue pourrait expliquer pourquoi il ne fonctionne pas juste automatiquement.

C'est une des raisons pourquoi j'aime à la limite de la dynamique des parties de code. Si vous appelez quelque chose qui n'est pas dynamique, avec une valeur dynamique, mais vous savez ce type, vous vous attendez à être, explicitement exprimés pour empêcher l'invocation d'être dynamique. Vous obtenez de retour à la "normale terre", la compilation de vérification de type, refactoring, etc. Juste à cocher dans la dynamique de l'utiliser là où vous en avez besoin, et rien de plus.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X