【问题标题】:How do I pass a table-valued parameter to Dapper in .NET Core?现在有没有办法将 TVP 传递给 .Net Core 上的 dapper?
【发布时间】:2016-12-13 23:29:34
【问题描述】:

我正在使用 .net core 和 dapper,第一个没有 DataTables,第二个将它们用于 TVP。

我试图将 List<T> 转换为 List<SqlDataRecord>,使用此列表创建一个 SqlParameter,然后将其转换为 DynamicParameter 但遗憾的是我得到了一个:The member of type Microsoft.SqlServer.Server.SqlDataRecord cannot be used as a parameter value

更新

在和IDynamicParameters 玩了一会儿之后,我成功了。

IEnumerable的扩展方法

public static DynamicWrapper toTVP<T>(this IEnumerable<T> enumerable, string tableName, string typeName)
{
    List<SqlDataRecord> records = new List<SqlDataRecord>();
    var properties = typeof(T).GetProperties().Where(p => Mapper.TypeToSQLMap.ContainsKey(p.PropertyType));
    var definitions = properties.Select(p => Mapper.TypeToMetaData(p.Name, p.PropertyType)).ToArray();
    foreach (var item in enumerable)
    {
        var values = properties.Select(p => p.GetValue(item, null)).ToArray();
        var schema = new SqlDataRecord(definitions);
        schema.SetValues(values);
        records.Add(schema);
    }

    SqlParameter result = new SqlParameter(tableName, SqlDbType.Structured);
    result.Direction = ParameterDirection.Input;
    result.TypeName = typeName;
    result.Value = records;
    return new DynamicWrapper(result);
}

实现IDynamicParameters的包装器

public class DynamicWrapper : IDynamicParameters
{
    private readonly SqlParameter _Parameter;
    public DynamicWrapper(SqlParameter param)
    {
        _Parameter = param;
    }

    public void AddParameters(IDbCommand command, Identity identity)
    {
        command.Parameters.Add(_Parameter);
    }
}

Mapper(未经过全面测试,仅对 NVARCHAR 进行托管字符串,因为它会在没有 maxLength 的情况下引发异常)

public class Mapper
{
    public static Dictionary<Type, SqlDbType> TypeToSQLMap = new Dictionary<Type, SqlDbType>()
        {
              {typeof (long),SqlDbType.BigInt},
              {typeof (long?),SqlDbType.BigInt},
              {typeof (byte[]),SqlDbType.Image},
              {typeof (bool),SqlDbType.Bit},
              {typeof (bool?),SqlDbType.Bit},
              {typeof (string),SqlDbType.NVarChar},
              {typeof (DateTime),SqlDbType.DateTime2},
              {typeof (DateTime?),SqlDbType.DateTime2},
              {typeof (decimal),SqlDbType.Money},
              {typeof (decimal?),SqlDbType.Money},
              {typeof (double),SqlDbType.Float},
              {typeof (double?),SqlDbType.Float},
              {typeof (int),SqlDbType.Int},
              {typeof (int?),SqlDbType.Int},
              {typeof (float),SqlDbType.Real},
              {typeof (float?),SqlDbType.Real},
              {typeof (Guid),SqlDbType.UniqueIdentifier},
              {typeof (Guid?),SqlDbType.UniqueIdentifier},
              {typeof (short),SqlDbType.SmallInt},
              {typeof (short?),SqlDbType.SmallInt},
              {typeof (byte),SqlDbType.TinyInt},
              {typeof (byte?),SqlDbType.TinyInt},
              {typeof (object),SqlDbType.Variant},
              {typeof (DataTable),SqlDbType.Structured},
              {typeof (DateTimeOffset),SqlDbType.DateTimeOffset}
        };

    public static SqlMetaData TypeToMetaData(string name, Type type)
    {
        SqlMetaData data = null;

        if (type == typeof(string))
        {
            data = new SqlMetaData(name, SqlDbType.NVarChar, -1);
        }
        else
        {
            data = new SqlMetaData(name, TypeToSQLMap[type]);
        }

        return data;
    }
}

我的示例的 SQL 类型:

CREATE TYPE TestType AS TABLE ( 
    FirstName NVARCHAR(255)  
    , GamerID INT 
    , LastName NVARCHAR(255)
    , Salt UNIQUEIDENTIFIER);  
GO  

使用它:

List<Gamer> gamers = new List<Gamer>();

gamers.Add(new Gamer {
                Email = new string[] { "dsadsdsa@dasddas.com" },
                FirstName = "Test_F0",
                LastName = "Test_L0",
                GamerID = 0,
                Salt = Guid.NewGuid()});

            gamers.Add(new Gamer {
                Email = new string[] { "11111@11111.com" },
                FirstName = "Test_F1",
                LastName = "Test_L1",
                GamerID = 1,
                Salt = Guid.NewGuid()});

            var structured = gamers.toTVP("GamerTable", "dbo.TestType");

            using (var con = new SqlConnection(TestConnectionString))
            {
                con.Open();

                string query = @"

                SELECT * 
                FROM @GamerTable t
                WHERE t.GamerID = 1

                ";

var result = con.Query(query, structured);

//var result = con.Query("dbo.DapperTest", structured, commandType: CommandType.StoredProcedure);

如您所见,模型去除了电子邮件的字符串数组,因为我没有将其编码为嵌套 tvp。 (TypeToSQLMap.ContainsKey 部分),但可以进行编码,将包装器更改为接受可枚举的参数,并将 AddParameters 更改为 foreach 并添加它们。更多的是关于类型名称等问题。我正在考虑创建一些基于属性类型命名的泛型类型。暂时这样就够了,不升级可以升级。

我会在今天晚些时候尝试改进它。

【问题讨论】:

  • 在任何情况下都无法正常工作,因为 TVP 需要 TypeName,而这不是从 structured 读取的。根据this,自己实现IDynamicParameters 应该可以工作。
  • 哦,你在更新后发表了评论:p 我正在回家的路上,所以我将在 30 分钟内测试我的理论。
  • @Nauzet 请将您对问题的答案发布在答案部分以供其他人查找,谢谢!
  • 自从提出这个问题以来,.NET Core 2.0 的预览版已经发布,其中包括 DataTables。我知道迁移到预览版是一件大事,但如果您有可能,您现在可以使用 .NET Core 2.0。
  • @AdrienTorris 对不起,我知道你的评论有点老了,但我想我会补充一点,是的,.Net Core 2.0 支持 DataTables,但是 AsTableValuedParameter() 方法没有实现,所以据我所知,仍然需要一种解决方法

标签: c# sql reflection .net-core dapper


【解决方案1】:

是的,这是可能的。在 .NET Framework 中,您可以使用 .AsTableValuedParameter extension 方法,但在 .NET Core 中没有此选项(从 Dapper v 1.5 开始)要解决此问题,您必须创建一个实现 ICustomQueryMapper 的类:

public class TabledValuedParameter: ICustomQueryMapper
{
    public void AddParameter() {...}
}

然后您可以使用它来包装您的 IEnumerable。我在这里写过关于这个主题的文章:

https://medium.com/dapper-net/sql-server-specific-features-2773d894a6ae

示例代码可在 GitHub 上获得:

https://github.com/yorek/dapper-samples/blob/master/Dapper.Samples.Advanced/SQLServerFeatures.cs

2019 年 12 月更新

现在不再需要解决方法,因为 Dapper 2.0 原生支持 TVP。我刚刚更新了我的示例,现在一切正常。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-11
    • 1970-01-01
    • 2015-07-27
    • 2021-04-13
    相关资源
    最近更新 更多