web-dev-qa-db-fra.com

Appel de la procédure stockée de dapper qui accepte la liste de type de table défini par l'utilisateur

J'ai une procédure stockée InsertCars qui accepte la liste des types de table définis par l'utilisateur 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

J'ai besoin d'appeler cette procédure stockée de Dapper. Je l'ai googlé et j'ai trouvé des solutions.

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

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

Mais j'obtiens une erreur:

La procédure ou la fonction InsertCars a trop d'arguments spécifiés

La procédure stockée InsertCars renvoie également le nombre de lignes insérées; J'ai besoin de cette valeur.

Où est la racine du problème?

Mon problème est aussi que j'ai des voitures dans la liste générique List<Car> Cars et je veux passer cette liste pour stocker la procédure. Il existe de manière élégante comment le faire?

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

Merci pour l'aide

[~ # ~] a modifié [~ # ~]

J'ai trouvé des solutions

Dapper prend-il en charge les paramètres de table SQL 2008?

ou

Dapper prend-il en charge les paramètres table SQL 2008 2?

Alors j'essaye de créer ma propre classe d'aide stupide

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

Utilisation

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

Je reçois une exception

Lorsque vous utilisez les API de mappage multiple, assurez-vous de définir le paramètre splitOn si vous avez des clés autres que Id.

Trace de la pile:

   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

Qu'est-ce qui ne va pas?

FIXE:

Appel con.Execute au lieu con.Query

18
imodin

Mon problème est également que j'ai des voitures dans la liste générique Liste des voitures et je veux passer cette liste à la procédure de stockage. Il existe de manière élégante comment le faire?

Vous devez convertir votre liste générique Car en un datatable puis le passer à la procédure stockée. Un point à noter est que l'ordre de vos champs doit être identique à celui défini dans le type de table défini par l'utilisateur dans la base de données. Sinon, les données ne seront pas enregistrées correctement. Et il doit avoir le même nombre de colonnes aussi.

J'utilise cette méthode pour convertir List en DataTable. Vous pouvez l'appeler comme 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;
    }
14
Ehsan

Je sais que c'est un peu vieux, mais je pensais que je publierais quand même à ce sujet car je cherchais à rendre cela un peu plus facile. J'espère que je l'ai fait avec un package NuGet que j'ai créé qui permettra d'utiliser du code comme:

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

Package NuGet: https://www.nuget.org/packages/Dapper.ParameterExtensions/0.2. Encore à ses débuts, il peut donc ne pas fonctionner avec tout!

Veuillez lire le README et n'hésitez pas à contribuer sur GitHub: https://github.com/RasicN/Dapper-Parameters

7
JCisar

L'utilisation de la réflexion pour mapper les propriétés d'un objet sur des colonnes datables est coûteuse. En poussant plus loin la solution d'Ehsan, lorsque les performances sont un problème, vous pouvez mettre en cache les mappages de propriétés de type. Comme Ehsan l'a également souligné, l'ordre dans la classe doit être le même que dans la base de données et il doit y avoir un nombre égal de colonnes. Cela peut être surmonté en réorganisant les colonnes en fonction de la définition du type.

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
{

}
0
Jamie Pearcey

L'autre solution serait de l'appeler comme ça

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

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

Supprimer: nouveau CarDynamicParam (voiture), commandType: CommandType.StoredProcedure

Utilisez directement le paramètre de type de table, cela fonctionnera.

Si vous pouvez utiliser Datatable (.net core ne le prend pas en charge), alors c'est très simple.

Créer DataTable -> Ajouter les colonnes requises pour correspondre avec votre type de table -> Ajouter les lignes requises. Enfin appelez-le simplement en utilisant dapper comme ça.

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