问题描述
我在sql Server中有一个用户定义的函数,该函数接受TVP(表值参数)作为参数。在EF中,如何从C#调用这样的函数?
我尝试使用方法ObjectContext.createquery<>
,但遇到以下错误:
也尝试了方法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返回,而不是消耗的结果。
解决方法
您可以使用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",SqlDbType = SqlDbType.Structured };
var query = context.Table1.SqlQuery("SELECT * FROM dbo.func1(@Ids)",param);
var result = query.ToList();
}
}
,
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