问题 从dapper调用存储过程,接受用户定义的表类型列表


我有一个存储过程 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调用这个存储过程。我用Google搜索并找到了一些解决方案。

 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; }
}

谢谢你的帮助

EDITED

我找到了解决方案

Dapper是否支持SQL 2008表值参数?

要么

Dapper是否支持SQL 2008表值参数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


1978
2017-08-16 09:16


起源



答案:


我的问题是我在通用列表List Cars中有汽车,我希望将此列表传递给存储过程。它优雅的方式存在怎么办?

你需要 将您的通用列表Car转换为数据表 然后将其传递给storedprocedure。需要注意的是,字段的顺序必须是 与用户定义的表类型中定义的相同 在数据库中。否则数据将无法正常保存。它 必须具有相同数量的列 同样。

我使用此方法将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;
    }

8
2017-08-16 09:27



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


答案:


我的问题是我在通用列表List Cars中有汽车,我希望将此列表传递给存储过程。它优雅的方式存在怎么办?

你需要 将您的通用列表Car转换为数据表 然后将其传递给storedprocedure。需要注意的是,字段的顺序必须是 与用户定义的表类型中定义的相同 在数据库中。否则数据将无法正常保存。它 必须具有相同数量的列 同样。

我使用此方法将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;
    }

8
2017-08-16 09:27



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


我知道这有点旧了,但我想我会发布这个,因为我试图让这更容易一点。我希望我已经使用我创建的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 仍然处于早期阶段,所以可能无法解决所有问题!

请阅读README并随时在GitHub上做出贡献: https://github.com/RasicN/Dapper-Parameters


2
2017-12-28 18:13



感谢NuGet! - optim1st
你知道任何.net核心替代品吗? - FiringSquadWitness
我没有更新这个与.net核心一起工作认为它是开源的,所以请随意考虑添加它。如果没有别的,请添加它作为跟踪它的问题,我将最终考虑更新它。 - JCisar


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

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

var result = con.Query<dynamic>("InsertCars", param);

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

直接使用表类型的参数,它将起作用。

如果你可以使用Datatable(.net核心不支持它),那么它非常容易。

创建DataTable - >添加所需的列以匹配您的表类型 - >添加所需的行。最后用这样的小巧玲子来调用它。

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

0
2017-09-21 23:56



你在.Net Core做什么呢? - Jorge Yanes Diez
解决方案的第一部分是.Net Core - pawan nepal