EF:从 C# 将表值参数传递给用户定义的函数
Posted
技术标签:
【中文标题】EF:从 C# 将表值参数传递给用户定义的函数【英文标题】:EF: Passing a table valued parameter to a user-defined function from C# 【发布时间】:2020-12-23 19:54:34 【问题描述】:我在 SQL Server 中有一个用户定义的函数,它接受 TVP(表值参数)作为参数。在 EF 中,如何从 C# 调用这样的函数?
我尝试使用ObjectContext.CreateQuery<>
的方法,但出现以下错误:
函数“QueryByParam”的参数“param”无效。参数只能是可以转换为 Edm 标量类型的类型。
还尝试了方法ObjectContext.ExecuteStoreQuery<>
并得到了同样的错误。无论如何它都不会返回IQueryable
。
示例代码
[DbFunction(nameof(SampleDbContext), "QueryByParam")]
public IQueryable<SecurityQueryRow> QueryByParam(IEnumerable<ProfileType> profiles, bool isActive = false)
DataTable dataTable = ....
ObjectParameter profilesParam = new ObjectParameter("profileTypeIds", dataTable);
ObjectParameter isActiveParam = new ObjectParameter("isActive ", isActive);
return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<SecurityQueryRow>(
string.Format("[0].[1](@profileTypeIds, @isActive)", GetType().Name, "QueryByParam"),
profilesParam,
isActiveParam);
要求是我们需要一个 IQueryable 返回,而不是消费的结果。
【问题讨论】:
这些信息可能会有所帮助entityframeworkcore.com/knowledge-base/57056492/… 在正常情况下,我们会以 IQueryable 的形式返回。但是当输入参数为 Tvp 时它不起作用。对我们来说,输入参数是一个 tvp(例如;- IEnumerableObjectParameter
永远不可能成为TVP。
【参考方案1】:
您可以使用 Raw Sql en EF Core,EF6 中的类似方法,但您无法获得 IQueryable。以下两个示例。
实体框架核心
将其用作列表过滤器的 SQL 类型:
CREATE TYPE [dbo].[Table1Type] AS TABLE(
[Id] [int] NULL,
[Name] [nchar](10) NULL
)
SQL UDF:
CREATE FUNCTION [dbo].[Func1]
(
@Ids Table1Type readonly
)
RETURNS TABLE
AS
RETURN
(
SELECT * from Table1 where id in (select Id from @Ids)
)
EF 上下文:
public class MyContext : DbContext
public DbSet<Table1> Table1 get; set;
DTO 匹配 sql 类型(为简单起见也与表相同):
public class Table1
public int Id get; set;
public string Name get; set;
示例:
static void Main(string[] args)
using (var context = new MyContext())
// Declare de Structure filter param
var dt = new DataTable();
DataTable table = new DataTable();
table.Columns.Add(new DataColumn("Id", typeof(int)));
table.Columns.Add(new DataColumn("Name", typeof(string)));
DataRow row = table.NewRow();
row["Id"] = 1;
row["Name"] = "Item";
table.Rows.Add(row);
var param = new SqlParameter("@Ids", table) TypeName = "dbo.Table1Type", SqlDbType = SqlDbType.Structured ;
IQueryable<Table1> query = context.Table1.FromSqlRaw("SELECT * FROM dbo.func1(@Ids)", param);
var result = query.ToList();
实体框架 6
您无法获得 IQueryable,但您可以链接到生成的 IEnumerable。
static void Main(string[] args)
using (var context = new MyContext())
context.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
// Declare de Structure filter param
var dt = new DataTable();
DataTable table = new DataTable();
table.Columns.Add(new DataColumn("Id", typeof(int)));
table.Columns.Add(new DataColumn("Name", typeof(string)));
DataRow row = table.NewRow();
row["Id"] = 1;
row["Name"] = "Item";
table.Rows.Add(row);
var param = new SqlParameter("@Ids", table) TypeName = "dbo.Table1Type", SqlDbType = SqlDbType.Structured ;
var query = context.Table1.SqlQuery("SELECT * FROM dbo.func1(@Ids)", param);
var result = query.ToList();
【讨论】:
@golda,这就是你要找的东西吗?如果这个答案对您没有帮助,也许您可以澄清一下。 这适用于 EF Core,而错误消息和 OP 代码的结构指示 EF6。 @IvanStoev,我在 EF6 中编辑了类似答案的答案,但是,由于无法从 SqlQuery 获取 IQueryable,因此并不完全符合需要。 谢谢@nerlijma。很高兴知道它可以在 EFCore 中完成。 EF6 和 EF Core 是完全不同的系统。这个问题专门针对 EF6 并请求服务器端IQueryable
结果,这个答案不满足。如果不能满足要求,正确答案就是“不可能”。【参考方案2】:
AdventureWorks示例数据库有一个表值函数dbo.ufnGetContactInformation,它的返回类型也可以表示为另一种复杂类型
[ComplexType]
public class ContactInformation
public int PersonID get; set;
public string FirstName get; set;
public string LastName get; set;
public string JobTitle get; set;
public string BusinessEntityType get; set;
那么ufnGetContactInformation函数可以映射为:
// Defines table-valued function, which must return IQueryable<T>.
[Function(FunctionType.TableValuedFunction, nameof(ufnGetContactInformation), Schema = dbo)]
public IQueryable<ContactInformation> ufnGetContactInformation(
[Parameter(DbType = "int", Name = "PersonID")]int? personId)
ObjectParameter personIdParameter = personId.HasValue
? new ObjectParameter("PersonID", personId)
: new ObjectParameter("PersonID", typeof(int));
return this.ObjectContext().CreateQuery<ContactInformation>(
$"[nameof(this.ufnGetContactInformation)](@nameof(personId))", personIdParameter);
它的返回类型应该是 IQueryable,以便它可以在 LINQ to Entities 中组合。并且可以调用:
[TestMethod]
public void CallTableValuedFunction()
using (AdventureWorks database = new AdventureWorks())
IQueryable<ContactInformation> employees = database.ufnGetContactInformation(1).Take(2);
Assert.IsNotNull(employees.Single());
上述 ufnGetContactInformation 调用和 Take 调用将被转换为单个 SQL 查询:
exec sp_executesql N'SELECT TOP (2)
[top].[C1] AS [C1],
[top].[PersonID] AS [PersonID],
[top].[FirstName] AS [FirstName],
[top].[LastName] AS [LastName],
[top].[JobTitle] AS [JobTitle],
[top].[BusinessEntityType] AS [BusinessEntityType]
FROM ( SELECT TOP (2)
[Extent1].[PersonID] AS [PersonID],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName],
[Extent1].[JobTitle] AS [JobTitle],
[Extent1].[BusinessEntityType] AS [BusinessEntityType],
1 AS [C1]
FROM [dbo].[ufnGetContactInformation](@PersonID) AS [Extent1]
) AS [top]',N'@PersonID int',@PersonID=1
【讨论】:
TVP 在哪里? 请参考这里。 sqlshack.com/table-valued-parameters-in-sql-server 为什么?您的代码中没有表值 Parameter。 第一次尝试时我错过了这个问题。但我相信这个链接会有所帮助.. ***.com/questions/5595353/…以上是关于EF:从 C# 将表值参数传递给用户定义的函数的主要内容,如果未能解决你的问题,请参考以下文章