在Data Core 2.0中使用DataTable作為表值參數

c# entity-framework entity-framework-core table-valued-parameters

[更新的問題描述]我們有一個批量導入過程,我們將IEnumerable<SqlDataRecord>作為表值參數(TVP)傳遞給存儲過程,因為在EF Core 1.1之前, DataTable類型不可用。我們剛剛升級了我們的項目以使用.Net Core 2.0並開始更新代碼以使用DataTable。 ExecuteSqlCommandAsync命令開始拋出InvalidCastException 。以下是異常詳細信息:

System.InvalidCastException occurred
  HResult=0x80004002
  Message=Failed to convert parameter value from a DataTable to a IEnumerable`1.
  Source=<Cannot evaluate the exception source>
  StackTrace:
   at System.Data.SqlClient.SqlParameter.CoerceValue(Object value, MetaType destinationType, Boolean& coercedToDataFeed, Boolean& typeChanged, Boolean allowStreaming)
   at System.Data.SqlClient.SqlParameter.GetCoercedValue()
   at System.Data.SqlClient.SqlParameter.Validate(Int32 index, Boolean isCommandProc)
   at System.Data.SqlClient.SqlCommand.BuildParamList(TdsParser parser, SqlParameterCollection parameters)
   at System.Data.SqlClient.SqlCommand.BuildExecuteSql(CommandBehavior behavior, String commandText, SqlParameterCollection parameters, _SqlRPC& rpc)
   at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, SqlDataReader ds)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean asyncWrite, String method)
   at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite, String methodName)
   at System.Data.SqlClient.SqlCommand.BeginExecuteNonQuery(AsyncCallback callback, Object stateObject)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncImpl(Func`3 beginMethod, Func`2 endFunction, Action`1 endAction, Object state, TaskCreationOptions creationOptions)
   at System.Threading.Tasks.TaskFactory`1.FromAsync(Func`3 beginMethod, Func`2 endMethod, Object state)
   at System.Data.SqlClient.SqlCommand.ExecuteNonQueryAsync(CancellationToken cancellationToken)
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.<ExecuteAsync>d__26.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.<ExecuteSqlCommandAsync>d__11.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at X.Y.Repositories.Repository.<Import>d__4.MoveNext() in Repository.cs:line 95
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at X.Y.Services.ImportService`2.<BulkImportAsync>d__6.MoveNext() in ImportService.cs:line 70

Inner Exception 1:
InvalidCastException: Object must implement IConvertible.

這就是我調用存儲過程的方式:

var dataTable = new DataTable();
dataTable.Columns.Add("Col1", typeof(String));
dataTable.Columns.Add("Col2", typeof(String)); //and so on

foreach (var record in records)
{
    var row = dataTable.NewRow();
    SetStringValue(row, "Col1", record.Field1); //SetStringValue is just a helper method that assigns DBNull.Value if the field value is null. 
    SetStringValue(row, "Col2", record.Field2); //and so on

    dataTable.Rows.Add(row);
}

var param = new SqlParameter("@Records", dataTable)
{
    TypeName = "TVPRecords",
    SqlDbType = SqlDbType.Structured
};

await _dbContext.Database.ExecuteSqlCommandAsync("EXEC dbo.ImportData @Records", param);

由於DataTable現在可以在EF Core 2.0中使用,我仍然無法使用它作為TVP傳遞給存儲過程。是因為它尚未得到支持還是可能是一個錯誤?

熱門答案

我在代碼中找到的唯一可能的錯誤是SqlParameter TypeName。你的不完全合格,我需要包括架構,即“dbo”。

我正在使用EFCore 2.0.2和EFCore.SqlServer 2.0.2

這是我的代碼:

            DataTable table = new DataTable();
            table.Columns.Add(new DataColumn("FieldId", typeof(int)));
            table.Columns.Add(new DataColumn("Value", typeof(double)));
            foreach (Value v in NewRows)
            {
                DataRow row = table.NewRow();
                row["FieldId"] = v.FieldId;
                row["Value"] = v.Value;
                table.Rows.Add(row);
            }

            var param = new SqlParameter("@replacementValues", table) { TypeName = "dbo.CustomSqlType", SqlDbType = SqlDbType.Structured };
            await _dbContext.Database.ExecuteSqlCommandAsync("EXEC dbo.UpdateValues @replacementValues", param);


Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow