是否可以将 DataTable 传递给实体框架中的即席 SQL 查询?

Oly*_*Oly 7 c# sql sql-server entity-framework parameterized-query

我希望能够使用使用表值参数的实体框架构建参数化的临时 SQL 查询。

注意:引起我兴趣的用例是在给定 ID 列表的情况下查询多个实体。我希望查询计划程序能够在可能的情况下缓存计划,但我不一定要创建存储过程。

假设我有一些 id:

IEnumerable<int> ids = new [] {0, 42, -1};
Run Code Online (Sandbox Code Playgroud)

如果我编写一个 EF 查询,例如

context.MyEntities
    .Where(e => ids.Contains(e.Id))
Run Code Online (Sandbox Code Playgroud)

生成的 sql参数化,如下所示:

SELECT
    [Extent1].[Name] AS [Name]
    FROM [MyEntities] AS [Extent1]
    WHERE [Extent1].[Id] IN (0, 42, -1)
Run Code Online (Sandbox Code Playgroud)

我想要得到的是类似的东西

SELECT
    [Extent1].[Name] AS [Name]
    FROM [MyEntities] AS [Extent1]
    WHERE EXISTS (SELECT
        1
        FROM @ids AS [Extent2]
        WHERE [Extent2].[Id] = [Extent1].[Id]
    )
Run Code Online (Sandbox Code Playgroud)

这是完全参数化的。

这可以在 EF 即席查询中完成吗?

我知道可以使用EF 传递表值参数来直接查询(例如存储过程),使用 withSqlParameterSqlDbType.StructuredaDataTable作为其值(请参阅/sf/answers/728679731/) 。当我尝试使用相同的技巧来创建IQueryablemy 的版本时ids,我惊讶地发现生成的 SQL 实际上枚举了值,因此它看起来像我给出的第一个(不需要的)SQL 示例!The SqlParameter is already contained by another SqlParameterCollection当我尝试执行查询时它也会抱怨。


一种可行的方法是将IEnumerableID 转换为IQueryable以下方式:

  • 将值连接到单个分隔字符串中joined
  • 在数据库端创建某种带有表值输出的字符串分割和解析函数(MyStringSplit
  • 为上述函数的输出结构创建一个 EF“复杂类型”,例如public class IntId { public int Id { get; set; } }
  • 用于((IObjectContextAdapter)context).ObjectContext.CreateQuery<IntId>("MyStringSplit(@joined)", new ObjectParameter("joined", joined))创建IQueryable我的 ID。

这会产生类似的东西

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]
    )
Run Code Online (Sandbox Code Playgroud)

这与我所追求的很接近,但是很混乱,并且肯定不能提供实际表值参数的性能优势。

编辑:为了澄清,我想到的是某种很好的 c# 端抽象,我可以用它来将我的IEnumerable集合“转换”为IQueryable表示形式(对于特定上下文),当 EF 使用时,这些表示形式会被解释为表值参数。我们可以假设必要的表类型已经在 SQL 端定义(例如整数 ID 的表类型、字符串 ID 的表类型...)

Mic*_*ker 2

我们遇到了类似的问题,查询存储被这些类型的动态查询(基于变量“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));
Run Code Online (Sandbox Code Playgroud)

注意:STRING_SPLIT 可在兼容性级别 130 或更高版本上使用,参见https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql?view=sql-server-版本15