这是设置:我有一个名为“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) - 会抛出一个奇怪的错误。这是代码:
之前有效的方法失败了,没有任何意义的消息。 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,它可以工作:) - 这是我的意思的图像:
答案 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个):
强制转换为静态声明 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
使用using (var conn = (SqlConnection) OpenConnection())
的运行时类型:
conn
不要动态绑定CreateCommand:
using (dynamic conn = OpenConnection())
静态定义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();它将是正确的类型,并且已经设置了连接。