Query Extensions
LinqToDB contains different mechanisms to extend and customize generated SQL. Query Extensions are designed to extend SQL on clause and statement level such as table, join, query hints, etc.
Common Hint Extensions
The QueryExtensionScope
enumeration defines a scope where this extension is applied to.
Value | Method | Applied to | Description |
---|---|---|---|
None | Path through methods | IQueryable |
This type of extension should not generate SQL and can be used to implement path through methods such as AsSqlServer() that converts IQueryable sequence to ISqlServerSpecificQueryable . |
TableHint | TableHint , With |
ITable |
Generates table hints. |
TablesInScopeHint | TablesInScopeHint |
IQueryable |
This method applies provided hint to all the tables in scope of this method. It is supported by the same database providers as TableHint. |
IndexHint | IndexHint |
ITable |
MySql supports both hint styles: Oracle Optimizer Hints and SqlServer Table Hints. The TableHint extension generates Oracle hints, whereas this extension supports SqlServer hint style. |
JoinHint | JoinHint |
IQueryable |
Generates join hints. |
SubQueryHint | SubQueryHint |
IQueryable |
Generates subquery or statement hints. Supported by PostgreSQL. |
QueryHint | QueryHint |
IQueryable |
Generates statement hints. |
TableID method and Sql.SqlID class
Some hints require references to table specifications or aliases. LinqToDB automatically generates table and subquery aliases, so the idea to use generated names for hints is definitely not the best. The TableID
method assigns provided identifier to a table and this ID can be used later to generate references to the table. The following methods can be used as hint parameters to generate table references:
Method | Description |
---|---|
Sql.TableAlias("id") |
Generates table alias. |
Sql.TableName("id") |
Generates table name. |
Sql.TableSpec("id") |
Generates table specification. May include query block name. |
Naming Query Blocks
Oracle and MySql table-level, index-level, and subquery optimizer hints permit specific query blocks to be named as part of their argument syntax. To create these names, use the following methods:
AsSubQuery("qb_name")
QueryName("qb_name")
Examples
Access
var q =
(
from p in db.Parent
select p
)
.QueryHint(AccessHints.Query.WithOwnerAccessOption);
SELECT
[p].[ParentID],
[p].[Value1]
FROM
[Parent] [p]
WITH OWNERACCESS OPTION
MySql
var q =
(
from p in
(
from p in db.Parent.TableID("Pr")
.TableHint(MySqlHints.Table.NoBka)
.TableHint(MySqlHints.Table.Index, "PK_Parent")
from c in db.Child.TableID("Ch")
.IndexHint(MySqlHints.Table.UseKeyForOrderBy, "IX_ChildIndex", "IX_ChildIndex2")
select p
)
.AsSubQuery("qq")
select p
)
.QueryHint(MySqlHints.Query.NoBka, Sql.TableSpec("Pr"), Sql.TableSpec("Ch"))
.QueryHint(MySqlHints.Query.SetVar, "sort_buffer_size=16M");
SELECT /*+ NO_BKA(p@qq) INDEX(p@qq PK_Parent) NO_BKA(p@qq, c_1@qq) SET_VAR(sort_buffer_size=16M) */
`p_1`.`ParentID`,
`p_1`.`Value1`
FROM
(
SELECT /*+ QB_NAME(qq) */
`p`.`ParentID`,
`p`.`Value1`
FROM
`Parent` `p`,
`Child` `c_1` USE KEY FOR ORDER BY(IX_ChildIndex, IX_ChildIndex2)
) `p_1`
Oracle
var q =
(
from p in
(
from c in db.Child
.TableHint(OracleHints.Hint.Full)
.TableHint(OracleHints.Hint.Parallel, "DEFAULT")
join p in db.Parent
.TableHint(OracleHints.Hint.DynamicSampling, 1)
.TableHint(OracleHints.Hint.Index, "parent_ix")
.AsSubQuery("Parent")
on c.ParentID equals p.ParentID
select p
)
.AsSubQuery()
select p
)
.QueryHint(OracleHints.Hint.NoUnnest, "@Parent");
SELECT /*+ FULL(p_1.c_1) PARALLEL(p_1.c_1 DEFAULT) DYNAMIC_SAMPLING(t1@Parent 1) INDEX(t1@Parent parent_ix) NO_UNNEST(@Parent) */
p_1."ParentID",
p_1."Value1"
FROM
(
SELECT
p."ParentID",
p."Value1"
FROM
"Child" c_1
INNER JOIN (
SELECT /*+ QB_NAME(Parent) */
t1."ParentID",
t1."Value1"
FROM
"Parent" t1
) p ON c_1."ParentID" = p."ParentID"
) p_1
PostgreSQL
var q =
(
from p in
(
from p in
(
from p in db.Parent
from c in db.Child
where c.ParentID == p.ParentID
select p
)
.SubQueryHint(PostgreSQLHints.ForUpdate)
.AsSubQuery()
where p.ParentID < -100
select p
)
.SubQueryHint(PostgreSQLHints.ForShare)
select p
)
.SubQueryHint(PostgreSQLHints.ForKeyShare + " " + PostgreSQLHints.SkipLocked);
SELECT
p_1."ParentID",
p_1."Value1"
FROM
(
SELECT
p."ParentID",
p."Value1"
FROM
"Parent" p,
"Child" c_1
WHERE
c_1."ParentID" = p."ParentID"
FOR UPDATE
) p_1
WHERE
p_1."ParentID" < -100
FOR SHARE
FOR KEY SHARE SKIP LOCKED
SqlCe
from p in db.Person
.TableHint(SqlCeHints.Table.Index, "PK_Person")
.With(SqlCeHints.Table.NoLock)
select p;
SELECT
[p].[FirstName],
[p].[PersonID],
[p].[LastName],
[p].[MiddleName],
[p].[Gender]
FROM
[Person] [p] WITH (Index(PK_Person), NoLock)
SQLite
from p in db.Person.TableHint(SQLiteHints.Hint.IndexedBy("IX_PersonDesc"))
where p.ID > 0
select p;
SELECT
[p].[FirstName],
[p].[PersonID],
[p].[LastName],
[p].[MiddleName],
[p].[Gender]
FROM
[Person] [p] INDEXED BY IX_PersonDesc
WHERE
[p].[PersonID] > 0
SqlServer
var q =
(
from c in db.Child
.TableHint(SqlServerHints.Table.SpatialWindowMaxCells(10))
.IndexHint(SqlServerHints.Table.Index, "IX_ChildIndex")
join p in
(
from t in db.Parent.With(SqlServerHints.Table.NoLock)
where t.Children.Any()
select new { t.ParentID, t.Children.Count }
)
.JoinHint(SqlServerHints.Join.Hash) on c.ParentID equals p.ParentID
select p
)
.QueryHint(SqlServerHints.Query.Recompile)
.QueryHint(SqlServerHints.Query.Fast(10))
.QueryHint(SqlServerHints.Query.MaxGrantPercent(25));
SELECT
[p].[ParentID],
[p].[Count_1]
FROM
[Child] [c_1] WITH (SPATIAL_WINDOW_MAX_CELLS=10, Index(IX_ChildIndex))
INNER HASH JOIN (
SELECT
[t].[ParentID],
(
SELECT
Count(*)
FROM
[Child] [t1]
WHERE
[t].[ParentID] = [t1].[ParentID]
) as [Count_1]
FROM
[Parent] [t] WITH (NoLock)
WHERE
EXISTS(
SELECT
*
FROM
[Child] [t2]
WHERE
[t].[ParentID] = [t2].[ParentID]
)
) [p] ON [c_1].[ParentID] = [p].[ParentID]
OPTION (RECOMPILE, FAST 10, MAX_GRANT_PERCENT=25)
Database Specific Hint Extensions
The extension methods above are common and can be used to generate SQL for all database providers. You will be responsible for generated SQL as LinqToDB will generate SQL based on what you pass as parameters. Besides, LinqToDB implements database specific hint extensions. These extensions are designed specially for specific providers in the type-safe way and are “provider friendly” (which means you can use different specific database hints applied for the same LINQ query and they will not conflict).
C#
var q =
(
from p in db.Parent.TableID("pr")
.AsMySql()
.NoBatchedKeyAccessHint()
.IndexHint("PK_Parent")
from c in db.Child.TableID("ch")
.AsMySql()
.UseIndexHint("IX_ChildIndex")
.AsOracle()
.FullHint()
.HashHint()
.AsSqlCe()
.WithNoLock()
.AsSQLite()
.NotIndexedHint()
.AsSqlServer()
.WithNoLock()
.WithNoWait()
join t in db.Patient.TableID("pt")
.AsSqlServer()
.JoinLoopHint()
on c.ParentID equals t.PersonID
select t
)
.QueryName("qb")
.AsAccess()
.WithOwnerAccessOption()
.AsMySql()
.MaxExecutionTimeHint(1000)
.BatchedKeyAccessHint(Sql.TableSpec("ch"))
.AsOracle()
.ParallelHint(2)
.NoUnnestHint("qb")
.AsPostgreSQL()
.ForShareHint(Sql.TableAlias("pt"))
.AsSqlServer()
.WithReadUncommittedInScope()
.OptionRecompile()
.OptionTableHint(Sql.TableAlias("pr"), SqlServerHints.Table.ReadUncommitted)
.OptionNoPerformanceSpool()
;
SQL
Access
SELECT
[t].[PersonID],
[t].[Diagnosis]
FROM
(
SELECT
[c_1].[ParentID]
FROM
[Parent] [p],
[Child] [c_1]
) [t1]
INNER JOIN [Patient] [t] ON ([t1].[ParentID] = [t].[PersonID])
WITH OWNERACCESS OPTION
MySql
SELECT /*+ QB_NAME(qb) NO_BKA(t1.p@qb) INDEX(t1.p@qb PK_Parent) MAX_EXECUTION_TIME(1000) BKA(t1.c_1@qb) */
`t`.`PersonID`,
`t`.`Diagnosis`
FROM
(
SELECT
`c_1`.`ParentID`
FROM
`Parent` `p`,
`Child` `c_1` USE INDEX(IX_ChildIndex)
) `t1`
INNER JOIN `Patient` `t` ON `t1`.`ParentID` = `t`.`PersonID`
Oracle
SELECT /*+ QB_NAME(qb) FULL(t1.c_1@qb) HASH(t1.c_1@qb) PARALLEL(2) NO_UNNEST(qb) */
t."PersonID",
t."Diagnosis"
FROM
(
SELECT
c_1."ParentID"
FROM
"Parent" p,
"Child" c_1
) t1
INNER JOIN "Patient" t ON t1."ParentID" = t."PersonID"
PostgreSQL
SELECT /* qb */
t."PersonID",
t."Diagnosis"
FROM
(
SELECT
c_1."ParentID"
FROM
"Parent" p,
"Child" c_1
) t1
INNER JOIN "Patient" t ON t1."ParentID" = t."PersonID"
FOR SHARE OF t
SqlCe
SELECT /* qb */
[t].[PersonID],
[t].[Diagnosis]
FROM
(
SELECT
[c_1].[ParentID]
FROM
[Parent] [p],
[Child] [c_1] WITH (NoLock)
) [t1]
INNER JOIN [Patient] [t] ON [t1].[ParentID] = [t].[PersonID]
SQLite
SELECT /* qb */
[t].[PersonID],
[t].[Diagnosis]
FROM
(
SELECT
[c_1].[ParentID]
FROM
[Parent] [p],
[Child] [c_1] NOT INDEXED
) [t1]
INNER JOIN [Patient] [t] ON [t1].[ParentID] = [t].[PersonID]
SqlServer 2005
SELECT /* qb */
[t].[PersonID],
[t].[Diagnosis]
FROM
(
SELECT
[c_1].[ParentID]
FROM
[Parent] [p] WITH (ReadUncommitted),
[Child] [c_1] WITH (NoLock, NoWait, ReadUncommitted)
) [t1]
INNER LOOP JOIN [Patient] [t] WITH (ReadUncommitted) ON [t1].[ParentID] = [t].[PersonID]
OPTION (RECOMPILE)
SqlServer 2008
SELECT /* qb */
[t].[PersonID],
[t].[Diagnosis]
FROM
(
SELECT
[c_1].[ParentID]
FROM
[Parent] [p] WITH (ReadUncommitted),
[Child] [c_1] WITH (NoLock, NoWait, ReadUncommitted)
) [t1]
INNER LOOP JOIN [Patient] [t] WITH (ReadUncommitted) ON [t1].[ParentID] = [t].[PersonID]
OPTION (RECOMPILE, TABLE HINT(p, ReadUncommitted))
SqlServer 2019
SELECT /* qb */
[t].[PersonID],
[t].[Diagnosis]
FROM
(
SELECT
[c_1].[ParentID]
FROM
[Parent] [p] WITH (ReadUncommitted),
[Child] [c_1] WITH (NoLock, NoWait, ReadUncommitted)
) [t1]
INNER LOOP JOIN [Patient] [t] WITH (ReadUncommitted) ON [t1].[ParentID] = [t].[PersonID]
OPTION (RECOMPILE, TABLE HINT(p, ReadUncommitted), NO_PERFORMANCE_SPOOL)