如果在ToList之后链接ForEach,则DynamicParameters无法添加模板

时间:2017-06-27 17:10:11

标签: c# dapper

我有一个基于现有模型动态构建sql语句的函数,然后添加每个表中存在的一些公共字段。

问题在于,当我尝试添加公共字段时,我遇到了一些奇怪的行为。

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

public class Program
{
    private static readonly dynamic[] Data = new dynamic[] { new {Foo = 1, Bar = 2}, new {Foo = 3, Bar = 4} }; 
    public static void Main()
    {
        Console.WriteLine("Hello World");
    }

    public static void Broken()
    {
        var parameters = ((IEnumerable<dynamic>)Data).Select(x => new DynamicParameters(x));
        parameters.ToList().ForEach(x => x.AddDynamicParams(new {Bax = 7}));
        using (var connection = new SqlConnection(""))
        {
            // This fails with a "Must declare scalar variable @Bax".
            connection.Execute("INSERT INTO DataTable(Foo, Bar, Bax) VALUES(@Foo, @Bar, @Bax)", parameters);
        }
    }

    public static void Working()
    {
        var parameters = ((IEnumerable<dynamic>)Data).Select(x => new DynamicParameters(x)).ToList();
        parameters.ForEach(x => x.AddDynamicParams(new {Bax = 7}));
        using (var connection = new SqlConnection(""))
        {
            // This works fine.
            connection.Execute("INSERT INTO DataTable(Foo, Bar, Bax) VALUES(@Foo, @Bar, @Bax)", parameters);
        }
    }
}

如果您注意到,唯一的区别就是我对.ToList()的通话位置。在Broken函数中,我在.ForEach之前将其称为链的一部分,在Working函数中,我将其称为与赋值相同的行。

我已尝试将调用替换为AddDynamicParams,并为每个参数添加Add次调用,但这似乎并不重要。我也试过没有运气for(var parameter in parameters) { parameter.AddDynamicParams(new {Bax = 7}) }

不幸的是,没有好的方法可以在不设置断点的情况下显示templates并在Visual Studio中检查对象的.templates属性。如果有人可以提供更好的方式来证明模板消失,我会全力以赴。

1 个答案:

答案 0 :(得分:2)

Broken方法中,您有效地枚举了两次收藏。这是由于LINQ Deferred Execution

这一行:

 parameters.ToList().ForEach(x => x.AddDynamicParams(new {Bax = 7}));

将使用IEnumerable<dynamic>创建List<dynamic>来枚举您的ForEach,然后根本不使用此列表,然后再次传递parameters }对象connection.Execute方法:

connection.Execute("**your query**", parameters);

每当Dapper枚举parameter对象时,它将再次投射每个对象(在上面的列表中找到的相同对象),从而创建一个未使用AddDynamicParams修改的新实例。

更清楚一点,你破碎的方法与以下内容相同:

public static void Broken()
{
    var parameters = ((IEnumerable<dynamic>)Data).Select(x => new DynamicParameters(x));
    var parametersList = parameters.ToList(); // you create a list
    parametersList.ForEach(x => x.AddDynamicParams(new {Bax = 7})); // then you modify that list

    // parametersList != parameters

    using (var connection = new SqlConnection(""))
    {
        // This fails with a "Must declare scalar variable @Bax".
        connection.Execute("INSERT INTO DataTable(Foo, Bar, Bax) VALUES(@Foo, @Bar, @Bax)", parameters); // but here you are passing the old parameters collection
    }
}

要解决您的问题,请使用您的Working版本,或再次使用LINQ创建可运行的可枚举版本:

var parameters = ((IEnumerable<dynamic>)Data).Select(x =>
{
    var p = new DynamicParameters(x);
    p.AddDynamicParams(new {Bax = 7});
    return p;
});