我希望能够使用实体框架构建参数化的临时SQL查询,该实体框架使用表值参数。
注意:引起我兴趣的用例是在给定ID列表的情况下查询多个实体。我希望查询规划器能够在可能的情况下缓存计划,但我不一定要创建存储过程。
假设我有一些ID:
context.MyEntities
.Where(e => ids.Contains(e.Id))
如果我写一个像
这样的EF查询SELECT
[Extent1].[Name] AS [Name]
FROM [MyEntities] AS [Extent1]
WHERE [Extent1].[Id] IN (0, 42, -1)
生成的sql 不参数化,如下所示:
SELECT
[Extent1].[Name] AS [Name]
FROM [MyEntities] AS [Extent1]
WHERE EXISTS (SELECT
1
FROM @ids AS [Extent2]
WHERE [Extent2].[Id] = [Extent1].[Id]
)
我想要的是
SqlParameter
是完全参数化的。
可以在EF即席查询中完成吗?
我知道 可以将表值参数传递给使用EF的直接查询(例如,存储过程),使用SqlDbType.Structured
和DataTable
并且IQueryable
作为其值(请参阅https://stackoverflow.com/a/10409710/5181199)。当我尝试使用相同的技巧来创建ids
The SqlParameter is already contained by another SqlParameterCollection
版本时,我惊讶地发现生成的SQL实际上枚举了值,因此它看起来像第一个(不需要的)SQL示例我给!当我尝试执行查询时,它也会抱怨IEnumerable
。
一种简单易用的方法是按以下方式将IQueryable
ID转换为joined
:
MyStringSplit
public class IntId { public int Id { get; set; } }
)((IObjectContextAdapter)context).ObjectContext.CreateQuery<IntId>("MyStringSplit(@joined)", new ObjectParameter("joined", joined))
IQueryable
创建SELECT
[Extent1].[Name] AS [Name]
FROM [MyEntities] AS [Extent1]
WHERE [Extent1].[Id] IN (SELECT
1
FROM [MyStringSplit](@joined) AS [Extent2]
WHERE [Extent2].[Id] = [Extent1].[Id]
)
我的ID。这会产生类似
的东西IEnumerable
接近我之后的情况,但是很麻烦,肯定不能提供实际表值参数的性能优势。
编辑:为了澄清,我想到的是一些很好的c#-side抽象,我可以使用它来转换&#39;我的IQueryable
集合到{{1}}表示(对于特定上下文),当被EF使用时,它们被解释为表值参数。我们可以假设已经在SQL端定义了必要的表类型(例如,整数ID的表类型,字符串ID的表类型......)
答案 0 :(得分:0)
我们有一个类似的问题,查询存储中已经充满了这类动态查询(基于变量“ IN”子句)。因此,我们将其从EF查询更改为内联SQL,以使其成为参数化查询,因此使用单个查询计划。
IEnumerable<int> ids = new [] {0, 42, -1};
var strIds = string.Join(",", ids);
var names = context.Database.SqlQuery<string>(@"SELECT [NAME]
FROM [MyEntities] as e
join STRING_SPLIT(@ids, ',') as i on e.Id = o.value",
new SqlParameter("@ids", strIds));
注意:根据https://docs.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql?view=sql-server-ver15,STRING_SPLIT的兼容级别为130或更高