【问题标题】:C# Object doesnt support FirstOrDefault() depending on the input parameterC# 对象不支持 FirstOrDefault() 取决于输入参数
【发布时间】:2022-01-21 07:01:12
【问题描述】:

我不知道为什么 LoadData 函数的返回值(如下所示)在一种情况下支持 FirstOrDefault() 方法,而在另一种情况下不支持。在这两种情况下,它都是相同的返回类型。

唯一的区别是动态参数的创建方式。

    public async Task<LookupModel?> UpdateLookup(LookupModel item, Guid newUk)
        var p1 = new
        {
            item.Code,
            item.Name,
            item.uk,
            newUk
        };

        var result1 = await _db.LoadData<LookupModel, dynamic>("lu.Lookup_Update", p1);
                // result1.GetType() = System.Collections.Generic.List`1[DataAccess.Models.Lookups.LookupModel]
        var rtv1 = result1.FirstOrDefault();        // ok


        var p2 = item.GetUpdateParams(newUk);
        
        var result2 = await _db.LoadData<LookupModel, dynamic>("lu.Lookup_Update", p2);
                // result2.GetType() = System.Collections.Generic.List`1[DataAccess.Models.Lookups.LookupModel]

        var rtv2 = result2.FirstOrDefault();        // not ok
                                                    // This throws an exception as FirstOrDefault() is not defined

        return rtv1;
    }

    // LookupModel
    public dynamic GetUpdateParams(Guid newUk)
    {
        return new
        {
            this.Code,
            this.Name,
            this.uk,
            newUK
        };
    }

    // LoadData
    public async Task<IEnumerable<T>> LoadData<T, U>(string storedProcedure, U parameters, string connectionId = "Default")
    {
        using IDbConnection connection = new SqlConnection(_config.GetConnectionString(connectionId));
        return await connection.QueryAsync<T>(storedProcedure, parameters, commandType: CommandType.StoredProcedure);

    }


【问题讨论】:

  • 因为 p1 是具体类型,而 p2 是对象类型(在运行时)。所以在运行时,LoadData 将分别对匿名类型或对象是通用的。由于对象没有任何属性,因此失败。
  • 它会抛出异常还是给你一个编译器错误?

标签: c# linq dapper


【解决方案1】:

从这一行中删除等待

//var result1 = await _db.LoadData<LookupModel, dynamic>("lu.Lookup_Update", p1);
var result1 = _db.LoadData<LookupModel, dynamic>("lu.Lookup_Update", p1);

改变

var rtv1 = result1.FirstOrDefault();

var rtv1= await result1.FirstOrDefaultAsync()

应该添加属于FirstorDefaultAsync()的命名空间。

【讨论】:

    【解决方案2】:

    因为 p1 是具体类型,而 p2 是对象类型(在运行时)。所以在运行时,LoadData 将分别对匿名类型或对象是通用的。由于对象没有任何属性,因此失败。

    我建议在调用 LoadData 之前将 p2 克隆为具体的匿名类型

    Parameters p2clone = new { Code = p2.Code...};
    var bar = LoadData(p2clone);
    
    interface Parameters 
    {
        string Code {get;}
        string Name {get;}
        ...
    }
    

    您需要这样做的原因是 Linq 表达式编译器在传入的类型上大量使用反射。没有类型信息,dapper 无法编译您的 SQL。

    【讨论】:

    • p2dynamic,所以p2clone 也是动态的,这仍然会阻止调用扩展方法。
    • @DStanley 该死的,不错的收获。我忘了 p2 的属性在编译时是未知的。
    • 是的,动态是很棘手的——直到运行时你才意识到你已经绕过了类型安全。
    【解决方案3】:

    p2dynamic,也就是说result2也是动态的(因为它需要p2作为输入),而FirstOrDefault是一个扩展方法。由于result2的类型在编译时是未知的,所以不能对其调用扩展方法。

    您仍然可以通过使用实际的静态方法而不是扩展方法来做到这一点:

    var rtv2 = Enumerable.FirstOrDefault(result2);
    

    因为静态方法可以在运行时绑定。

    但我只想更改GetUpdateParams,使其返回命名类型而不是匿名类型,然后您就不必使用dynamic。由于编译器可以进行类型检查,而且您不必等到运行时才发现类型错误,因此这也会为您提供更高的类型安全性。

    【讨论】:

    • 这是有道理的。我在想 p1 也是动态的,无法理解不同的行为,但我现在看到 p1 是一个匿名类型。 Enumerable.FirstOrDefault(result2) 有效,但您对命名类型的建议听起来更好。谢谢。
    猜你喜欢
    • 1970-01-01
    • 2020-11-24
    • 2022-01-11
    • 2019-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-09
    • 1970-01-01
    相关资源
    最近更新 更多