为什么ExpandoObject打破代码,否则工作正常?

时间:2011-09-26 22:40:35

标签: c#-4.0 dynamic

这是设置:我有一个名为“Massive”的开源项目(github / robconery / massive),我正在围绕动态作为一种动态创建SQL的方式,以及动态的动态结果集。 / p>

要做数据库结束我正在使用System.Data.Common和ProviderFactory的东西。这是一个工作正常的示例(它是静态的,因此您可以在控制台中运行):

    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;
    }

运行此代码的结果是“它有效!”

现在,如果我将字符串参数更改为dynamic - 特别是ExpandoObject(假设某个例程将Expando压缩为SQL) - 会抛出一个奇怪的错误。这是代码:

Dynamic Error

之前有效的方法失败了,没有任何意义的消息。 SqlConnection 一个DbConnection - 而且如果你在debug中鼠标悬停代码,你会发现这些类型都是SQL类型。 “conn”是一个SqlConnection,“cmd”是一个SqlCommand。

这个错误完全没有意义 - 但更重要的是,它的出现是因为没有触及任何实现代码的ExpandoObject。两个例程之间的区别是: 1 - 我在CreateCommand()中更改了参数以接受“dynamic”而不是string 2 - 我创建了一个ExpandoObject并设置了一个属性。

它变得怪异。

如果只是使用一个字符串而不是ExpandoObject - 它一切正常!

    //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;
    }

如果我将CreateCommand()的参数换成我的ExpandoObject(“ex”) - 它会导致代码的所有成为“动态表达式”,并在运行时进行评估。 / p>

看起来这段代码的运行时评估与编译时评估不同......这没有任何意义。

**编辑:我应该在此添加,如果我硬编码所有以明确使用SqlConnection和SqlCommand,它可以工作:) - 这是我的意思的图像:

enter image description here

7 个答案:

答案 0 :(得分:32)

当您将动态传递给CreateCommand时,编译器会将其返回类型视为必须在运行时解析的动态。不幸的是,你在解析器和C#语言之间遇到了一些奇怪的问题。幸运的是,通过删除使用var强制编译器执行您期望的操作,可以轻松解决此问题:

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;
}

这已在Mono 2.10.5上测试过,但我确信它也适用于MS。

答案 1 :(得分:18)

它表现得好像你试图在程序集中传递 dynamics 匿名类型,这是不受支持的。但是,支持传递ExpandoObject。当我需要传递程序集并且我已成功测试它时,我使用的解决方法是将动态 输入变量强制转换为{{1}当你传递它时:

ExpandoObject

修改 正如评论中指出的那样,你可以跨程序集传递动态,你不能在不首先构建它们的情况下在程序集中传递匿名类型。

上述解决方案的有效性与Frank Krueger在上述说明的原因相同。

  

当您将动态传递给CreateCommand时,编译器正在处理   它的返回类型是动态的,它必须在运行时解析。

答案 2 :(得分:15)

因为您使用dynamic作为CreateCommand()的参数,所以cmd变量也是动态的,这意味着它的类型在运行时被解析为SqlCommand。相比之下,conn变量不是动态的,并且被编译为DbConnection类型。

基本上,SqlCommand.Connection的类型为SqlConnection,因此conn变量(类型为DbConnection)是将Connection设置为无效的无效值。您可以通过将conn转换为SqlConnection或制作conn变量dynamic来解决此问题。

之前工作正常的原因是因为cmd实际上是一个DbCommand变量(即使它指向同一个对象),DbCommand.Connection属性的类型为{{1 }}。即DbConnection类对SqlCommand属性的new定义。

已注明的来源问题:

Connection

修复来源的选项(仅挑选1个):

  1. 强制转换为静态声明 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; } 为SqlConnection:  conn

  2. 使用using (var conn = (SqlConnection) OpenConnection())的运行时类型:  conn

  3. 不要动态绑定CreateCommand:  using (dynamic conn = OpenConnection())

  4. 静态定义var cmd = CreateCommand((object)ex);:  cmd

答案 3 :(得分:5)

查看抛出的异常,似乎即使OpenConnection返回静态类型(DbConnection)而CreateCommand返回静态类型(DbCommand),因为传递给DbConnection的参数是dynamic类型,它基本上将以下代码视为动态绑定站点:

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

因此,运行时绑定程序试图找到可能的最具体的绑定,即将连接转换为SqlConnection。尽管该实例在技术上是一个SqlConnection,但它静态地键入为DbConnection,因此这是绑定器尝试从中进行转换的内容。由于没有从DbConnection到SqlConnection的直接转换,它失败了。

似乎有效,取自this S.O.回答处理底层异常类型,实际上是将conn声明为动态,而不是使用var,在这种情况下,binder找到SqlConnection - &gt; SqlConnection setter就可以了,就像这样:

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;
    }

话虽如此,鉴于您将CreateCommand的返回类型静态键入DbConnection这一事实,人们可能会认为绑定器在这种情况下做得更好“做正确的事情”,这很可能是C#中动态绑定器实现中的一个错误。

答案 4 :(得分:3)

  

此代码的运行时评估似乎不同于   编译时评估......这没有任何意义。

这就是发生了什么。如果调用的任何部分是动态的,则整个调用是动态的。将动态参数传递给方法会导致动态调用整个方法。这使得返回类型动态,依此类推。这就是为什么它在传递字符串时有效,你不再动态调用它。

我不明确具体说明错误发生的原因,但我认为隐式转换不会自动处理。我知道还有其他一些动态调用的情况与正常情况略有不同,因为我们在Orchard CMS中执行一些动态POM(页面对象模型)时遇到其中一个。这是一个极端的例子,Orchard非常深入地调用动态调用,可能只是做了一些不是为它设计的东西。

至于“没有意义” - 同意这是出乎意料的,并希望在未来的转速中有所改进。我打赌我的脑子里有一些微妙的理由,语言专家可以解释为什么它不能自动起作用。

这就是为什么我喜欢限制代码的动态部分的一个原因。如果你使用动态值调用非动态的东西,但是你知道你期望它是什么类型,那么显式地转换它以防止调用是动态的。你回到'普通的土地',编译类型检查,重构等等。只需在你需要的地方动态使用它,并且不超过它。

答案 5 :(得分:3)

答案 6 :(得分:2)

您无需使用Factory创建命令。只需使用conn.CreateCommand();它将是正确的类型,并且已经设置了连接。