entity framework存储过程表值参数

我试图调用一个接受表值参数的存储过程。 我知道,这不是直接支持在entity framework,但从我的理解,你可以使用ExecuteStoreQuery命令closures的ObjectContext 。 我有一个通用的entity framework库,我有以下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); } 

命令string如下所示:

 EXEC someStoredProcedureName {0},{1},{2},{3},{4},{5},{6},{7} 

我试图在接受表值参数的存储过程上运行此方法,并打破。 我在这里读到,需要是SqlParametertypes的参数和表值参数需要将SqlDbType设置为Structured 。 所以我这样做,我得到一个错误,指出:

 The table type parameter p6 must have a valid type name 

所以,我将SqlParameter.TypeName设置为在数据库上创build的用户定义types的名称,然后在运行查询时,我得到以下真正有用的错误:

 Incorrect syntax near '0'. 

我可以让查询运行,如果我恢复到ADO.NET和执行数据读取器,但我希望得到它使用数据上下文的工作。

有没有办法使用ExecuteStoreQuery传递表值参数? 此外,我实际上使用entity frameworkCode First,并将DbContextObjectContext以获取ExecuteStoreQuery方法。 这是必要的,或者我可以这样做对DbContext以及?

UPDATE

我已经添加了对Nuget包的支持 – https://github.com/Fodsuk/EntityFrameworkExtras#nuget(EF4,EF5,EF6

查看代码示例的GitHub存储库。


有一点问题,但对于试图将用户定义的表传递给存储过程的人来说,没有什么用处。 玩了尼克的例子和其他Stackoverflow的post后,我想出了这个:

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

我发现的约束包括:

  1. 传入ExecuteStoreCommand的参数必须按照存储过程中的参数顺序排列
  2. 您必须将每一列传递给用户定义的表格,即使它们有默认值。 所以看来我不能在我的UDT上有一个IDENTITY(1,1)NOT NULL列

我想在这个问题上分享我的解决scheme:

我有存储过程与几个表值参数,我发现,如果你这样调用它:

 var query = dbContext.ExecuteStoreQuery<T>(@" EXECUTE [dbo].[StoredProcedure] @SomeParameter, @TableValueParameter1, @TableValueParameter2", spParameters[0], spParameters[1], spParameters[2]); var list = query.ToList(); 

你会得到一个没有logging的列表。

但是我玩了更多,这条线给了我一个想法:

 var query = dbContext.ExecuteStoreQuery<T>(@" EXECUTE [dbo].[StoredProcedure] 'SomeParameterValue', @TableValueParameter1, @TableValueParameter2", spParameters[1], spParameters[2]); var list = query.ToList(); 

我用命令文本中的实际值“SomeParameterValue”更改了我的参数@SomeParameter 。 它的工作:)这意味着,如果我们有其他东西比SqlDbType.Structured在我们的参数,它并没有正确地通过它们,我们什么也得不到。 我们需要用实际参数replace它们的值。

所以,我的解决scheme如下所示:

 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; } 

代码当然可以更优化,但我希望这会有所帮助。

DataTable方法是唯一的方法,但是构build一个DataTable并手动填充它是非常困难的。 我想直接从我的IEnumerable中定义我的DataTable,风格类似于EF的stream利模型生成器thingy。 所以:

 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(注意,你不能在那里运行它,因为不是所有的程序集都加载到小提琴)

改变你的string连接代码来产生类似于:

 EXEC someStoredProcedureName @p0,@p1,@p2,@p3,@p4,@p5,@p6,@p7