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

38 c# dynamic c#-4.0

这是设置:我有一个名为"Massive"的开源项目(github/robconery/massive),我正在动态地将动态作为一种动态创建SQL的方式,以及动态结果集.

要做数据库的结束我正在使用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;
    }
Run Code Online (Sandbox Code Playgroud)

运行此代码的结果是"它工作了!"

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

动态错误

之前有效的方法失败了,没有任何意义的消息.SqlConnection 一个DbConnection - 而且如果你在debug中鼠标悬停代码,你可以看到这些类型都是SQL类型."conn"是一个SqlConnection,"cmd"是一个SqlCommand.

这个错误完全没有意义 - 但更重要的是它是由于没有触及任何实现代码的ExpandoObject的存在.两个例程之间的区别是:1 - 我在CreateCommand()中更改了参数以接受"dynamic"而不是字符串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;
    }
Run Code Online (Sandbox Code Playgroud)

如果我将CreateCommand()的参数换成我的ExpandoObject("ex") - 它会导致所有代码都是在运行时计算的"动态表达式".

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

**编辑:我应该在这里添加,如果我硬编码一切明确使用SqlConnection和SqlCommand,它的工作原理:) - 这是我的意思的图像:

在此输入图像描述

Fra*_*ger 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;
}
Run Code Online (Sandbox Code Playgroud)

这已经在Mono 2.10.5上进行了测试,但我确信它也适用于MS.

  • 我把它拿回来 - 我的坏:).我用DbConnection替换了"var",它可以工作.谢谢. (4认同)
  • 这对我来说也适用于Windows/Visual Studio 2010/.NET 4.http://codepaste.net/uby5qr (2认同)

Ale*_*oun 18

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

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;
}
Run Code Online (Sandbox Code Playgroud)

编辑: 正如评论中指出的那样,您可以跨程序集传递动态,您无法在不首先投射它们的情况下跨程序集传递匿名类型.

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

将动态传递给CreateCommand时,编译器会将其返回类型视为必须在运行时解析的动态.

  • 通过组件传递动力当然确实有效.您可能正在考虑将匿名对象作为动态跨程序集传递,而不起作用,因为匿名类型是内部的. (3认同)

小智 15

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

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

它之前工作正常的原因是因为cmd它实际上是一个DbCommand变量(即使它指向同一个对象),并且DbCommand.Connection属性是类型的DbConnection.即SqlCommand该类具有newConnection属性的定义.

来源问题注释:

 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;
}
Run Code Online (Sandbox Code Playgroud)

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

  1. 转换为静态声明conn为SqlConnection: using (var conn = (SqlConnection) OpenConnection())

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

  3. 不要动态绑定CreateCommand: var cmd = CreateCommand((object)ex);

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

  • 一个简单的解决方法应该是在DbConnection本身上使用`CreateCommand`函数. (4认同)
  • 另一种解决方法是将cmd声明为`DbCommand cmd = ...`而不是使用`var`. (3认同)

Dou*_*rer 5

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

 var cmd = CreateCommand(ex);
    cmd.Connection = conn;
Run Code Online (Sandbox Code Playgroud)

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

这个处理底层异常类型的SO答案中得出的似乎是,实际上将conn声明为动态,而不是使用var,在这种情况下,绑定器找到SqlConnection - > 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;
    }
Run Code Online (Sandbox Code Playgroud)

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