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(例如;- IEnumerable)。我们需要输出为 IQueryable。 试试这个信息:c-sharpcorner.com/uploadfile/78607b/…. @MikeJ,不相关。 Golda 正在询问表值函数的情况。 我认为简单的事实是ObjectParameter永远不可能成为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# 将表值参数传递给用户定义的函数的主要内容,如果未能解决你的问题,请参考以下文章

将表变量作为参数传递给表值函数

如何从.net代码将表值参数传递给存储过程

将表值参数传递给存储过程

使用 PetaPoco 将表值参数传递给存储过程

将表值参数传递给具有不同字段数的存储过程

SQL Server存储过程中使用表值作为输入参数示例