Ошибка, если в хранимую процедуру передается одинаковое количество значений

#c# #asp.net #sql-server #tsql #dapper

Вопрос:

У меня есть эта хранимая процедура MERGE , которая запускает обновление. Для получения информации он использует параметр с табличным значением. Затем я звоню ему из своего ASP.NET 5 проект, который я использую Dapper ORM для вызова хранимой процедуры.

Все работает нормально, когда хранимая процедура базы данных и проход модели имеют одинаковое количество полей, но это означает, что если мне нужно обновить одну базу данных и обновить ASP.NET проект вызовы базы данных начнут выдавать ошибку или если мне придется понизить текущую версию ASP.NET проект, но не может понизить рейтинг базы данных, также приведет к какой-либо ошибке.

Кто — нибудь знает решение, которое могло бы решить эту проблему?

В принципе, я ищу способ, чтобы они не так сильно полагались на то, чтобы быть конкретной версией, чтобы она работала.

 CREATE TYPE [dbo].ItemType AS TABLE
(
    [Id]                VARCHAR(36)    NOT NULL,
    [Name]              VARCHAR(100)   NULL,
    [FullName]          VARCHAR(100)   NULL,
    [Description]       VARCHAR(4095)  NULL,
    [QuantityOnHand]    DECIMAL(18, 5) NULL,
    [IsActive]          BIT            NULL,
    [TimeCreated]       DATETIME2      NULL,
    [TimeModified]      DATETIME2      NULL,
    [AverageCost]       DECIMAL(18, 5) NULL,
    [SellingPrice]      DECIMAL(18, 5) NULL,
    [ExtCaseText]       VARCHAR(255)   NULL,
    [ExtCaseValue]      INT            NULL ,
    [Barcode]           VARCHAR(50)    NULL,
    [Brand]             VARCHAR(450)   NULL,
    [PurchaseCost]      DECIMAL(18, 5) NULL,
    [OurBrand]          BIT            NULL,
    [Vendor]            VARCHAR(450)   NULL 
)

CREATE PROCEDURE [dbo].UpsertItem
    @UpdateRecords dbo.ItemType READONLY,
    @LastModifiedSync DATETIME2 OUTPUT
AS
BEGIN
    MERGE INTO 
        qbItems AS Target
    USING 
        @UpdateRecords AS Source ON Target.Id = Source.Id

    WHEN MATCHED THEN
        UPDATE  
            SET Target.Id = Source.Id,
                Target.[Name] = Source.[Name],
                Target.[FullName] = Source.[FullName],
                Target.[Description] = Source.[Description],
                Target.QuantityOnHand = Source.QuantityOnHand,
                Target.IsActive = Source.IsActive,
                Target.[TimeCreated] = Source.[TimeCreated],
                Target.TimeModified = Source.TimeModified,
                Target.AverageCost = Source.AverageCost,
                Target.SellingPrice = Source.SellingPrice,
                Target.ExtCaseText = Source.ExtCaseText,
                Target.ExtCaseValue = Source.ExtCaseValue,
                Target.Barcode = Source.Barcode,
                Target.Brand = Source.Brand,
                Target.OurBrand = Source.OurBrand,
                Target.Vendor = Source.Vendor,
                Target.PurchaseCost = Source.PurchaseCost
            
    WHEN NOT MATCHED THEN           
        INSERT (Id, [Name], [FullName], [Description],
                QuantityOnHand, IsActive, [TimeCreated], TimeModified,
                AverageCost, SellingPrice, ExtCaseText, ExtCaseValue,
                Barcode, Brand, OurBrand, Vendor, PurchaseCost)
        VALUES (Source.Id, Source.[Name], Source.[FullName], Source.[Description],
                Source.QuantityOnHand, Source.IsActive, Source.[TimeCreated], Source.TimeModified,       
                Source.AverageCost, Source.SellingPrice, Source.ExtCaseText, Source.ExtCaseValue,
                Source.Barcode, Source.Brand, Source.OurBrand, Source.Vendor, Source.PurchaseCost);

    SELECT @LastModifiedSync = [TimeModified] 
    FROM qbItems 
    ORDER BY TimeModified ASC;
END
 

Код C# для вызова процедуры:

 public bool Create(ref List<qbItem> items)
{
    // LastMod = new DateTime();
    if (items.Count <= 0) 
        return true;

    try
    {
        var p = new DynamicParameters();
        p.Add("UpdateRecords", items.Where(x => x.FullName != null).OrderBy(x => x.TimeModified).ToDataTable().AsTableValuedParameter("ItemType"));
        p.Add("LastModifiedSync", dbType: DbType.DateTime, direction: ParameterDirection.Output);
        p.Add("affectedRows", dbType: DbType.Int32, direction: ParameterDirection.ReturnValue);

        using var connection = new SqlConnection(_dbConnection.DefaultConnection);

        var affectedRows = connection.Execute("[dbo].[UpsertItem]", p,
            commandType: CommandType.StoredProcedure);

            //LastMod = items.OrderByDescending(t => t.TimeModified).Select(x => x.TimeModified).First();

        return true;
    }
    catch (Exception e)
    {
        logger.LogError(e.ToString());
        return false;
    }
}

public static DataTable ToDataTable<T>(this IEnumerable<T> data)
{
    PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T));

    DataTable table = new DataTable();

    for (int i = 0; i < props.Count; i  )
    {
        PropertyDescriptor prop = props[i];
        table.Columns.Add(prop.Name, prop.PropertyType);
    }

    object[] values = new object[props.Count];

    foreach (T item in data)
    {
        for (int i = 0; i < values.Length; i  )
        {
            values[i] = props[i].GetValue(item);
        }

        table.Rows.Add(values);
    }

    return table;
}
 

Пожалуйста, прокомментируйте любую дополнительную информацию, которая может вам понадобиться для оказания помощи.

Ответ №1:

Как насчет того, чтобы ваш C# запросил у БД определение типа элемента и настроил на его основе таблицу данных

 select *
from information_schema.domains
where data_type = 'table type'
 

Вы можете использовать его, чтобы рассказать вам о типе БД на стороне; вероятно, подойдет список имен и типов столбцов

Затем вы можете создать свою таблицу данных, как вы это делаете в настоящее время, а затем настроить ее, определив, какие столбцы добавлять, а какие удалять

  var incs = table.Columns.Cast<DataColumn>().Select(c => c.ColumnName);

 var indb = (query the info schema and make a list)
 

Затем вы можете решить, какие столбцы удалить, а какие добавить:

 var toRemove = incs.Except(indb);
var toAdd = indb.Except(incs);
 

И соответствующим образом скорректируйте данные

 foreach(.. in toRemove)
  dt.Columns.Remove(..)
 

Добавить, вероятно, немного сложнее из-за сопоставления типов бд с типами c#, но не невозможно