如何在实体框架中指定索引提示?
Posted
技术标签:
【中文标题】如何在实体框架中指定索引提示?【英文标题】:How can I specify an index hint in Entity Framework? 【发布时间】:2011-12-23 06:47:40 【问题描述】:sql
select * from table1 with(index=IX_table1_1)
Linq to sql 使用 ado.net 实体想写上面的代码。我找不到实体,特别是索引提示的使用。
linq
var querysample = from a in db.table1
select a;
【问题讨论】:
你不能在 EF LINQ 中指定查询提示...你必须使用 ExecuteStoreQuery @Sam 是 ExecuteStoreQuery L2S 的 EF 版本ExecuteQuery<T>
?
是的......除了它慢了 2* @MarcGravell
我更改了标题 - 这是否准确地代表了问题?
@Sam 如果有人会编写一个工具来执行这些基于 SQL 的查询,而无需通常与 ORM 相关的开销!
【参考方案1】:
除了 wh1sp3r 的回答,请参见下面的另一个拦截器,它依赖于 EF query tags 而不是线程静态变量:
public class QueryHintInterceptor : DbCommandInterceptor
private static readonly Regex _tableAliasRegex = new Regex("( AS [^ ]+)",
RegexOptions.Multiline | RegexOptions.IgnoreCase);
private readonly string _hintPrefix;
public QueryHintInterceptor(string hintPrefix)
_hintPrefix = "-- " + hintPrefix;
public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command,
CommandEventData eventData, InterceptionResult<DbDataReader> result)
PatchCommandText(command);
return base.ReaderExecuting(command, eventData, result);
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
DbCommand command, CommandEventData eventData,
InterceptionResult<DbDataReader> result,
CancellationToken cancellationToken = default)
PatchCommandText(command);
return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
public override InterceptionResult<object> ScalarExecuting(DbCommand command,
CommandEventData eventData, InterceptionResult<object> result)
PatchCommandText(command);
return base.ScalarExecuting(command, eventData, result);
public override ValueTask<InterceptionResult<object>> ScalarExecutingAsync(
DbCommand command, CommandEventData eventData, InterceptionResult<object> result,
CancellationToken cancellationToken = default)
PatchCommandText(command);
return base.ScalarExecutingAsync(command, eventData, result, cancellationToken);
private void PatchCommandText(DbCommand command)
if (command.CommandText.StartsWith(_hintPrefix, StringComparison.Ordinal))
int index = command.CommandText.IndexOfAny(Environment.NewLine.ToCharArray(),
_hintPrefix.Length);
command.CommandText = _tableAliasRegex
.Replace(command.CommandText, "$0 WITH (" + command.CommandText
.Substring(_hintPrefix.Length, index - _hintPrefix.Length) + ")")
.Substring(index);
扩展方法:
public static class QueryHintsDbContextOptionsBuilderExtensions
private const string HintTag = "Use hint: ";
public static IQueryable<T> WithHint<T>(this IQueryable<T> source,
TableHint hint) =>
source.TagWith(HintTag + hint);
public static DbContextOptionsBuilder<TContext> AddQueryHints<TContext>(
this DbContextOptionsBuilder<TContext> builder)
where TContext : DbContext =>
builder.AddInterceptors(new QueryHintInterceptor(HintTag));
public enum TableHint
KeepIdentity,
KeepDefaults,
HoldLock,
Ignore_Constraints,
Ignore_Triggers,
Nolock,
NoWait,
PagLock,
ReadCommitted,
ReadCommittedLock,
ReadPast,
RepeatableRead,
RowLock,
Serializable,
Snapshot,
TabLock,
TabLockX,
UpdLock,
Xlock
示例用法:
await using var context = new TestDbContext(
new DbContextOptionsBuilder<TestDbContext>()
.UseSqlServer("<connection string>")
.AddQueryHints()
.LogTo(message => Console.WriteLine("EF: 0", message))
.Options);
var result = await context.SomeEntities
.WithHint(TableHint.TabLock)
.ToListAsync();
更新
上述自定义 SQL 生成的方式不会影响.ToQueryString()
,因为命令拦截器在实际 SQL 生成之后才起作用。它会导致测试和日志记录出现问题,例如在 ASP.Net 核心中。所以另一种解决方案是使用自定义QuerySqlGenerator
:
public static class TableHintsDbContextOptionsBuilderExtensions
public static IQueryable<T> WithHint<T>(this IQueryable<T> source,
TableHint hint) =>
source.TagWith(hint.ToString());
public static DbContextOptionsBuilder UseTableHints(
this DbContextOptionsBuilder builder) =>
builder.ReplaceService<IQuerySqlGeneratorFactory,
HintTagSqlServerQuerySqlGeneratorFactory>();
class HintTagSqlServerQuerySqlGeneratorFactory : IQuerySqlGeneratorFactory
private readonly QuerySqlGeneratorDependencies dependencies;
public HintTagSqlServerQuerySqlGeneratorFactory(
QuerySqlGeneratorDependencies dependencies)
this.dependencies = dependencies;
public QuerySqlGenerator Create()
return new HintTagSqlServerQuerySqlGenerator(dependencies);
class HintTagSqlServerQuerySqlGenerator : SqlServerQuerySqlGenerator
private readonly HashSet<TableHint> tableHints = new();
public HintTagSqlServerQuerySqlGenerator(
QuerySqlGeneratorDependencies dependencies) :
base(dependencies)
protected override void GenerateTagsHeaderComment(
SelectExpression selectExpression)
foreach (var tag in selectExpression.Tags)
if (Enum.TryParse(typeof(TableHint), tag, out var hint))
tableHints.Add((TableHint)hint!);
selectExpression.Tags.Remove(tag);
base.GenerateTagsHeaderComment(selectExpression);
protected override Expression VisitTable(
TableExpression tableExpression)
var result = base.VisitTable(tableExpression);
if (tableHints.Count > 0)
Sql.Append($" WITH (string.Join(", ", tableHints).ToUpperInvariant())");
return result;
但是,警告表明
Microsoft.EntityFrameworkCore.SqlServer.Query.Internal.SqlServerQuerySqlGenerator 是一个内部 API,它支持 Entity Framework Core 基础结构,并且不受与公共 API 相同的兼容性标准的约束。它可能会在任何版本中更改或删除,恕不另行通知。
【讨论】:
【参考方案2】:添加到 stop-crans 答案,这适用于(我在)EF Core 5.0 并支持WITH(INDEX(param)) 的参数。
public class QueryHintInterceptor : DbCommandInterceptor
private static readonly Regex _tableAliasRegex = new Regex(@"(FROM[\s\r\n]+\S+(?:[\s\r\n]+AS[\s\r\n]+[^\s\r\n]+)?)", RegexOptions.Multiline | RegexOptions.IgnoreCase);
private readonly string _hintPrefix;
public QueryHintInterceptor(string hintPrefix)
_hintPrefix = "-- " + hintPrefix;
public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
PatchCommandtext(command);
return base.ReaderExecuting(command, eventData, result);
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result, CancellationToken cancellationToken = default)
PatchCommandtext(command);
return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
public override InterceptionResult<object> ScalarExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<object> result)
PatchCommandtext(command);
return base.ScalarExecuting(command, eventData, result);
public override ValueTask<InterceptionResult<object>> ScalarExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<object> result, CancellationToken cancellationToken = default)
PatchCommandtext(command);
return base.ScalarExecutingAsync(command, eventData, result, cancellationToken);
private void PatchCommandtext(DbCommand command)
if (command.CommandText.StartsWith(_hintPrefix, StringComparison.Ordinal))
int index = command.CommandText.IndexOfAny(Environment.NewLine.ToCharArray(), _hintPrefix.Length);
command.CommandText = _tableAliasRegex
.Replace(command.CommandText, "$0 WITH (" + command.CommandText
.Substring(_hintPrefix.Length, index - _hintPrefix.Length) + ")")
.Substring(index);
public static class QueryHintsDbContextOptionsBuilderExtensions
private const string HintTag = "Use hint: ";
public static IQueryable<T> WithHint<T>(this IQueryable<T> source, TableHint hint) =>
source.TagWith(HintTag + hint);
public static IQueryable<T> WithHint<T>(this IQueryable<T> source, TableHint hint, string param) =>
source.TagWith(HintTag + hint + " (" + param+")");
public static DbContextOptionsBuilder AddQueryHints(this DbContextOptionsBuilder builder) =>
builder.AddInterceptors(new QueryHintInterceptor(HintTag));
public enum TableHint
Index,
KeepIdentity,
KeepDefaults,
HoldLock,
Ignore_Constraints,
Ignore_Triggers,
Nolock,
NoWait,
PagLock,
ReadCommitted,
ReadCommittedLock,
ReadPast,
RepeatableRead,
RowLock,
Serializable,
Snapshot,
TabLock,
TabLockX,
UpdLock,
Xlock
【讨论】:
【参考方案3】:解决方案很简单。 让我们添加一个拦截器!!!
public class HintInterceptor : DbCommandInterceptor
private static readonly Regex _tableAliasRegex = new Regex(@"(?<tableAlias>AS \[Extent\d+\](?! WITH \(*HINT*\)))", RegexOptions.Multiline | RegexOptions.IgnoreCase);
[ThreadStatic] public static string HintValue;
public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
if (!String.IsNullOrWhiteSpace(HintValue))
command.CommandText = _tableAliasRegex.Replace(command.CommandText, "$tableAlias WITH (*HINT*)");
command.CommandText = command.CommandText.Replace("*HINT*", HintValue);
HintValue = String.Empty;
public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
if (!String.IsNullOrWhiteSpace(HintValue))
command.CommandText = _tableAliasRegex.Replace(command.CommandText, "$tableAlias WITH (*HINT*)");
command.CommandText = command.CommandText.Replace("*HINT*", HintValue);
HintValue = String.Empty;
我知道,正则表达式可能会更好。 让我们在 Config 类中注册我们的拦截器
public class PbsContextConfig : DbConfiguration
public PbsContextConfig()
this.AddInterceptor(new HintInterceptor());
让我们为 DbSet 制作漂亮的提示扩展
public static class HintExtension
public static DbSet<T> WithHint<T>(this DbSet<T> set, string hint) where T : class
HintInterceptor.HintValue = hint;
return set;
如何使用?
context.Persons.WithHint("INDEX(XI_DOWNTIME_LOCK)").Where( x => x.ID == ....
欢迎修改!
【讨论】:
有趣的想法,但是当我尝试它时,我发现在我的查询之前执行了许多上下文设置查询,并且在第一个上下文设置查询中它会清除 HintValue 并且永远不会将其应用于我的查询。此外,由于它正在执行通过 ReaderExecuting 和 ScalarExecuting 传递的上下文设置查询,因此很有可能将“提示”应用于错误的查询。不知道如何更改代码以使其特定于我的查询,如果我找到方法,我会发布更新。【参考方案4】:L2S 和 EF 都不会像定制的那样提供对 SQL 的直接支持(索引提示等),尽管使用 L2S 您可以通过ExecuteQuery<T>(...)
实现它(它采用原始 TSQL)。如果您需要这种级别的控制,请考虑存储过程或替代 ORM。
这里特别的一个问题是查询提示非常特定于平台,但 EF 试图与平台无关。
【讨论】:
"如果您需要这种级别的控制,请考虑存储过程或替代 ORM" => 当然,但不实用。当你知道事实是这样的时候,已经太晚了以上是关于如何在实体框架中指定索引提示?的主要内容,如果未能解决你的问题,请参考以下文章
当我使用两个表实体时如何在 JPA @JoinColumn 中指定忽略大小写