【问题标题】:Pass table value type to SQL Server stored procedure via Entity Framework通过实体框架将表值类型传递给 SQL Server 存储过程
【发布时间】:2016-02-19 18:22:06
【问题描述】:

我在 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循环遍历列表以将项目插入数据库,但如果列表有很多项目,那么就会出现性能问题,所以,我正在考虑传递一个列出存储过程并在里面插入。

那么如何解决这个问题呢?还是有更好的方法来做到这一点?

【问题讨论】:

标签: c# sql sql-server entity-framework stored-procedures


【解决方案1】:

我建议你不要使用存储过程来插入批量数据,而只是依靠实体框架插入机制。

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

实体框架将智能地在单个事务中和(通常)在单个查询执行中执行这些插入,这几乎等于执行存储过程。这比仅仅依靠存储过程插入大量数据要好。

【讨论】:

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

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

首先,我们需要使用SqlMetaData 创建一个结构,它表示表(列)的架构。

下面的代码演示了GUID中名为“Id”的一列是SQL存储过程参数表类型

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

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

下面的代码演示了如何使用上面创建的架构在列表中添加项目。为列表中的每个项目创建一个新的 SqlDataRecord。将 SetGuid 替换为对应的类型,将 Guid.NewGuid() 替换为对应的值。 为每个项目重复新的 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"; //@UserIds is the stored procedure parameter name
parameter.TypeName = "{Your stored procedure type name}"
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 @UserIds", parameters)
    .ToList();         // calls the stored procedure
    // ToListAsync();  // Async
{

在 SQL Server 中,创建您的用户定义的表类型(我在它们后面加上 TTV,表类型值):

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

然后将类型指定为参数(别忘了,Table Type Values 必须是只读的!):

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

【讨论】:

  • .SetGuid 是从哪里来的?
  • 1) 调试器中的 mouseover result 将抛出虚假异常“存储过程必须声明标量变量”,因为它试图对参数进行两次评估。使用.ToList()(如上)来避免这种情况。 2)我收到异常“表类型参数'@table'必须有一个有效的类型名称。”已通过指定参数类型修复:parameter.TypeName = "dbo.TestType"(带有“dbo”前缀)
  • 我不认为 BurnsBA 试图询问有关代码的问题。我认为他试图提出改进建议。至少,他的评论对我来说是这样的。
  • 嗨。首先,感谢您的解决方案。我使用它,但对我不起作用。我花了大约 2 个小时找出问题所在。见stackoverflow.com/questions/46908349/…。问题是,我在调用“myContext.Database.SqlQuery("GetUsers", parameters)”时忘记传递参数。如果你更新你的答案会很棒,所以它看起来像这样“myContext.Database.SqlQuery("GetUsers @UserIds", parameters)”。谢谢,马丁
猜你喜欢
  • 1970-01-01
  • 2016-01-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多