我正在尝试调用接受表值参数的存储过程。我知道实体框架尚未直接支持这一点,但根据我的理解,您可以使用ExecuteStoreQuery
之外的ObjectContext
命令来完成此操作。我有一个通用的实体框架存储库,我有以下ExecuteStoredProcedure
方法:
public IEnumerable<T> ExecuteStoredProcedure<T>(string procedureName, params object[] parameters)
{
StringBuilder command = new StringBuilder();
command.Append("EXEC ");
command.Append(procedureName);
command.Append(" ");
// Add a placeholder for each parameter passed in
for (int i = 0; i < parameters.Length; i++)
{
if (i > 0)
command.Append(",");
command.Append("{" + i + "}");
}
return this.context.ExecuteStoreQuery<T>(command.ToString(), parameters);
}
命令字符串最终如下:
EXEC someStoredProcedureName {0},{1},{2},{3},{4},{5},{6},{7}
我试图在接受表值参数的存储过程上运行此方法,并且它会中断。我看了here,参数需要是SqlParameter
类型,而表值参数需要将SqlDbType
设置为Structured
。所以我这样做了,我得到一个错误说明:
The table type parameter p6 must have a valid type name
因此,我将SqlParameter.TypeName设置为我在数据库上创建的用户定义类型的名称,然后当我运行查询时,我得到以下真正有用的错误:
Incorrect syntax near '0'.
如果我恢复到ADO.NET并执行数据读取器,我可以运行查询,但我希望使用数据上下文让它工作。
有没有办法使用ExecuteStoreQuery
传递表值参数?此外,我实际上正在使用实体框架代码优先并将DbContext
转换为ObjectContext
以获取ExecuteStoreQuery
方法。这是必要的还是我可以针对DbContext
执行此操作?
答案 0 :(得分:89)
<强>更新强>
我在Nuget Package上添加了对此的支持 - https://github.com/Fodsuk/EntityFrameworkExtras#nuget(EF4,EF5,EF6)
查看GitHub存储库以获取代码示例。
稍微偏离问题,但对于尝试将用户定义的表传递到存储过程的人来说,这一点毫无用处。在玩了Nick的例子和其他Stackoverflow帖子之后,我想出了这个:
class Program
{
static void Main(string[] args)
{
var entities = new NewBusinessEntities();
var dt = new DataTable();
dt.Columns.Add("WarningCode");
dt.Columns.Add("StatusID");
dt.Columns.Add("DecisionID");
dt.Columns.Add("Criticality");
dt.Rows.Add("EO01", 9, 4, 0);
dt.Rows.Add("EO00", 9, 4, 0);
dt.Rows.Add("EO02", 9, 4, 0);
var caseId = new SqlParameter("caseid", SqlDbType.Int);
caseId.Value = 1;
var userId = new SqlParameter("userid", SqlDbType.UniqueIdentifier);
userId.Value = Guid.Parse("846454D9-DE72-4EF4-ABE2-16EC3710EA0F");
var warnings = new SqlParameter("warnings", SqlDbType.Structured);
warnings.Value= dt;
warnings.TypeName = "dbo.udt_Warnings";
entities.ExecuteStoredProcedure("usp_RaiseWarnings_rs", userId, warnings, caseId);
}
}
public static class ObjectContextExt
{
public static void ExecuteStoredProcedure(this ObjectContext context, string storedProcName, params object[] parameters)
{
string command = "EXEC " + storedProcName + " @caseid, @userid, @warnings";
context.ExecuteStoreCommand(command, parameters);
}
}
,存储过程如下所示:
ALTER PROCEDURE [dbo].[usp_RaiseWarnings_rs]
(@CaseID int,
@UserID uniqueidentifier = '846454D9-DE72-4EF4-ABE2-16EC3710EA0F', --Admin
@Warnings dbo.udt_Warnings READONLY
)
AS
,用户定义的表如下所示:
CREATE TYPE [dbo].[udt_Warnings] AS TABLE(
[WarningCode] [nvarchar](5) NULL,
[StatusID] [int] NULL,
[DecisionID] [int] NULL,
[Criticality] [int] NULL DEFAULT ((0))
)
我发现的限制因素包括:
ExecuteStoreCommand
的参数必须与存储过程中的参数一致答案 1 :(得分:12)
好的,这是一个 2018更新:端到端解决方案,该解决方案描述了如何使用来自实体框架 的表参数调用存储过程而没有nuget包 < / strong>
我正在使用EF 6.xx,SQL Server 2012和VS2017
假设您有一个像这样定义的简单表类型(只有一列)
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touched")
super.touchesBegan(touches, with: event)
}
和一个包含多个参数的存储过程:
go
create type GuidList as table (Id uniqueidentifier)
在SQL中你会使用类似的东西:
go
create procedure GenerateInvoice
@listIds GuidList readonly,
@createdBy uniqueidentifier,
@success int out,
@errorMessage nvarchar(max) out
as
begin
set nocount on;
begin try
begin tran;
--
-- Your logic goes here, let's say a cursor or something:
--
-- declare gInvoiceCursor cursor forward_only read_only for
--
-- bla bla bla
--
-- if (@brokenRecords > 0)
-- begin
-- RAISERROR(@message,16,1);
-- end
--
-- All good!
-- Bonne chance mon ami!
select @success = 1
select @errorMessage = ''
end try
begin catch
--if something happens let's be notified
if @@trancount > 0
begin
rollback tran;
end
declare @errmsg nvarchar(max)
set @errmsg =
(select 'ErrorNumber: ' + cast(error_number() as nvarchar(50))+
'ErrorSeverity: ' + cast(error_severity() as nvarchar(50))+
'ErrorState: ' + cast(error_state() as nvarchar(50))+
'ErrorProcedure: ' + cast(error_procedure() as nvarchar(50))+
'ErrorLine: ' + cast(error_number() as nvarchar(50))+
'error_message: ' + cast(error_message() as nvarchar(4000))
)
--save it if needed
print @errmsg
select @success = 0
select @errorMessage = @message
return;
end catch;
--at this point we can commit everything
if @@trancount > 0
begin
commit tran;
end
end
go
以下是如何从实体框架(WebAPI内部)调用存储过程:
declare @p3 dbo.GuidList
insert into @p3 values('f811b88a-bfad-49d9-b9b9-6a1d1a01c1e5')
exec sp_executesql N'exec GenerateInvoice @listIds, @CreatedBy, @success',N'@listIds [dbo].[GuidList] READONLY,@CreatedBy uniqueidentifier',@listIds=@p3,@CreatedBy='FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF'
我希望它有所帮助!
答案 2 :(得分:8)
我想在这个问题上分享我的解决方案:
我有几个表值参数的存储过程,我发现如果你这样称呼它:
var query = dbContext.ExecuteStoreQuery<T>(@"
EXECUTE [dbo].[StoredProcedure] @SomeParameter, @TableValueParameter1, @TableValueParameter2", spParameters[0], spParameters[1], spParameters[2]);
var list = query.ToList();
你得到一个没有记录的清单。
但是我玩了更多,这句话给了我一个想法:
var query = dbContext.ExecuteStoreQuery<T>(@"
EXECUTE [dbo].[StoredProcedure] 'SomeParameterValue', @TableValueParameter1, @TableValueParameter2", spParameters[1], spParameters[2]);
var list = query.ToList();
我在参数文本中更改了参数 @SomeParameter 及其实际值'SomeParameterValue'。 它工作:) 这意味着如果我们的参数中除了 SqlDbType.Structured 之外还有其他东西,它们并没有正确地传递它们,我们什么也得不到。 我们需要用它们的值替换实际参数。
所以,我的解决方案如下:
public static List<T> ExecuteStoredProcedure<T>(this ObjectContext dbContext, string storedProcedureName, params SqlParameter[] parameters)
{
var spSignature = new StringBuilder();
object[] spParameters;
bool hasTableVariables = parameters.Any(p => p.SqlDbType == SqlDbType.Structured);
spSignature.AppendFormat("EXECUTE {0}", storedProcedureName);
var length = parameters.Count() - 1;
if (hasTableVariables)
{
var tableValueParameters = new List<SqlParameter>();
for (int i = 0; i < parameters.Count(); i++)
{
switch (parameters[i].SqlDbType)
{
case SqlDbType.Structured:
spSignature.AppendFormat(" @{0}", parameters[i].ParameterName);
tableValueParameters.Add(parameters[i]);
break;
case SqlDbType.VarChar:
case SqlDbType.Char:
case SqlDbType.Text:
case SqlDbType.NVarChar:
case SqlDbType.NChar:
case SqlDbType.NText:
case SqlDbType.Xml:
case SqlDbType.UniqueIdentifier:
case SqlDbType.Time:
case SqlDbType.Date:
case SqlDbType.DateTime:
case SqlDbType.DateTime2:
case SqlDbType.DateTimeOffset:
case SqlDbType.SmallDateTime:
// TODO: some magic here to avoid SQL injections
spSignature.AppendFormat(" '{0}'", parameters[i].Value.ToString());
break;
default:
spSignature.AppendFormat(" {0}", parameters[i].Value.ToString());
break;
}
if (i != length) spSignature.Append(",");
}
spParameters = tableValueParameters.Cast<object>().ToArray();
}
else
{
for (int i = 0; i < parameters.Count(); i++)
{
spSignature.AppendFormat(" @{0}", parameters[i].ParameterName);
if (i != length) spSignature.Append(",");
}
spParameters = parameters.Cast<object>().ToArray();
}
var query = dbContext.ExecuteStoreQuery<T>(spSignature.ToString(), spParameters);
var list = query.ToList();
return list;
}
代码肯定可以更优化,但我希望这会有所帮助。
答案 3 :(得分:2)
DataTable方法是唯一的方法,但构建DataTable并手动填充它是非常困难的。我想直接从我的IEnumerable中定义我的DataTable,其风格类似于EF的流畅模型构建器。所以:
var whatever = new[]
{
new
{
Id = 1,
Name = "Bacon",
Foo = false
},
new
{
Id = 2,
Name = "Sausage",
Foo = false
},
new
{
Id = 3,
Name = "Egg",
Foo = false
},
};
//use the ToDataTable extension method to populate an ado.net DataTable
//from your IEnumerable<T> using the property definitions.
//Note that if you want to pass the datatable to a Table-Valued-Parameter,
//The order of the column definitions is significant.
var dataTable = whatever.ToDataTable(
whatever.Property(r=>r.Id).AsPrimaryKey().Named("item_id"),
whatever.Property(r=>r.Name).AsOptional().Named("item_name"),
whatever.Property(r=>r.Foo).Ignore()
);
我已经在dontnetfiddle上发布了这个东西:https://dotnetfiddle.net/ZdpYM3(请注意,你不能在那里运行它,因为并非所有的程序集都被加载到小提琴中)
答案 4 :(得分:1)
var sqlp = new SqlParameter("@param3", my function to get datatable);
sqlp.SqlDbType = System.Data.SqlDbType.Structured;
sqlp.TypeName = "dbo.mytypename";
var v = entitycontext.Database.SqlQuery<bool?>("exec [MyStorProc] @param1,@param2,@param3,@param4", new SqlParameter[]
{
new SqlParameter("@param1",value here),
new SqlParameter("@param2",value here),
sqlp,
new SqlParameter("@param4",value here)
}).FirstOrDefault();
答案 5 :(得分:0)
更改字符串连接代码以生成类似:
EXEC someStoredProcedureName @p0,@p1,@p2,@p3,@p4,@p5,@p6,@p7