Skip to content

SqlExpression: extension methods, STRFTIME/mixed-literal templates, EFP0003 compile-time analyzer, StringSyntax#4

Draft
Copilot wants to merge 5 commits intomasterfrom
copilot/add-sql-expression-attribute
Draft

SqlExpression: extension methods, STRFTIME/mixed-literal templates, EFP0003 compile-time analyzer, StringSyntax#4
Copilot wants to merge 5 commits intomasterfrom
copilot/add-sql-expression-attribute

Conversation

Copy link

Copilot AI commented Mar 14, 2026

Extends [SqlExpression] with four improvements: extension method support verified with tests, correct translation of templates containing SQL literal fragments (e.g. STRFTIME('%Y', {0})), a compile-time Roslyn analyzer that catches out-of-range {N} argument indices, and [StringSyntax("sql")] for IDE awareness.

Template translator — mixed literal/placeholder args

Previously only FUNCNAME({0}, {1}) (pure placeholder args) was supported. A new SplitArguments helper splits argument lists while respecting single-quoted strings and nested parentheses; literal tokens become Fragment(...) expressions:

[SqlExpression("STRFTIME('%Y', {0})", Configuration = "Sqlite")]  // now works
[SqlExpression("YEAR({0})",           Configuration = "SqlServer")]
public static int Year(DateTime date) => throw new NotImplementedException();

Extension methods

[SqlExpression] works on extension methods — {0} maps to the this parameter exactly as it does for regular static parameters. Added SqlExtensionFunctions helper class and three new functional tests (SQL Server + SQLite) confirming both direct and extension-syntax call forms translate correctly.

EFP0003 Roslyn analyzer

SqlExpressionAnalyzer reports a compile-time error when any {N} index in a SQL template exceeds the method's parameter count:

[SqlExpression("COALESCE({0}, {1})")]
public static string Coalesce(string a) => ...; // EFP0003: references {1} but only 1 parameter

Covers multiple stacked attributes, extension methods, and methods with no parameters. Added 9 unit tests in SqlExpressionAnalyzerTests.

[StringSyntax("sql")]

Applied to the sql constructor parameter of SqlExpressionAttribute for IDE SQL-awareness and future tooling hooks.

Original prompt

Add EF Core Custom SQL Expression Support (linq2db-style)

This task is to implement the 3 first features that bring linq2db-style fine-grained SQL translation control to this EF Core library (EntityFrameworkCore.Projectables), which already supports projecting over properties and functions in LINQ queries.

The goal is to allow users to annotate C# methods with SQL expression templates (similar to linq2db's [Sql.Expression]) and have EF Core translate them correctly during query execution.


Task 1: [SqlExpression] Attribute

Create a custom attribute SqlExpressionAttribute that allows decorating static methods with a SQL template string.

Requirements:

  • Lives in the main library project
  • Accepts a SQL template string with positional placeholders {0}, {1}, etc.
  • Optionally accepts a ServerSideOnly boolean property (default: true) — if true, the method cannot be evaluated client-side and must throw NotImplementedException in its body
  • Example usage:
public static class MyFunctions
{
    [SqlExpression("SOUNDEX({0})")]
    public static string Soundex(string value) => throw new NotImplementedException();

    [SqlExpression("{0} LIKE {1} + '%'")]
    public static bool StartsWith(string field, string prefix) => throw new NotImplementedException();
}

Task 2: Detection and Translation via IMethodCallTranslator

Implement an IMethodCallTranslator (and the required IMethodCallTranslatorPlugin) that:

  • Detects when a method decorated with [SqlExpression] is used in a LINQ query
  • Reads the SQL template from the attribute
  • Uses EF Core's ISqlExpressionFactory to build the appropriate SqlExpression by substituting the positional arguments into the template using a SqlFragmentExpression or equivalent
  • Returns null for methods not decorated with [SqlExpression]

Registration:

  • Register the plugin via an extension method on DbContextOptionsBuilder, e.g.:
optionsBuilder.UseSqlExpressions();

Or, if it integrates into the existing Projectables infrastructure, hook it into the existing setup method.


Task 3: Integration Tests

Add integration/unit tests that verify end-to-end translation:

  • A test that uses a method decorated with [SqlExpression("UPPER({0})")] in a LINQ .Where() clause and verifies the generated SQL contains UPPER(...)
  • A test that uses a two-argument template like [SqlExpression("COALESCE({0}, {1})")] in a .Select() and verifies correct SQL output
  • Tests should use an in-memory or SQLite provider (whichever is already used in the test project)
  • Follow the existing test patterns/conventions in the repository

Notes

  • Follow the existing code style, namespaces, and project structure
  • Do not break any existing tests
  • The attribute and translator should be in the main library project
  • Tests should be in the existing test project

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: What has linq2db that ef core does not?

Assistant: Here's a breakdown of features that linq2db has which EF Core lacks or handles less well:


🚀 Features linq2db Has That EF Core Does Not

1. Native Bulk Operations

  • linq2db offers built-in Bulk Insert, Bulk Update, Bulk Copy, and Batch Delete/Update natively with great performance.
  • EF Core requires paid or third-party extensions (like EFCore.BulkExtensions) to achieve similar results.

2. Fine-Grained SQL Translation Control

  • You can define custom SQL templates and expressions to precisely control how LINQ queries are translated to SQL.
  • EF Core's query translation is a black box — you get limited control over the output SQL.

3. Schema-Free / DTO Mapping

  • Query any table or view and map results directly to arbitrary DTOs or non-entity classes without needing a DbSet<T> or modifying the context.
  • EF Core requires entities to be registered in the model to fully participate in queries.

4. No Tracking by Default

  • linq2db has zero change tracking overhead unless you explicitly opt in.
  • EF Core tracks entities by default — you must explicitly use .AsNoTracking().

5. Wider Database Provider Support

  • linq2db supports a broader range of RDBMS out-of-the-box, including Sybase, Informix, SAP HANA, and others — not just mainstream engines.

6. Table-Valued Parameters (TVPs)

  • linq2db has native, streamlined support for user-defined table types and table-valued parameters.
  • EF Core makes this cumbersome and requires custom workarounds.

7. Stored Procedure & Table Function Mapping

  • Mapping C# methods directly to stored procedures and table-valued functions is more straightforward in linq2db without needing model scaffolding or special attributes.

8. External / XML Mapping Schemas

  • Mapping configuration can live outside the C# code via **XML or external f...

This pull request was created from Copilot chat.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits March 14, 2026 21:56
Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
…e placeholders

Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
Copilot AI changed the title [WIP] Add SqlExpression attribute for SQL templates Add linq2db-style [SqlExpression] attribute for custom SQL translation in EF Core queries Mar 14, 2026
Copilot AI requested a review from PhenX March 14, 2026 22:02
Copilot stopped work on behalf of PhenX due to an error March 14, 2026 22:18
…n property

Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
Copilot AI changed the title Add linq2db-style [SqlExpression] attribute for custom SQL translation in EF Core queries Add provider-specific SQL expressions via Configuration on SqlExpressionAttribute Mar 14, 2026
…, and StringSyntax hint for SqlExpression

Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
Copilot AI changed the title Add provider-specific SQL expressions via Configuration on SqlExpressionAttribute SqlExpression: extension methods, STRFTIME/mixed-literal templates, EFP0003 compile-time analyzer, StringSyntax Mar 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants