【问题标题】:Call stored procedure from dapper which accept list of user defined table type从接受用户定义表类型列表的 dapper 调用存储过程
【发布时间】:2013-08-16 09:16:48
【问题描述】:

我有一个存储过程InsertCars,它接受用户定义的表类型列表CarType

CREATE TYPE dbo.CarType
AS TABLE
(
    CARID int null,
    CARNAME varchar(800) not null,
);

CREATE PROCEDURE dbo.InsertCars
    @Cars AS CarType READONLY
AS
-- RETURN COUNT OF INSERTED ROWS
END

我需要从 Dapper 调用这个存储过程。我搜索了一下,找到了一些解决方案。

 var param = new DynamicParameters(new{CARID= 66, CARNAME= "Volvo"});

 var result = con.Query("InsertCars", param, commandType: CommandType.StoredProcedure);

但我得到一个错误:

过程或函数 InsertCars 指定的参数过多

同样存储过程InsertCars返回插入的行数;我需要得到这个值。

问题的根源在哪里?

我的问题也是我在通用列表List<Car> Cars 中有汽车,我想将此列表传递给存储过程。它存在优雅的方式如何做到这一点?

public class Car
{
    public CarId { get; set; }
    public CarName { get; set; }
}

感谢您的帮助

已编辑

我找到了解决办法

Does Dapper support SQL 2008 Table-Valued Parameters?

Does Dapper support SQL 2008 Table-Valued Parameters 2?

所以我尝试制作自己的愚蠢助手类

class CarDynamicParam : Dapper.SqlMapper.IDynamicParameters
{
    private Car car;

    public CarDynamicParam(Car car)
    {
        this.car = car;
    }

    public void AddParameters(IDbCommand command, SqlMapper.Identity identity)
    {
        var sqlCommand = (SqlCommand)command;

        sqlCommand.CommandType = CommandType.StoredProcedure;

        var carList = new List<Microsoft.SqlServer.Server.SqlDataRecord>();

        Microsoft.SqlServer.Server.SqlMetaData[] tvpDefinition =
                                                                {

                                                                    new Microsoft.SqlServer.Server.SqlMetaData("CARID", SqlDbType.Int),
                                                                    new Microsoft.SqlServer.Server.SqlMetaData("CARNAME", SqlDbType.NVarChar, 100),
                                                                };

        var rec = new Microsoft.SqlServer.Server.SqlDataRecord(tvpDefinition);
        rec.SetInt32(0, car.CarId);
        rec.SetString(1, car.CarName);

        carList.Add(rec);

        var p = sqlCommand.Parameters.Add("Cars", SqlDbType.Structured);
        p.Direction = ParameterDirection.Input;
        p.TypeName = "CarType";
        p.Value = carList;
    }
}

使用

var result = con.Query("InsertCars", new CarDynamicParam(car), commandType: CommandType.StoredProcedure);

我得到异常

使用多映射 API 时,如果您的键不是 Id,请确保设置 splitOn 参数。

堆栈跟踪:

   at Dapper.SqlMapper.GetDynamicDeserializer(IDataRecord reader, Int32 startBound, Int32 length, Boolean returnNullIfFirstMissing) in c:\Dev\Dapper\Dapper\SqlMapper.cs:line 1308
   at Dapper.SqlMapper.GetDeserializer(Type type, IDataReader reader, Int32 startBound, Int32 length, Boolean returnNullIfFirstMissing) in c:\Dev\Dapper\Dapper\SqlMapper.cs:line 1141
   at Dapper.SqlMapper.<QueryInternal>d__d`1.MoveNext() in c:\Dev\Dapper\Dapper\SqlMapper.cs:line 819
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at Dapper.SqlMapper.Query[T](IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Boolean buffered, Nullable`1 commandTimeout, Nullable`1 commandType) in c:\Dev\Dapper\Dapper\SqlMapper.cs:line 770
   at Dapper.SqlMapper.Query(IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Boolean buffered, Nullable`1 commandTimeout, Nullable`1 commandType) in c:\Dev\Dapper\Dapper\SqlMapper.cs:line 715

怎么了?

已修复:

请致电con.Execute 而不是con.Query

【问题讨论】:

    标签: c# sql-server stored-procedures dapper


    【解决方案1】:

    我的问题也是我在通用列表 List Cars 中有汽车,我想将此列表传递给存储过程。它存在优雅的方式如何做到这一点?

    您需要将通用列表 Car 转换为数据表,然后将其传递给存储过程。需要注意的一点是,您的字段顺序必须与数据库中用户定义的表类型中定义的顺序相同。否则数据将无法正确保存。而且它也必须有相同的列数

    我使用这种方法将 List 转换为 DataTable。你可以像 yourList.ToDataTable() 一样调用它

    public static DataTable ToDataTable<T>(this List<T> iList)
        {
            DataTable dataTable = new DataTable();
            PropertyDescriptorCollection propertyDescriptorCollection =
                TypeDescriptor.GetProperties(typeof(T));
            for (int i = 0; i < propertyDescriptorCollection.Count; i++)
            {
                PropertyDescriptor propertyDescriptor = propertyDescriptorCollection[i];
                Type type = propertyDescriptor.PropertyType;
    
                if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
                    type = Nullable.GetUnderlyingType(type);
    
    
                dataTable.Columns.Add(propertyDescriptor.Name, type);
            }
            object[] values = new object[propertyDescriptorCollection.Count];
            foreach (T iListItem in iList)
            {
                for (int i = 0; i < values.Length; i++)
                {
                    values[i] = propertyDescriptorCollection[i].GetValue(iListItem);
                }
                dataTable.Rows.Add(values);
            }
            return dataTable;
        }
    

    【讨论】:

    • 第一个问题是将参数传递给存储过程。我认为计数和顺序是正确的。我通过 MS SQL Studio 测试了我的程序,它运行良好。用户定义的表类型只有 2 列,CARID 和 CARNAME。在 DynamicParamater 中,我只设置了这两列。所以我不明白哪里有问题?
    • @Wobe 我已经提到了这样做的完整方法。对于表类型参数,我将数据表作为参数而不是动态参数传递
    【解决方案2】:

    我知道这有点老了,但我想我还是会在这上面发帖,因为我试图让这更容易一些。我希望我已经使用我创建的 NuGet 包做到了这一点,该包将允许以下代码:

    public class CarType
    {
      public int CARID { get; set; }
      public string CARNAME{ get; set; }
    }
    
    var cars = new List<CarType>{new CarType { CARID = 1, CARNAME = "Volvo"}};
    
    var parameters = new DynamicParameters();
    parameters.AddTable("@Cars", "CarType", cars)
    
     var result = con.Query("InsertCars", parameters, commandType: CommandType.StoredProcedure);
    

    NuGet 包:https://www.nuget.org/packages/Dapper.ParameterExtensions/0.2.0 仍处于早期阶段,因此可能无法处理所有问题!

    请阅读自述文件并随时在 GitHub 上贡献:https://github.com/RasicN/Dapper-Parameters

    【讨论】:

    • 你知道任何 .net 核心替代品吗?
    • 我还没有更新它以与 .net 核心一起工作,我认为它是开源的,所以请随意考虑添加它。如果没有其他问题,请将其添加为问题以跟踪它,我最终会考虑更新它。
    • @FiringSquadWitness nuget.org/packages/Dapper.ParameterExtensions/2018.12.7.1 应该支持 .net core 2.0 和 2.1
    • @JCisar 感谢您的回复!快一年了,你的传奇!
    • 使用 DapperParameters;将此命名空间添加到 using Dapper; JCisar 你是 2021 年的传奇人物!
    【解决方案3】:

    您也可以使用查询方法以及执行。参数必须是 DataTable 类型,并且可以作为匿名参数集合的一部分提供。如果您的需求很简单,并且不需要通用解决方案来构建 DataTable,那么一个小的非通用函数是最简单的:

    private DataTable CreateDataTable( IEnumerable<AlertChannelContainer> alertData )
    {
        DataTable table = new DataTable();
        table.Columns.Add( "ChannelOrdinal", typeof( int ) );
        table.Columns.Add( "Value", typeof( decimal ) );
    
        foreach ( var alertChannel in alertData )
        {
            var dataRow = table.NewRow();
            dataRow["ChannelOrdinal"] = alertChannel.ChannelOrdinal;
            dataRow["Value"] = alertChannel.Value;
                table.Rows.Add( dataRow );
        }
    
        return table;
    }
    

    那就这样称呼它吧:

    var result = await connection.QuerySingleAsync<AlertMetadata>( 
        "[dbo].[InsertAlert]",
        new
        {
            eventId,
            deviceId,
            timestamp,
            alertThresholds = JsonConvert.SerializeObject( rules ),
            data = CreateDataTable( alertData )
        },
        commandType: CommandType.StoredProcedure );
    

    【讨论】:

      【解决方案4】:

      使用反射将对象属性映射到数据表列的成本很高。进一步采用 Ehsan 的解决方案,在关注性能的情况下,您可以缓存类型属性映射。正如 Ehsan 还指出的,类中的顺序必须与数据库中的顺序相同,并且列数必须相等。这可以通过根据类型定义重新排序列来克服。

      public static class DataTableExtensions
      {
          private static readonly EntityPropertyTypeMap PropertyTypeMap = new EntityPropertyTypeMap();
      
          public static DataTable ToDataTable<T>(this ICollection<T> values)
          {
              if (values is null)
              {
                  throw new ArgumentNullException(nameof(values));
              }
      
              var table = new DataTable();
      
              var properties = PropertyTypeMap.GetPropertiesForType<T>().Properties;
      
              foreach (var prop in properties)
              {
                  table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
              }
      
              foreach (var value in values)
              {
                  var propertyCount = properties.Count();
                  var propertyValues = new object[propertyCount];
      
                  if (value != null)
                  {
                      for (var i = 0; i < propertyCount; i++)
                      {
                          propertyValues[i] = properties[i].GetValue(value);
                      }
                  }
      
                  table.Rows.Add(propertyValues);
              }
      
              return table;
          }
      }
      
      
      public static class DapperExtensions
      {
          private static readonly SqlSchemaInfo SqlSchemaInfo = new SqlSchemaInfo();
      
          public static DataTable ConvertCollectionToUserDefinedTypeDataTable<T>(this SqlConnection connection, ICollection<T> values, string dataTableType = null)
          {
              if (dataTableType == null)
              {
                  dataTableType = typeof(T).Name;
              }
      
              var data = values.ToDataTable();
      
              data.TableName = dataTableType;
      
              var typeColumns = SqlSchemaInfo.GetUserDefinedTypeColumns(connection, dataTableType);
      
              data.SetColumnsOrder(typeColumns);
      
              return data;
          }
      
          public static DynamicParameters AddTableValuedParameter(this DynamicParameters source, string parameterName, DataTable dataTable, string dataTableType = null)
          {
              if (dataTableType == null)
              {
                  dataTableType = dataTable.TableName;
              }
      
              if (dataTableType == null)
              {
                  throw new NullReferenceException(nameof(dataTableType));
              }
      
              source.Add(parameterName, dataTable.AsTableValuedParameter(dataTableType));
      
              return source;
          }
      
          private static void SetColumnsOrder(this DataTable table, params string[] columnNames)
          {
              int columnIndex = 0;
      
              foreach (var columnName in columnNames)
              {
                  table.Columns[columnName].SetOrdinal(columnIndex);
                  columnIndex++;
              }
          }
      }
      
      class EntityPropertyTypeMap
      {
          private readonly ConcurrentDictionary<Type, TypePropertyInfo> _mappings;
      
          public EntityPropertyTypeMap()
          {
              _mappings = new ConcurrentDictionary<Type, TypePropertyInfo>();
          }
      
          public TypePropertyInfo GetPropertiesForType<T>()
          {
              var type = typeof(T);
              return GetPropertiesForType(type);
          }
      
          private TypePropertyInfo GetPropertiesForType(Type type)
          {
              return _mappings.GetOrAdd(type, (key) => new TypePropertyInfo(type));
          }
      }
      
      
      class TypePropertyInfo
      {
          private readonly Lazy<PropertyInfo[]> _properties;
          public PropertyInfo[] Properties => _properties.Value;
      
          public TypePropertyInfo(Type objectType)
          {
              _properties = new Lazy<PropertyInfo[]>(() => CreateMap(objectType), true);
          }
      
          private PropertyInfo[] CreateMap(Type objectType)
          {
              var typeProperties = objectType
                  .GetProperties(BindingFlags.DeclaredOnly |
                                 BindingFlags.Public |
                                 BindingFlags.Instance)
                  .ToArray();
      
              return typeProperties.Where(property => !IgnoreProperty(property)).ToArray();
          }
      
          private static bool IgnoreProperty(PropertyInfo property)
          {
              return property.SetMethod == null || property.GetMethod.IsPrivate || HasAttributeOfType<IgnorePropertyAttribute>(property);
          }
      
          private static bool HasAttributeOfType<T>(MemberInfo propInfo)
          {
              return propInfo.GetCustomAttributes().Any(a => a is T);
          }
      }
      
      public class SqlSchemaInfo
      {
          private readonly ConcurrentDictionary<string, string[]> _udtColumns = new ConcurrentDictionary<string, string[]>();
      
          public string[] GetUserDefinedTypeColumns(SqlConnection connection, string dataTableType)
          {
              return _udtColumns.GetOrAdd(dataTableType, (x) =>
                  connection.Query<string>($@"
                          SELECT name FROM 
                          (
                              SELECT column_id, name
                              FROM sys.columns
                              WHERE object_id IN (
                                SELECT type_table_object_id
                                FROM sys.table_types
                                WHERE name = '{dataTableType}'
                              )
                          ) Result
                          ORDER BY column_id").ToArray());
          }
      }
      
      
      [AttributeUsage(AttributeTargets.Property)]
      public sealed class IgnorePropertyAttribute : Attribute
      {
      
      }
      

      【讨论】:

        【解决方案5】:

        另一种解决方案是这样称呼它

        var param = new DynamicParameters(new{CARID= 66, CARNAME= "Volvo"});
        
        var result = con.Query<dynamic>("InsertCars", param);
        

        删除:new CarDynamicParam(car),commandType:CommandType.StoredProcedure

        直接使用table类型的参数,就可以了。

        如果可以使用Datatable(.net core不支持),那就很简单了。

        创建 DataTable -> 添加所需的列以匹配您的表类型 -> 添加所需的行。最后只需像这样使用 dapper 调用它。

        var result = con.Query<dynamic>("InsertCars", new{paramFromStoredProcedure=yourDataTableInstance}, commandType: CommandType.StoredProcedure);
        

        【讨论】:

        • 那你在 .Net Core 中做什么?
        • 解决方案的第一部分是针对.Net Core
        【解决方案6】:

        添加到上面的 JCisar 结果中,您可以创建一个通用函数,该函数既可用于单个参数,也可在此处列出。您可以将返回类型设置为您的预期结果。

        你可以这样称呼它

        var cars = new List<CarType>{new CarType { CARID = 1, CARNAME = "Volvo"}};
        var parameters = new DynamicParameters();
        parameters.AddTable("@Cars", "CarType", cars)
        

        //调用函数

        ExecuteStordProcedureDynamic("ProcedureName", parameters)
        
        
        public IEnumerable<T> ExecuteStordProcedureDynamic<T>(String ProcedureName, DynamicParameters Param) where T : class
                {
                    using (SqlConnection conn = new SqlConnection(ConnectionString))
                    {
                        conn.Open();
        
                        var result= conn.Query<T>(ProcedureName, Param, commandType: CommandType.StoredProcedure);
                        return result;
                    }
                }
        

        【讨论】:

          【解决方案7】:

          除了上面 Ehsan & Pawan Nepal 的回答外,我还想出了一个适用于 .NET Core 和 .NET 的解决方案。以下是执行此操作的步骤。

          1.创建扩展方法将模型转换为数据表(感谢Ehsan提供上述扩展方法)。

          public static DataTable ToDataTable<T>(this List<T> iList)
              {
                  DataTable dataTable = new DataTable();
                  PropertyDescriptorCollection propertyDescriptorCollection =
                      TypeDescriptor.GetProperties(typeof(T));
                  for (int i = 0; i < propertyDescriptorCollection.Count; i++)
                  {
                      PropertyDescriptor propertyDescriptor = propertyDescriptorCollection[i];
                      Type type = propertyDescriptor.PropertyType;
          
                      if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
                          type = Nullable.GetUnderlyingType(type);
          
          
                      dataTable.Columns.Add(propertyDescriptor.Name, type);
                  }
                  object[] values = new object[propertyDescriptorCollection.Count];
                  foreach (T iListItem in iList)
                  {
                      for (int i = 0; i < values.Length; i++)
                      {
                          values[i] = propertyDescriptorCollection[i].GetValue(iListItem);
                      }
                      dataTable.Rows.Add(values);
                  }
                  return dataTable;
              }
          

          2.将模型转换为数据表。

          var cars = new List<CarType>{new CarType { CARID = 1, CARNAME = "Volvo"}};
          DataTable dt = cars.ToDataTable();
          

          3.最后添加UDT作为动态参数并将其传递给查询

           var parameters = new DynamicParameters();
           parameters.Add("@paramFromStoredProcedure", 
           dt.AsTableValuedParameter("YourUDTNameInDataBase"));
           //Pass it to query
          var result = con.Query<dynamic>("InsertCars", parameters , commandType: CommandType.StoredProcedure);
          

          【讨论】:

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