问题 通过Entity Framework将表值类型传递给SQL Server存储过程


我在SQL Server中创建了一个用户定义的表类型:

CREATE TYPE dbo.TestType AS TABLE 
(
    ColumnA int,
    ColumnB nvarchar(500)
)

我正在使用存储过程将记录插入数据库:

create procedure [dbo].[sp_Test_CustomType]
   @testing TestType READONLY
as
    insert into [dbo].[myTable]
        select ColumnA, ColumnB 
        from @testing

我想使用EF来执行此存储过程,但问题是:如何将用户定义的表传递给存储过程?

我尝试将存储过程添加到模型中,但我无法在更新的上下文中找到所需的存储过程。

我要做的是对表执行批量插入,这是我目前使用的方法:

List<items> itemToInsertToDB = //fetchItems;

foreach(items i in itemToInsertToDB)
{
     context.sp_InsertToTable(i.ColumnA, i.ColumnB)
}

目前,我用的是 foreach 循环遍历列表以将项插入到DB,但如果列表中有很多项,那么就会出现性能问题,因此,我正在考虑将列表传递给存储过程并在内部插入。

那么如何解决这个问题呢?或者有更好的方法吗?


7502
2017-11-18 06:04


起源

c-sharpcorner.com/UploadFile/78607b/... - Nikhil Vartak


答案:


假设您要发送一个包含一列GUID的表。

首先,我们需要使用创建结构 SqlMetaData 表示表(列)的模式。

var tableSchema = new List<SqlMetaData>(1)
{
  new SqlMetaData("Id", SqlDbType.UniqueIdentifier)
}.ToArray();

接下来,您将创建与使用的架构匹配的记录列表 的SqlDataRecord

var tableRow = new SqlDataRecord(tableSchema);
tableRow.SetGuid(0, Guid.NewGuid());
var table = new List<SqlDataRecord>(1)
{
  tableRow
};

然后创建 的SqlParameter

var parameter = new SqlParameter();
parameter.SqlDbType = SqlDbType.Structured;
parameter.ParameterName = "@UserIds";
parameter.Value = table;

var parameters = new SqlParameter[1]
{
  parameter
};

然后只需使用。调用存储过程 Database.SqlQuery

IEnumerable<ReturnType> result;
using (var myContext = new DbContext())
{
  result = myContext.Database.SqlQuery<User>("GetUsers", parameters)
    .ToList();         // calls the stored procedure
    // ToListAsync();  // Async
{

在SQL Server中,创建用户定义的表类型(我用TTV,表类型值后缀):

CREATE TYPE [dbo].[UniqueidentifiersTTV] AS TABLE(
  [Id] [uniqueidentifier] NOT NULL
)
GO

然后将类型指定为参数(不要忘记,表类型值必须是只读的!):

CREATE PROCEDURE [dbo].[GetUsers] (
  @UserIds [UniqueidentifiersTTV] READONLY
) AS
BEGIN
  SET NOCOUNT ON

  SELECT u.* -- Just an example :P
  FROM [dbo].[Users] u
  INNER JOIN @UserIds ids On u.Id = ids.Id
END

10
2017-11-18 06:37



.SetGuid来自哪里? - eran otzap
SqlDataRecord.SetGuid() - Erik Philips
1)鼠标悬停 result 在调试器中将抛出虚假异常“存储过程必须声明标量变量”,因为它试图两次计算参数。使用 .ToList() (如上所述)避免这种情况。 2)我得到异常“表类型参数'@table'必须具有有效的类型名称。”通过指定参数类型修复了: parameter.TypeName = "dbo.TestType" (带“dbo”前缀) - BurnsBA
@BurnsBA如果你的代码有问题,在评论中发帖不是正确的解决方案。而是使用 Ask A Question 按钮,显示您的代码,提出问题并参考此问题/答案。 - Erik Philips
我不认为BurnsBA试图询问有关代码的问题。我认为他正试图提出改进建议。至少,这就是他的评论对我的看法。 - Cody Gray♦


我建议您不要使用存储过程来插入批量数据,而只是依赖于Entity Framework插入机制。

List<items> itemToInsertToDB = //fetchItems;
foreach(items i in itemToInsertToDB)
{
    TestType t = new TestType() { ColumnA = i.ColumnA, ColumnB = i.ColumnB };
    context.TestTypes.Add(t);
}
context.SaveChanges();

实体框架将巧妙地在单个事务中执行那些插入,并且(通常)在单个查询执行中执行,这几乎等于执行存储过程。这是更好的,而不是仅仅依靠存储过程来插入大量数据。


3
2017-11-18 06:10



感谢您的建议,我知道Entity框架有一个名为“bulkinsert”的函数 efbulkinsert.codeplex.com,但我被要求使用存储过程以便于维护。 - User2012384
除非您处于使用事务范围的非常复杂的分布式事务场景中,否则EF运行良好。 - Chazt3n
EF并不那么聪明。保存更改会执行事务,但会为插入的每个实体发出单独的插入语句。这就是为什么BulkInsert是一个单独的付费库,尽管存在较旧的免费版本。默认情况下,EF将为插入的每个项目发出单独的插入语句。表现太可怕了。对于批量插入,请使用SqlBulkCopy或使用批量插入扩展的免费版本。 - Triynko