11

我很想知道是否有其他人遇到过同样的问题...我在 ORM 上使用 Dapper 作为一个项目,并在IDbConnection接口之外创建了一些我自己的扩展方法,以简化我遇到的代码(我发现是)令人费解的错误。

我将介绍我所经历的过程。

首先,我在一个静态类中为我的项目添加了一个扩展方法,DbExtensions如下所示:

using System.Collections.Generic;
using System.Data;
using System.Linq;

public static class DbExtensions
{
    public static T Scalar<T>(
        this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
    {
        var ret = cnn.Query<T>(sql, param as object, transaction, buffered, commandTimeout, commandType).First();
        return ret;
    }
}

这会产生一个编译错误,描述如下:

'System.Data.IDbConnection' has no applicable method named 'Query' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.

这很好,该错误实际上很有帮助,因为它甚至告诉我如何修复它。所以我然后尝试:

using System.Collections.Generic;
using System.Data;
using System.Linq;

public static class DbExtensions
{
    public static T Scalar<T>(
        this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
    {
        var ret = SqlMapper.Query<T>(cnn, sql, param, transaction, buffered, commandTimeout, commandType).First();
        return ret;
    }
}

它编译正确。不过有些奇怪的事情正在发生。在 Visual Studio 中,如果我获取SqlMapper.Query<T>which should be的返回值IEnumerable<T>,并尝试对其进行操作,Visual Studio 不会给我任何智能感知属性,除了那些通过object.

认为我只是在做一些智能感知不够聪明的事情,我继续我的快乐方式......直到我真正尝试运行代码。

当我尝试运行它时,它会在我调用.First()的地方出现以下错误:

'System.Collections.Generic.List<MyNameSpace.MyClass>' does not contain a definition for 'First'

现在这个错误,我觉得很有趣......在敲了一会儿头之后,我意识到第一个参数是在抱怨动态类型......

我想这个错误的发生是因为编译器无法构建通用模板,因为它不知道 Query IEnumerable<T>在 DLR 中执行时正在返回?我很想听听知识渊博的人解释这一点。我基本上找到了两种解决方法:

  • dynamic参数转换为object
  • 将返回的值转换为IEnumerable<T>

using System.Collections.Generic;
using System.Data;
using System.Linq;

public static class DbExtensions
{
    public static T Scalar<T>(
        this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
    {
        var ret = SqlMapper.Query<T>(cnn, sql, param as object, transaction, buffered, commandTimeout, commandType).First();
        return ret;
    }
}

using System.Collections.Generic;
using System.Data;
using System.Linq;

public static class DbExtensions
{
    public static T Scalar2<T>(
        this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
    {
        var ret = ((IEnumerable<T>)SqlMapper.Query<T>(cnn, sql, param, transaction, commandTimeout, commandType)).First();
        return ret;
    }
}

总之:

我是刚接触 DLR 的 qwerks 的新手,在搞乱动态 + 泛型时似乎有一些注意事项要牢记......?

我知道这本身不是一个问题,但是当我真正开始写这篇文章时,我不知道发生了什么,我在这个过程中弄清楚了!我认为它可能会帮助其他有类似问题的人......

4

1 回答 1

11

如建议的那样,我将尝试在实际答案中回答我的问题......(现在已经 8 小时了)

我对这个问题的理解是这样的:

  • 引用问题中所述,动态类型没有可用的扩展方法,但可以正常使用扩展方法(作为实例方法),就像没有this关键字...

例如:

dynamic list = someListObject;

var item = list.First(); //this will not compile

var item = Enumerable.First(list);  //this will compile

正如 Jon Skeet 在这个答案中指出的那样,这完全是设计和 DLR 实现的一部分 - 如果任何调用具有动态参数,它将具有被认为是动态的返回类型。

  • 出于类似的原因,在扩展方法中使用动态变量有点不稳定......

public static Enumerable<T> ExtensionMethod(this ExtendedObject p1, dynamic p2) {
    //Do Stuff
}

dynamic y = something;
var x = new ExtendedObject();

//this works
var returnedEnumerable = x.ExtensionMethod(y); 

//this doesn't work
var returnedValue = x.ExtensionMethod(y).SomeEnumerableExtensionMethodLikeFirst() 

要使上述示例正常工作,您可以执行以下操作之一:

//cast dynamic as object
var returnedValue = x.ExtensionMethod(y as object).First(); 
//cast returned object
var returnedValue = ((IEnumerable<KnownType>)x.ExtensionMethod(y)).First(); 
于 2012-06-13T14:51:01.517 回答