Skip to content

Migrating from Projectables

ExpressiveSharp is the successor to EntityFrameworkCore.Projectables. It keeps the same core concept -- mark members with an attribute, get companion expression trees via source generation -- but is rebuilt with significantly broader C# syntax support, a customizable transformer pipeline, and no coupling to EF Core.

This guide covers a complete step-by-step migration, including automated code fixers that handle most of the mechanical changes.

Why Migrate

  • Modern C# syntax in LINQ chains -- Use null-conditional operators (?.), switch expressions, pattern matching, and more directly in .Where(), .Select(), .OrderBy() via IExpressiveQueryable<T>.
  • Broader C# syntax in [Expressive] members -- Switch expressions, pattern matching (constant, type, relational, logical, property, positional), string interpolation, tuples, and constructor projections all work out of the box.
  • Not EF Core specific -- Works standalone with any LINQ provider, or use ExpressionPolyfill.Create to build expression trees without a queryable.
  • More accurate code generation -- The source generator now analyzes code at the semantic level rather than rewriting syntax.
  • Customizable transformers -- The IExpressionTreeTransformer interface lets you plug in your own expression tree transformations.
  • Simpler configuration -- No CompatibilityMode. UseExpressives() handles all the EF Core defaults automatically.

Package Changes

Old PackageNew PackageNotes
EntityFrameworkCore.ProjectablesExpressiveSharp.EntityFrameworkCoreDirect replacement -- includes core as a dependency
EntityFrameworkCore.Projectables.Abstractions(included above)No longer a separate package
EntityFrameworkCore.Projectables.Generator(included above)Generator ships as an analyzer inside the package
bash
# Remove old packages
dotnet remove package EntityFrameworkCore.Projectables
dotnet remove package EntityFrameworkCore.Projectables.Abstractions

# Add new package
dotnet add package ExpressiveSharp.EntityFrameworkCore

Automated Migration with Code Fixers

ExpressiveSharp.EntityFrameworkCore includes built-in Roslyn analyzers that detect old Projectables API usage and offer automatic code fixes:

DiagnosticDetectsAuto-fix
EXP1001[Projectable] attributeRenames to [Expressive], removes obsolete properties
EXP1002UseProjectables(...) callReplaces with UseExpressives()
EXP1003using EntityFrameworkCore.Projectables*Replaces with using ExpressiveSharp*

Automated bulk fix

After installing the package, build your solution -- warnings will appear on all Projectables API usage. Use Fix All in Solution (lightbulb menu in your IDE) to apply all fixes at once.

Namespace Changes

OldNew
using EntityFrameworkCore.Projectables;using ExpressiveSharp;
using EntityFrameworkCore.Projectables.Extensions;using ExpressiveSharp;
using EntityFrameworkCore.Projectables.Infrastructure;(removed)

The EF Core extension methods (UseExpressives, AsExpressiveDbSet) live in the Microsoft.EntityFrameworkCore namespace, which you likely already import.

API Changes

Attribute Rename

csharp
// Before
[Projectable]
public double Total => Price * Quantity;

// After
[Expressive]
public double Total => Price * Quantity;

DbContext Configuration

csharp
// Before
options.UseSqlServer(connectionString)
       .UseProjectables(opts =>
       {
           opts.CompatibilityMode(CompatibilityMode.Full);
       });

// After -- no compatibility mode; optional callback for plugins
options.UseSqlServer(connectionString)
       .UseExpressives();

An optional configuration callback is available for registering plugins:

csharp
options.UseSqlServer(connectionString)
       .UseExpressives(opts => opts.AddPlugin(new MyPlugin()));

UseExpressives() automatically registers six transformers as global defaults (ReplaceThrowWithDefault, ConvertLoopsToLinq, RemoveNullConditionalPatterns, FlattenTupleComparisons, FlattenConcatArrayCalls, FlattenBlockExpressions), sets up the query compiler decorator, and configures model conventions. ReplaceThrowWithDefault can be opted out via o => o.PreserveThrowExpressions().

Null-Conditional Handling

Projectables had a three-value enum controlling null-conditional behavior:

NullConditionalRewriteSupportBehavior
NoneNull-conditional operators not allowed
IgnoreA?.B becomes A.B (strip the null check)
RewriteA?.B becomes A != null ? A.B : default

ExpressiveSharp always generates the faithful ternary pattern (A != null ? A.B : default). The RemoveNullConditionalPatterns transformer, applied globally by UseExpressives(), strips it before queries reach the database. No per-member configuration needed.

csharp
// Before
[Projectable(NullConditionalRewriteSupport = NullConditionalRewriteSupport.Rewrite)]
public string? CustomerName => Customer?.Name;

// After -- just remove the property; UseExpressives() handles it globally
[Expressive]
public string? CustomerName => Customer?.Name;

INFO

Both the old Ignore and Rewrite behaviors converge to the same result in ExpressiveSharp. The transformer strips the explicit null check, and the database handles null propagation natively via LEFT JOIN.

Changed and Removed Properties

Old PropertyMigration
UseMemberBody = "SomeMethod"Replace with [ExpressiveProperty] or plain [ExpressiveFor]. See Migrating UseMemberBody below.
AllowBlockBody = trueKeep -- block bodies remain opt-in. Set per-member or globally via Expressive_AllowBlockBody MSBuild property.
ExpandEnumMethods = trueRemove -- enum method expansion is enabled by default.
CompatibilityMode.Full / .LimitedRemove -- only the full approach exists.

Migrating UseMemberBody

In Projectables, UseMemberBody let you point one member's expression body at another member -- typically to work around syntax limitations or to provide an expression-tree-friendly alternative for projection middleware (HotChocolate, AutoMapper) that required a writable target.

ExpressiveSharp offers two replacement shapes, depending on your scenario:

  • [ExpressiveProperty] -- the closest analogue: you write only the formula; the generator synthesizes the settable target property on a partial class. The property participates in projection middleware because it has an init accessor. Best fit when you want a dedicated property backed purely by an expression.
  • Plain [ExpressiveFor] -- when the target property already exists (or lives on an external type you do not own). No property is synthesized; the stub maps to an existing member.

Pick based on whether you want the generator to declare the target property for you.

Option A -- [ExpressiveProperty] (formula-only, property is generated):

csharp
// Before (Projectables)
[Projectable(UseMemberBody = nameof(FullNameProjection))]
public string FullName { get; init; }
private string FullNameProjection => $"{LastName}, {FirstName}";

// After (ExpressiveSharp) -- partial class, stub only; FullName is generated
public partial class Customer
{
    [ExpressiveProperty("FullName")]
    private string FullNameExpression => $"{LastName}, {FirstName}";
}

The generator picks between a coalesce shape (non-nullable targets) and a ternary+flag shape (nullable targets) so materialized null stays distinguishable from "not materialized." See the [ExpressiveProperty] reference and the Projection Middleware recipe.

Target name must be a string literal

The target property does not exist during the generator's pass, so nameof(FullName) fails to resolve. Always pass the name as a string literal: [ExpressiveProperty("FullName")].

Option B -- plain [ExpressiveFor] (target property already exists, or lives on an external type):

Scenario 1: Same-type member with an alternative body

Use the co-located form: a property stub on the same class combined with the single-argument attribute. this is the receiver naturally -- the migration reads almost identically to UseMemberBody.

csharp
// Before (Projectables)
public string FullName => $"{FirstName} {LastName}".Trim().ToUpper();

[Projectable(UseMemberBody = nameof(FullNameProjection))]
public string FullName => ...;
private string FullNameProjection => $"{FirstName} {LastName}";

// After (ExpressiveSharp)
using ExpressiveSharp.Mapping;

public string FullName => $"{FirstName} {LastName}".Trim().ToUpper();

[ExpressiveFor(nameof(FullName))]
private string FullNameExpression => $"{FirstName} {LastName}";

Scenario 2: External/third-party type methods

[ExpressiveFor] also enables a use case that UseMemberBody never supported -- providing expression tree bodies for methods on types you do not own:

db
    .LineItems
    .Where(i => Math.Clamp((double)i.UnitPrice, 20, 100) > 50)

// Setup
public static class MathExpressives
{
    // Make Math.Clamp usable in EF Core queries
    [ExpressiveSharp.Mapping.ExpressiveFor(typeof(Math), nameof(Math.Clamp))]
    static double Clamp(double value, double min, double max)
        => value < min ? min : (value > max ? max : value);
}
SELECT "l"."Id", "l"."OrderId", "l"."ProductId", "l"."Quantity", "l"."UnitPrice"
FROM "LineItems" AS "l"
WHERE CASE
    WHEN CAST("l"."UnitPrice" AS REAL) < 20.0 THEN 20.0
    WHEN CAST("l"."UnitPrice" AS REAL) > 100.0 THEN 100.0
    ELSE CAST("l"."UnitPrice" AS REAL)
END > 50.0
SELECT l."Id", l."OrderId", l."ProductId", l."Quantity", l."UnitPrice"
FROM "LineItems" AS l
WHERE CASE
    WHEN l."UnitPrice"::double precision < 20.0 THEN 20.0
    WHEN l."UnitPrice"::double precision > 100.0 THEN 100.0
    ELSE l."UnitPrice"::double precision
END > 50.0
SELECT [l].[Id], [l].[OrderId], [l].[ProductId], [l].[Quantity], [l].[UnitPrice]
FROM [LineItems] AS [l]
WHERE CASE
    WHEN CAST([l].[UnitPrice] AS float) < 20.0E0 THEN 20.0E0
    WHEN CAST([l].[UnitPrice] AS float) > 100.0E0 THEN 100.0E0
    ELSE CAST([l].[UnitPrice] AS float)
END > 50.0E0
playground.line_items.Aggregate([
    {
         "$match" : {
             "$expr" : {
                 "$gt" : [
                    {
                         "$cond" : {
                             "if" : {
                                 "$lt" : [
                                    { "$toDouble" : "$UnitPrice" },
                                    20.0
                                ] 
                            },
                            "then" : 20.0,
                            "else" : {
                                 "$cond" : {
                                     "if" : {
                                         "$gt" : [
                                            { "$toDouble" : "$UnitPrice" },
                                            100.0
                                        ] 
                                    },
                                    "then" : 100.0,
                                    "else" : { "$toDouble" : "$UnitPrice" } 
                                } 
                            } 
                        } 
                    },
                    50.0
                ] 
            } 
        } 
    }
])
// === System_Math.Clamp_P0_double_P1_double_P2_double.g.cs ===
// <auto-generated/>
#nullable disable

using System;
using System.Linq;
using System.Linq.Expressions;
using ExpressiveSharp;
using ExpressiveSharp.EntityFrameworkCore;
using ExpressiveSharp.Docs.PlaygroundModel.Webshop;
using System;

namespace ExpressiveSharp.Generated
{
    static partial class System_Math 
    {
        // // Make Math.Clamp usable in EF Core queries
        // [ExpressiveSharp.Mapping.ExpressiveFor(typeof(Math), nameof(Math.Clamp))]
        // static double Clamp(double value, double min, double max) => value < min ? min : (value > max ? max : value);
        static global::System.Linq.Expressions.Expression<global::System.Func<double, double, double, double>> Clamp_P0_double_P1_double_P2_double_Expression() 
        {
            var p_value = global::System.Linq.Expressions.Expression.Parameter(typeof(double), "value");
            var p_min = global::System.Linq.Expressions.Expression.Parameter(typeof(double), "min");
            var p_max = global::System.Linq.Expressions.Expression.Parameter(typeof(double), "max");
            var expr_1 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.LessThan, p_value, p_min); // value < min
            var expr_3 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, p_value, p_max); // value > max
            var expr_2 = global::System.Linq.Expressions.Expression.Condition(expr_3, p_max, p_value, typeof(double));
            var expr_0 = global::System.Linq.Expressions.Expression.Condition(expr_1, p_min, expr_2, typeof(double));
            return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<double, double, double, double>>(expr_0, p_value, p_min, p_max);
        }
    }
}


// === System_Math.Attributes.g.cs ===
// <auto-generated/>

namespace ExpressiveSharp.Generated
{
    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
    static partial class System_Math { }
}


// === ExpressionRegistry.g.cs ===
// <auto-generated/>
#nullable disable

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace ExpressiveSharp.Generated
{
    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
    internal static class ExpressionRegistry
    {
        private static Dictionary<nint, LambdaExpression> Build()
        {
            const BindingFlags allFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
            var map = new Dictionary<nint, LambdaExpression>();
            
            Register(map, typeof(global::System.Math).GetMethod("Clamp", allFlags, null, new global::System.Type[] { typeof(double), typeof(double), typeof(double) }, null), "ExpressiveSharp.Generated.System_Math", "Clamp_P0_double_P1_double_P2_double_Expression");
            
            return map;
        }
        
        private static volatile Dictionary<nint, LambdaExpression> _map = Build();
        
        internal static void ResetMap() => _map = Build();
        
        public static LambdaExpression TryGet(MemberInfo member)
        {
            var handle = member switch
            {
                MethodInfo m      => (nint?)m.MethodHandle.Value,
                PropertyInfo p    => p.GetMethod?.MethodHandle.Value,
                ConstructorInfo c => (nint?)c.MethodHandle.Value,
                _                 => null
            };
            
            return handle.HasValue && _map.TryGetValue(handle.Value, out var expr) ? expr : null;
        }
        
        private static void Register(Dictionary<nint, LambdaExpression> map, MethodBase m, string exprClass, string exprMethodName)
        {
            if (m is null) return;
            var exprType = m.DeclaringType?.Assembly.GetType(exprClass) ?? typeof(ExpressionRegistry).Assembly.GetType(exprClass);
            var exprMethod = exprType?.GetMethod(exprMethodName, BindingFlags.Static | BindingFlags.NonPublic);
            if (exprMethod is null) return;
            var expr = (LambdaExpression)exprMethod.Invoke(null, null)!;
            
            // Apply declared transformers from the generated class (if any)
            const string expressionSuffix = "_Expression";
            if (exprMethodName.EndsWith(expressionSuffix, StringComparison.Ordinal))
            {
                var transformersSuffix = exprMethodName.Substring(0, exprMethodName.Length - expressionSuffix.Length) + "_Transformers";
                var transformersMethod = exprType.GetMethod(transformersSuffix, BindingFlags.Static | BindingFlags.NonPublic);
                if (transformersMethod?.Invoke(null, null) is global::ExpressiveSharp.IExpressionTreeTransformer[] transformers)
                {
                    Expression transformed = expr;
                    foreach (var t in transformers) transformed = t.Transform(transformed);
                    if (transformed is LambdaExpression lambdaResult) expr = lambdaResult;
                }
            }
            
            map[m.MethodHandle.Value] = expr;
        }
    }
}


// === PolyfillInterceptors_b1293e61.g.cs ===
// <auto-generated/>
#nullable disable

namespace ExpressiveSharp.Generated.Interceptors
{
    internal static partial class PolyfillInterceptors
    {
        [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "RHFFIfSUdxAXRpautE6e/IIBAABfX1NuaXBwZXQuY3M=")]
        internal static global::ExpressiveSharp.IExpressiveQueryable<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem> __Polyfill_Where_3e61_14_24(
            this global::ExpressiveSharp.IExpressiveQueryable<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem> source,
            global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem, bool> __func)
        {
            // Source: i => Math.Clamp((double)i.UnitPrice, 20, 100) > 50
            var i3e6114c24_p_i = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem), "i");
            var i3e6114c24_expr_3 = global::System.Linq.Expressions.Expression.Property(i3e6114c24_p_i, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem).GetProperty("UnitPrice", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // i.UnitPrice
            var i3e6114c24_expr_2 = global::System.Linq.Expressions.Expression.Convert(i3e6114c24_expr_3, typeof(double));
            var i3e6114c24_expr_5 = global::System.Linq.Expressions.Expression.Constant(20, typeof(int)); // 20
            var i3e6114c24_expr_4 = global::System.Linq.Expressions.Expression.Convert(i3e6114c24_expr_5, typeof(double));
            var i3e6114c24_expr_7 = global::System.Linq.Expressions.Expression.Constant(100, typeof(int)); // 100
            var i3e6114c24_expr_6 = global::System.Linq.Expressions.Expression.Convert(i3e6114c24_expr_7, typeof(double));
            var i3e6114c24_expr_1 = global::System.Linq.Expressions.Expression.Call(typeof(global::System.Math).GetMethod("Clamp", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(double), typeof(double), typeof(double) }, null), new global::System.Linq.Expressions.Expression[] { i3e6114c24_expr_2, i3e6114c24_expr_4, i3e6114c24_expr_6 });
            var i3e6114c24_expr_9 = global::System.Linq.Expressions.Expression.Constant(50, typeof(int)); // 50
            var i3e6114c24_expr_8 = global::System.Linq.Expressions.Expression.Convert(i3e6114c24_expr_9, typeof(double));
            var i3e6114c24_expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, i3e6114c24_expr_1, i3e6114c24_expr_8);
            var __lambda = global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem, bool>>(i3e6114c24_expr_0, i3e6114c24_p_i);
            return global::ExpressiveSharp.ExpressiveQueryableExtensions.AsExpressive(
                global::System.Linq.Queryable.Where(
                    (global::System.Linq.IQueryable<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem>)source,
                    __lambda));
        }
    }
}

namespace System.Runtime.CompilerServices
{
    [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)]
    file sealed class InterceptsLocationAttribute : global::System.Attribute
    {
        public InterceptsLocationAttribute(int version, string data) { }
    }
}

Scenario 3: Constructors

csharp
using ExpressiveSharp.Mapping;

[ExpressiveForConstructor(typeof(OrderDto))]
static OrderDto CreateDto(int id, string name)
    => new OrderDto { Id = id, Name = name };

Key differences from UseMemberBody:

UseMemberBody (Projectables)[ExpressiveFor] (ExpressiveSharp)
ScopeSame type onlySame type or any accessible type (including external/third-party)
SyntaxProperty on [Projectable]Separate attribute on a stub method
Target memberMust be in the same classCo-located (single-arg form, this is receiver) or cross-type (two-arg form)
NamespaceEntityFrameworkCore.ProjectablesExpressiveSharp.Mapping
ConstructorsNot supported[ExpressiveForConstructor]

TIP

Many UseMemberBody use cases in Projectables existed because of syntax limitations. Since ExpressiveSharp supports switch expressions, pattern matching, string interpolation, and block bodies, you may be able to put [Expressive] directly on the member and delete the helper entirely.

MSBuild Properties

Old PropertyMigration
Projectables_NullConditionalRewriteSupportRemove -- UseExpressives() handles this globally
Projectables_ExpandEnumMethodsRemove -- always enabled
Projectables_AllowBlockBodyRename to Expressive_AllowBlockBody

The InterceptorsNamespaces MSBuild property needed for method interceptors is set automatically.

Breaking Changes

  1. Namespace change -- All EntityFrameworkCore.Projectables.* namespaces become ExpressiveSharp.*. This is a project-wide find-and-replace (or use the EXP1003 code fixer).

  2. Attribute rename -- [Projectable] becomes [Expressive] (use the EXP1001 code fixer).

  3. NullConditionalRewriteSupport enum removed -- ExpressiveSharp always generates faithful null-conditional ternaries. UseExpressives() globally registers the RemoveNullConditionalPatterns transformer to strip them.

  4. ProjectableOptionsBuilder replaced by ExpressiveOptionsBuilder -- UseProjectables(opts => { ... }) becomes UseExpressives() (or UseExpressives(opts => opts.AddPlugin(...)) for plugin registration).

  5. UseMemberBody property removed -- Replaced by [ExpressiveFor] from ExpressiveSharp.Mapping.

  6. CompatibilityMode removed -- ExpressiveSharp always uses the full query-compiler-decoration approach.

  7. AllowBlockBody retained (opt-in) -- Block bodies require AllowBlockBody = true per-member or the MSBuild property Expressive_AllowBlockBody. UseExpressives() registers FlattenBlockExpressions for runtime.

  8. MSBuild properties Projectables_* removed -- Remove any Projectables_NullConditionalRewriteSupport, Projectables_ExpandEnumMethods, or Projectables_AllowBlockBody from .csproj / Directory.Build.props.

  9. Package consolidation -- Remove all old packages and install ExpressiveSharp.EntityFrameworkCore.

  10. Target framework -- ExpressiveSharp targets .NET 8.0, .NET 9.0, and .NET 10.0. If you are on .NET 6 or 7, you will need to upgrade.

Feature Comparison

FeatureProjectablesExpressiveSharp
Attribute[Projectable][Expressive]
Expression-bodied properties/methodsYesYes
Block-bodied methodsOpt-inOpt-in
Null-conditional ?.NullConditionalRewriteSupport enumAlways emitted; UseExpressives() strips for EF Core
Switch expressionsNoYes
Pattern matchingNoYes (constant, type, relational, logical, property, positional)
String interpolationNoYes
Tuple literalsNoYes
Constructor projectionsNoYes
Inline expression creationNoExpressionPolyfill.Create(...)
Modern syntax in LINQ chainsNoYes (IExpressiveQueryable<T>)
Custom transformersNoIExpressionTreeTransformer interface
ExpressiveDbSet<T>NoYes
External member mappingUseMemberBody (same type only)[ExpressiveFor] (any type)
SQL window functionsNoYes (RelationalExtensions package)
EF Core specificYesNo -- works standalone
Compatibility modesFull / LimitedFull only (simpler)
Code generation approachSyntax tree rewritingSemantic (IOperation) analysis
Target frameworks.NET 6+.NET 8 / .NET 9 / .NET 10

New Features Available After Migration

After migrating, you gain access to features that Projectables never had. Here are some highlights:

Modern Syntax in LINQ Chains

Use IExpressiveQueryable<T> or ExpressiveDbSet<T> to write LINQ queries with modern C# syntax:

db
    .Orders
    .Where(o => o.Customer.Email != null)
    .Select(o => new { o.Id, Name = o.Customer.Name ?? "Unknown" })
SELECT "o"."Id", "c"."Name"
FROM "Orders" AS "o"
INNER JOIN "Customers" AS "c" ON "o"."CustomerId" = "c"."Id"
WHERE "c"."Email" IS NOT NULL
SELECT o."Id", c."Name"
FROM "Orders" AS o
INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id"
WHERE c."Email" IS NOT NULL
SELECT [o].[Id], [c].[Name]
FROM [Orders] AS [o]
INNER JOIN [Customers] AS [c] ON [o].[CustomerId] = [c].[Id]
WHERE [c].[Email] IS NOT NULL
playground.orders.Aggregate([
    {
         "$match" : {
             "Customer.Email" : { "$ne" : null } 
        } 
    },
    {
         "$project" : {
             "_id" : "$_id",
            "Name" : {
                 "$ifNull" : ["$Customer.Name", "Unknown"] 
            } 
        } 
    }
])
// === PolyfillInterceptors_b1293e61.g.cs ===
// <auto-generated/>
#nullable disable

namespace ExpressiveSharp.Generated.Interceptors
{
    internal static partial class PolyfillInterceptors
    {
        [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "TiDSr2zXWMO25DRg85lGH64BAABfX1NuaXBwZXQuY3M=")]
        internal static global::ExpressiveSharp.IExpressiveQueryable<T1> __Polyfill_Select_3e61_16_5<T0, T1>(
            this global::ExpressiveSharp.IExpressiveQueryable<T0> source,
            global::System.Func<T0, T1> __func)
        {
            // Source: o => new { o.Id, Name = o.Customer.Name ?? "Unknown" }
            var i3e6116c5_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(T0), "o");
            var i3e6116c5_expr_1 = global::System.Linq.Expressions.Expression.Property(i3e6116c5_p_o, typeof(T0).GetProperty("Id", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // o.Id
            var i3e6116c5_expr_4 = global::System.Linq.Expressions.Expression.Property(i3e6116c5_p_o, typeof(T0).GetProperty("Customer", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // o.Customer
            var i3e6116c5_expr_3 = global::System.Linq.Expressions.Expression.Property(i3e6116c5_expr_4, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer).GetProperty("Name", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance));
            var i3e6116c5_expr_5 = global::System.Linq.Expressions.Expression.Constant("Unknown", typeof(string)); // "Unknown"
            var i3e6116c5_expr_2 = global::System.Linq.Expressions.Expression.Coalesce(i3e6116c5_expr_3, i3e6116c5_expr_5);
            var i3e6116c5_expr_6 = typeof(T1).GetConstructors()[0];
            var i3e6116c5_expr_0 = global::System.Linq.Expressions.Expression.New(i3e6116c5_expr_6, new global::System.Linq.Expressions.Expression[] { i3e6116c5_expr_1, i3e6116c5_expr_2 }, new global::System.Reflection.MemberInfo[] { typeof(T1).GetProperty("Id"), typeof(T1).GetProperty("Name") });
            var __lambda = global::System.Linq.Expressions.Expression.Lambda<global::System.Func<T0, T1>>(i3e6116c5_expr_0, i3e6116c5_p_o);
            return (global::ExpressiveSharp.IExpressiveQueryable<T1>)(object)
                global::ExpressiveSharp.ExpressiveQueryableExtensions.AsExpressive(
                    global::System.Linq.Queryable.Select(
                        (global::System.Linq.IQueryable<T0>)(object)source,
                        __lambda));
        }
        [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "TiDSr2zXWMO25DRg85lGH4QBAABfX1NuaXBwZXQuY3M=")]
        internal static global::ExpressiveSharp.IExpressiveQueryable<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order> __Polyfill_Where_3e61_15_5(
            this global::ExpressiveSharp.IExpressiveQueryable<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order> source,
            global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, bool> __func)
        {
            // Source: o => o.Customer.Email != null
            var i3e6115c5_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order), "o");
            var i3e6115c5_expr_2 = global::System.Linq.Expressions.Expression.Property(i3e6115c5_p_o, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order).GetProperty("Customer", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // o.Customer
            var i3e6115c5_expr_1 = global::System.Linq.Expressions.Expression.Property(i3e6115c5_expr_2, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer).GetProperty("Email", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance));
            var i3e6115c5_expr_4 = global::System.Linq.Expressions.Expression.Constant(null, typeof(object)); // null
            var i3e6115c5_expr_3 = global::System.Linq.Expressions.Expression.Convert(i3e6115c5_expr_4, typeof(string));
            var i3e6115c5_expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.NotEqual, i3e6115c5_expr_1, i3e6115c5_expr_3);
            var __lambda = global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, bool>>(i3e6115c5_expr_0, i3e6115c5_p_o);
            return global::ExpressiveSharp.ExpressiveQueryableExtensions.AsExpressive(
                global::System.Linq.Queryable.Where(
                    (global::System.Linq.IQueryable<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order>)source,
                    __lambda));
        }
    }
}

namespace System.Runtime.CompilerServices
{
    [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)]
    file sealed class InterceptsLocationAttribute : global::System.Attribute
    {
        public InterceptsLocationAttribute(int version, string data) { }
    }
}

See Modern Syntax in LINQ Chains.

ExpressionPolyfill.Create

Create expression trees inline without needing an attribute:

db
    .Customers
    .Where(ExpressionPolyfill.Create((Customer c) => c.Email?.Length > 5))
SELECT "c"."Id", "c"."Country", "c"."Email", "c"."JoinedAt", "c"."Name"
FROM "Customers" AS "c"
WHERE length("c"."Email") > 5
SELECT c."Id", c."Country", c."Email", c."JoinedAt", c."Name"
FROM "Customers" AS c
WHERE length(c."Email")::int > 5
SELECT [c].[Id], [c].[Country], [c].[Email], [c].[JoinedAt], [c].[Name]
FROM [Customers] AS [c]
WHERE CAST(LEN([c].[Email]) AS int) > 5
playground.customers.Aggregate([
    {
         "$match" : {
             "$expr" : {
                 "$gt" : [
                    { "$strLenCP" : "$Email" },
                    5
                ] 
            } 
        } 
    }
])
// === PolyfillInterceptors_b1293e61.g.cs ===
// <auto-generated/>
#nullable disable

namespace ExpressiveSharp.Generated.Interceptors
{
    internal static partial class PolyfillInterceptors
    {
        [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "b6SF+VlzNKX+QqT/OQb365sBAABfX1NuaXBwZXQuY3M=")]
        internal static global::System.Linq.Expressions.Expression<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer, bool>> __Polyfill_Create_3e61_14_49(
            global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer, bool> __func)
        {
            // Source: (Customer c) => c.Email?.Length > 5
            var i3e6114c49_p_c = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer), "c");
            var i3e6114c49_expr_1 = global::System.Linq.Expressions.Expression.Property(i3e6114c49_p_c, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer).GetProperty("Email", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // c.Email
            var i3e6114c49_expr_2 = global::System.Linq.Expressions.Expression.Property(i3e6114c49_expr_1, typeof(string).GetProperty("Length", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // .Length
            var i3e6114c49_expr_3 = global::System.Linq.Expressions.Expression.Convert(i3e6114c49_expr_2, typeof(int?));
            var i3e6114c49_expr_5 = global::System.Linq.Expressions.Expression.Constant(null, typeof(string));
            var i3e6114c49_expr_6 = global::System.Linq.Expressions.Expression.NotEqual(i3e6114c49_expr_1, i3e6114c49_expr_5);
            var i3e6114c49_expr_7 = global::System.Linq.Expressions.Expression.Default(typeof(int?));
            var i3e6114c49_expr_4 = global::System.Linq.Expressions.Expression.Condition(i3e6114c49_expr_6, i3e6114c49_expr_3, i3e6114c49_expr_7, typeof(int?));
            var i3e6114c49_expr_9 = global::System.Linq.Expressions.Expression.Constant(5, typeof(int)); // 5
            var i3e6114c49_expr_8 = global::System.Linq.Expressions.Expression.Convert(i3e6114c49_expr_9, typeof(int?));
            var i3e6114c49_expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, i3e6114c49_expr_4, i3e6114c49_expr_8);
            var __lambda = global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer, bool>>(i3e6114c49_expr_0, i3e6114c49_p_c);
            return __lambda;
        }
    }
}

namespace System.Runtime.CompilerServices
{
    [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)]
    file sealed class InterceptsLocationAttribute : global::System.Attribute
    {
        public InterceptsLocationAttribute(int version, string data) { }
    }
}

Switch Expressions and Pattern Matching

db
    .Products
    .Select(p => new { p.Name, Grade = p.GetGrade() })

// Setup
public static class ProductExt
{
    [Expressive]
    public static string GetGrade(this Product p) => p.ListPrice switch
    {
        >= 100m => "Premium",
        >= 50m  => "Standard",
        _       => "Budget",
    };
}
SELECT "p"."Name", CASE
    WHEN ef_compare("p"."ListPrice", '100.0') >= 0 THEN 'Premium'
    WHEN ef_compare("p"."ListPrice", '50.0') >= 0 THEN 'Standard'
    ELSE 'Budget'
END AS "Grade"
FROM "Products" AS "p"
SELECT p."Name", CASE
    WHEN p."ListPrice" >= 100.0 THEN 'Premium'
    WHEN p."ListPrice" >= 50.0 THEN 'Standard'
    ELSE 'Budget'
END AS "Grade"
FROM "Products" AS p
SELECT [p].[Name], CASE
    WHEN [p].[ListPrice] >= 100.0 THEN N'Premium'
    WHEN [p].[ListPrice] >= 50.0 THEN N'Standard'
    ELSE N'Budget'
END AS [Grade]
FROM [Products] AS [p]
playground.products.Aggregate([
    {
         "$project" : {
             "Name" : "$Name",
            "Grade" : {
                 "$cond" : {
                     "if" : {
                         "$gte" : [
                            "$ListPrice",
                            { "$numberDecimal" : "100" }
                        ] 
                    },
                    "then" : "Premium",
                    "else" : {
                         "$cond" : {
                             "if" : {
                                 "$gte" : [
                                    "$ListPrice",
                                    { "$numberDecimal" : "50" }
                                ] 
                            },
                            "then" : "Standard",
                            "else" : "Budget" 
                        } 
                    } 
                } 
            },
            "_id" : 0 
        } 
    }
])
// === ExpressiveSharp_Docs_Playground_Snippet_ProductExt.GetGrade_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_Product.g.cs ===
// <auto-generated/>
#nullable disable

using System;
using System.Linq;
using System.Linq.Expressions;
using ExpressiveSharp;
using ExpressiveSharp.EntityFrameworkCore;
using ExpressiveSharp.Docs.PlaygroundModel.Webshop;
using ExpressiveSharp.Docs.Playground.Snippet;

namespace ExpressiveSharp.Generated
{
    static partial class ExpressiveSharp_Docs_Playground_Snippet_ProductExt 
    {
        // [Expressive]
        // public static string GetGrade(this Product p) => p.ListPrice switch
        // {
        //     >= 100m => "Premium",
        //     >= 50m => "Standard",
        //     _ => "Budget",
        // };
        static global::System.Linq.Expressions.Expression<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Product, string>> GetGrade_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_Product_Expression() 
        {
            var p_p = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Product), "p");
            var expr_0 = global::System.Linq.Expressions.Expression.Property(p_p, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Product).GetProperty("ListPrice", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // p.ListPrice
            var expr_1 = global::System.Linq.Expressions.Expression.Constant("Budget", typeof(string)); // "Budget"
            var expr_3 = global::System.Linq.Expressions.Expression.Constant(50m, typeof(decimal)); // 50m
            var expr_2 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThanOrEqual, expr_0, expr_3);
            var expr_4 = global::System.Linq.Expressions.Expression.Constant("Standard", typeof(string)); // "Standard"
            var expr_5 = global::System.Linq.Expressions.Expression.Condition(expr_2, expr_4, expr_1, typeof(string));
            var expr_7 = global::System.Linq.Expressions.Expression.Constant(100m, typeof(decimal)); // 100m
            var expr_6 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThanOrEqual, expr_0, expr_7);
            var expr_8 = global::System.Linq.Expressions.Expression.Constant("Premium", typeof(string)); // "Premium"
            var expr_9 = global::System.Linq.Expressions.Expression.Condition(expr_6, expr_8, expr_5, typeof(string));
            return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Product, string>>(expr_9, p_p);
        }
    }
}


// === ExpressiveSharp_Docs_Playground_Snippet_ProductExt.Attributes.g.cs ===
// <auto-generated/>

namespace ExpressiveSharp.Generated
{
    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
    static partial class ExpressiveSharp_Docs_Playground_Snippet_ProductExt { }
}


// === ExpressionRegistry.g.cs ===
// <auto-generated/>
#nullable disable

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace ExpressiveSharp.Generated
{
    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
    internal static class ExpressionRegistry
    {
        private static Dictionary<nint, LambdaExpression> Build()
        {
            const BindingFlags allFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
            var map = new Dictionary<nint, LambdaExpression>();
            
            Register(map, typeof(global::ExpressiveSharp.Docs.Playground.Snippet.ProductExt).GetMethod("GetGrade", allFlags, null, new global::System.Type[] { typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Product) }, null), "ExpressiveSharp.Generated.ExpressiveSharp_Docs_Playground_Snippet_ProductExt", "GetGrade_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_Product_Expression");
            
            return map;
        }
        
        private static volatile Dictionary<nint, LambdaExpression> _map = Build();
        
        internal static void ResetMap() => _map = Build();
        
        public static LambdaExpression TryGet(MemberInfo member)
        {
            var handle = member switch
            {
                MethodInfo m      => (nint?)m.MethodHandle.Value,
                PropertyInfo p    => p.GetMethod?.MethodHandle.Value,
                ConstructorInfo c => (nint?)c.MethodHandle.Value,
                _                 => null
            };
            
            return handle.HasValue && _map.TryGetValue(handle.Value, out var expr) ? expr : null;
        }
        
        private static void Register(Dictionary<nint, LambdaExpression> map, MethodBase m, string exprClass, string exprMethodName)
        {
            if (m is null) return;
            var exprType = m.DeclaringType?.Assembly.GetType(exprClass) ?? typeof(ExpressionRegistry).Assembly.GetType(exprClass);
            var exprMethod = exprType?.GetMethod(exprMethodName, BindingFlags.Static | BindingFlags.NonPublic);
            if (exprMethod is null) return;
            var expr = (LambdaExpression)exprMethod.Invoke(null, null)!;
            
            // Apply declared transformers from the generated class (if any)
            const string expressionSuffix = "_Expression";
            if (exprMethodName.EndsWith(expressionSuffix, StringComparison.Ordinal))
            {
                var transformersSuffix = exprMethodName.Substring(0, exprMethodName.Length - expressionSuffix.Length) + "_Transformers";
                var transformersMethod = exprType.GetMethod(transformersSuffix, BindingFlags.Static | BindingFlags.NonPublic);
                if (transformersMethod?.Invoke(null, null) is global::ExpressiveSharp.IExpressionTreeTransformer[] transformers)
                {
                    Expression transformed = expr;
                    foreach (var t in transformers) transformed = t.Transform(transformed);
                    if (transformed is LambdaExpression lambdaResult) expr = lambdaResult;
                }
            }
            
            map[m.MethodHandle.Value] = expr;
        }
    }
}


// === PolyfillInterceptors_b1293e61.g.cs ===
// <auto-generated/>
#nullable disable

namespace ExpressiveSharp.Generated.Interceptors
{
    internal static partial class PolyfillInterceptors
    {
        [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "lauC5LYGkPS38aiHuxXaz4EBAABfX1NuaXBwZXQuY3M=")]
        internal static global::ExpressiveSharp.IExpressiveQueryable<T1> __Polyfill_Select_3e61_14_23<T0, T1>(
            this global::ExpressiveSharp.IExpressiveQueryable<T0> source,
            global::System.Func<T0, T1> __func)
        {
            // Source: p => new { p.Name, Grade = p.GetGrade() }
            var i3e6114c23_p_p = global::System.Linq.Expressions.Expression.Parameter(typeof(T0), "p");
            var i3e6114c23_expr_1 = global::System.Linq.Expressions.Expression.Property(i3e6114c23_p_p, typeof(T0).GetProperty("Name", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // p.Name
            var i3e6114c23_expr_2 = global::System.Linq.Expressions.Expression.Call(typeof(global::ExpressiveSharp.Docs.Playground.Snippet.ProductExt).GetMethod("GetGrade", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(T0) }, null), new global::System.Linq.Expressions.Expression[] { i3e6114c23_p_p }); // p.GetGrade()
            var i3e6114c23_expr_3 = typeof(T1).GetConstructors()[0];
            var i3e6114c23_expr_0 = global::System.Linq.Expressions.Expression.New(i3e6114c23_expr_3, new global::System.Linq.Expressions.Expression[] { i3e6114c23_expr_1, i3e6114c23_expr_2 }, new global::System.Reflection.MemberInfo[] { typeof(T1).GetProperty("Name"), typeof(T1).GetProperty("Grade") });
            var __lambda = global::System.Linq.Expressions.Expression.Lambda<global::System.Func<T0, T1>>(i3e6114c23_expr_0, i3e6114c23_p_p);
            return (global::ExpressiveSharp.IExpressiveQueryable<T1>)(object)
                global::ExpressiveSharp.ExpressiveQueryableExtensions.AsExpressive(
                    global::System.Linq.Queryable.Select(
                        (global::System.Linq.IQueryable<T0>)(object)source,
                        __lambda));
        }
    }
}

namespace System.Runtime.CompilerServices
{
    [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)]
    file sealed class InterceptsLocationAttribute : global::System.Attribute
    {
        public InterceptsLocationAttribute(int version, string data) { }
    }
}
db
    .LineItems
    .Where(i => i.IsSpecialLine())

// Setup
public static class LineItemExt
{
    [Expressive]
    public static bool IsSpecialLine(this LineItem i) => i is { Quantity: > 100, UnitPrice: >= 50m };
}
SELECT "l"."Id", "l"."OrderId", "l"."ProductId", "l"."Quantity", "l"."UnitPrice"
FROM "LineItems" AS "l"
WHERE "l"."Quantity" > 100 AND ef_compare("l"."UnitPrice", '50.0') >= 0
SELECT l."Id", l."OrderId", l."ProductId", l."Quantity", l."UnitPrice"
FROM "LineItems" AS l
WHERE l."Quantity" > 100 AND l."UnitPrice" >= 50.0
SELECT [l].[Id], [l].[OrderId], [l].[ProductId], [l].[Quantity], [l].[UnitPrice]
FROM [LineItems] AS [l]
WHERE [l].[Quantity] > 100 AND [l].[UnitPrice] >= 50.0
playground.line_items.Aggregate([
    {
         "$match" : {
             "$and" : [
                {
                     "$expr" : {
                         "$ne" : ["$$ROOT", null] 
                    } 
                },
                {
                     "Quantity" : { "$gt" : 100 } 
                },
                {
                     "UnitPrice" : {
                         "$gte" : { "$numberDecimal" : "50" } 
                    } 
                }
            ] 
        } 
    }
])
// === ExpressiveSharp_Docs_Playground_Snippet_LineItemExt.IsSpecialLine_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_LineItem.g.cs ===
// <auto-generated/>
#nullable disable

using System;
using System.Linq;
using System.Linq.Expressions;
using ExpressiveSharp;
using ExpressiveSharp.EntityFrameworkCore;
using ExpressiveSharp.Docs.PlaygroundModel.Webshop;
using ExpressiveSharp.Docs.Playground.Snippet;

namespace ExpressiveSharp.Generated
{
    static partial class ExpressiveSharp_Docs_Playground_Snippet_LineItemExt 
    {
        // [Expressive]
        // public static bool IsSpecialLine(this LineItem i) => i is { Quantity: > 100, UnitPrice: >= 50m };
        static global::System.Linq.Expressions.Expression<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem, bool>> IsSpecialLine_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_LineItem_Expression() 
        {
            var p_i = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem), "i");
            var expr_1 = global::System.Linq.Expressions.Expression.Constant(null, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem)); // i is { Quantity: > 100, UnitPrice: >= 50m }
            var expr_0 = global::System.Linq.Expressions.Expression.NotEqual(p_i, expr_1);
            var expr_2 = global::System.Linq.Expressions.Expression.Property(p_i, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem).GetProperty("Quantity", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance));
            var expr_4 = global::System.Linq.Expressions.Expression.Constant(100, typeof(int)); // 100
            var expr_3 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, expr_2, expr_4);
            var expr_5 = global::System.Linq.Expressions.Expression.Property(p_i, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem).GetProperty("UnitPrice", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance));
            var expr_7 = global::System.Linq.Expressions.Expression.Constant(50m, typeof(decimal)); // 50m
            var expr_6 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThanOrEqual, expr_5, expr_7);
            var expr_8 = global::System.Linq.Expressions.Expression.AndAlso(expr_0, expr_3);
            var expr_9 = global::System.Linq.Expressions.Expression.AndAlso(expr_8, expr_6);
            return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem, bool>>(expr_9, p_i);
        }
    }
}


// === ExpressiveSharp_Docs_Playground_Snippet_LineItemExt.Attributes.g.cs ===
// <auto-generated/>

namespace ExpressiveSharp.Generated
{
    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
    static partial class ExpressiveSharp_Docs_Playground_Snippet_LineItemExt { }
}


// === ExpressionRegistry.g.cs ===
// <auto-generated/>
#nullable disable

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace ExpressiveSharp.Generated
{
    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
    internal static class ExpressionRegistry
    {
        private static Dictionary<nint, LambdaExpression> Build()
        {
            const BindingFlags allFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
            var map = new Dictionary<nint, LambdaExpression>();
            
            Register(map, typeof(global::ExpressiveSharp.Docs.Playground.Snippet.LineItemExt).GetMethod("IsSpecialLine", allFlags, null, new global::System.Type[] { typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem) }, null), "ExpressiveSharp.Generated.ExpressiveSharp_Docs_Playground_Snippet_LineItemExt", "IsSpecialLine_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_LineItem_Expression");
            
            return map;
        }
        
        private static volatile Dictionary<nint, LambdaExpression> _map = Build();
        
        internal static void ResetMap() => _map = Build();
        
        public static LambdaExpression TryGet(MemberInfo member)
        {
            var handle = member switch
            {
                MethodInfo m      => (nint?)m.MethodHandle.Value,
                PropertyInfo p    => p.GetMethod?.MethodHandle.Value,
                ConstructorInfo c => (nint?)c.MethodHandle.Value,
                _                 => null
            };
            
            return handle.HasValue && _map.TryGetValue(handle.Value, out var expr) ? expr : null;
        }
        
        private static void Register(Dictionary<nint, LambdaExpression> map, MethodBase m, string exprClass, string exprMethodName)
        {
            if (m is null) return;
            var exprType = m.DeclaringType?.Assembly.GetType(exprClass) ?? typeof(ExpressionRegistry).Assembly.GetType(exprClass);
            var exprMethod = exprType?.GetMethod(exprMethodName, BindingFlags.Static | BindingFlags.NonPublic);
            if (exprMethod is null) return;
            var expr = (LambdaExpression)exprMethod.Invoke(null, null)!;
            
            // Apply declared transformers from the generated class (if any)
            const string expressionSuffix = "_Expression";
            if (exprMethodName.EndsWith(expressionSuffix, StringComparison.Ordinal))
            {
                var transformersSuffix = exprMethodName.Substring(0, exprMethodName.Length - expressionSuffix.Length) + "_Transformers";
                var transformersMethod = exprType.GetMethod(transformersSuffix, BindingFlags.Static | BindingFlags.NonPublic);
                if (transformersMethod?.Invoke(null, null) is global::ExpressiveSharp.IExpressionTreeTransformer[] transformers)
                {
                    Expression transformed = expr;
                    foreach (var t in transformers) transformed = t.Transform(transformed);
                    if (transformed is LambdaExpression lambdaResult) expr = lambdaResult;
                }
            }
            
            map[m.MethodHandle.Value] = expr;
        }
    }
}


// === PolyfillInterceptors_b1293e61.g.cs ===
// <auto-generated/>
#nullable disable

namespace ExpressiveSharp.Generated.Interceptors
{
    internal static partial class PolyfillInterceptors
    {
        [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "IzG++rwOjmSZqOzehMKIKoIBAABfX1NuaXBwZXQuY3M=")]
        internal static global::ExpressiveSharp.IExpressiveQueryable<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem> __Polyfill_Where_3e61_14_24(
            this global::ExpressiveSharp.IExpressiveQueryable<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem> source,
            global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem, bool> __func)
        {
            // Source: i => i.IsSpecialLine()
            var i3e6114c24_p_i = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem), "i");
            var i3e6114c24_expr_0 = global::System.Linq.Expressions.Expression.Call(typeof(global::ExpressiveSharp.Docs.Playground.Snippet.LineItemExt).GetMethod("IsSpecialLine", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem) }, null), new global::System.Linq.Expressions.Expression[] { i3e6114c24_p_i }); // i.IsSpecialLine()
            var __lambda = global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem, bool>>(i3e6114c24_expr_0, i3e6114c24_p_i);
            return global::ExpressiveSharp.ExpressiveQueryableExtensions.AsExpressive(
                global::System.Linq.Queryable.Where(
                    (global::System.Linq.IQueryable<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem>)source,
                    __lambda));
        }
    }
}

namespace System.Runtime.CompilerServices
{
    [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)]
    file sealed class InterceptsLocationAttribute : global::System.Attribute
    {
        public InterceptsLocationAttribute(int version, string data) { }
    }
}

See Scoring and Classification.

Constructor Projections

db
    .Orders
    .Select(o => OrderSummaryBuilder.From(o))

// Setup
public sealed class OrderSummary
{
    public int Id { get; init; }
    public decimal Total { get; init; }
}

public static class OrderSummaryBuilder
{
    [Expressive]
    public static OrderSummary From(Order o) => new OrderSummary
    {
        Id = o.Id,
        Total = o.Items.Sum(i => i.UnitPrice * i.Quantity),
    };
}
SELECT "o"."Id", (
    SELECT COALESCE(ef_sum(ef_multiply("l"."UnitPrice", CAST("l"."Quantity" AS TEXT))), '0.0')
    FROM "LineItems" AS "l"
    WHERE "o"."Id" = "l"."OrderId") AS "Total"
FROM "Orders" AS "o"
SELECT o."Id", (
    SELECT COALESCE(sum(l."UnitPrice" * l."Quantity"::numeric(18,2)), 0.0)
    FROM "LineItems" AS l
    WHERE o."Id" = l."OrderId") AS "Total"
FROM "Orders" AS o
SELECT [o].[Id], (
    SELECT COALESCE(SUM([l].[UnitPrice] * CAST([l].[Quantity] AS decimal(18,2))), 0.0)
    FROM [LineItems] AS [l]
    WHERE [o].[Id] = [l].[OrderId]) AS [Total]
FROM [Orders] AS [o]
playground.orders.Aggregate([
    {
         "$project" : {
             "_id" : "$_id",
            "Total" : {
                 "$sum" : {
                     "$map" : {
                         "input" : "$Items",
                        "as" : "i",
                        "in" : {
                             "$multiply" : ["$$i.UnitPrice", "$$i.Quantity"] 
                        } 
                    } 
                } 
            } 
        } 
    }
])
// === ExpressiveSharp_Docs_Playground_Snippet_OrderSummaryBuilder.From_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_Order.g.cs ===
// <auto-generated/>
#nullable disable

using System;
using System.Linq;
using System.Linq.Expressions;
using ExpressiveSharp;
using ExpressiveSharp.EntityFrameworkCore;
using ExpressiveSharp.Docs.PlaygroundModel.Webshop;
using ExpressiveSharp.Docs.Playground.Snippet;

namespace ExpressiveSharp.Generated
{
    static partial class ExpressiveSharp_Docs_Playground_Snippet_OrderSummaryBuilder 
    {
        // [Expressive]
        // public static OrderSummary From(Order o) => new OrderSummary
        // {
        //     Id = o.Id,
        //     Total = o.Items.Sum(i => i.UnitPrice * i.Quantity),
        // };
        static global::System.Linq.Expressions.Expression<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, global::ExpressiveSharp.Docs.Playground.Snippet.OrderSummary>> From_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_Order_Expression() 
        {
            var p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order), "o");
            var expr_1 = global::System.Linq.Expressions.Expression.New(typeof(global::ExpressiveSharp.Docs.Playground.Snippet.OrderSummary).GetConstructor(global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance, null, new global::System.Type[] {  }, null)); // new OrderSummary     {         Id = o.Id,         Total =...
            var expr_2 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order).GetProperty("Id", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // o.Id
            var expr_3 = global::System.Linq.Expressions.Expression.Bind(typeof(global::ExpressiveSharp.Docs.Playground.Snippet.OrderSummary).GetProperty("Id", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance), expr_2);
            var expr_5 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order).GetProperty("Items", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // o.Items
            var p_i_6 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem), "i"); // i => i.UnitPrice * i.Quantity
            var expr_8 = global::System.Linq.Expressions.Expression.Property(p_i_6, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem).GetProperty("UnitPrice", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // i.UnitPrice
            var expr_10 = global::System.Linq.Expressions.Expression.Property(p_i_6, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem).GetProperty("Quantity", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // i.Quantity
            var expr_9 = global::System.Linq.Expressions.Expression.Convert(expr_10, typeof(decimal));
            var expr_7 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Multiply, expr_8, expr_9);
            var expr_11 = global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem, decimal>>(expr_7, p_i_6);
            var expr_4 = global::System.Linq.Expressions.Expression.Call(global::System.Linq.Enumerable.First(global::System.Linq.Enumerable.Where(typeof(global::System.Linq.Enumerable).GetMethods(global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static), m => m.Name == "Sum" && m.IsGenericMethodDefinition && m.GetGenericArguments().Length == 1 && m.GetParameters().Length == 2 && m.GetParameters()[0].ParameterType.IsGenericType && !m.GetParameters()[0].ParameterType.IsGenericParameter && m.GetParameters()[1].ParameterType.IsGenericType && !m.GetParameters()[1].ParameterType.IsGenericParameter && m.GetParameters()[1].ParameterType.GetGenericArguments()[1] == typeof(decimal))).MakeGenericMethod(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem)), new global::System.Linq.Expressions.Expression[] { expr_5, expr_11 });
            var expr_12 = global::System.Linq.Expressions.Expression.Bind(typeof(global::ExpressiveSharp.Docs.Playground.Snippet.OrderSummary).GetProperty("Total", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance), expr_4);
            var expr_0 = global::System.Linq.Expressions.Expression.MemberInit(expr_1, expr_3, expr_12);
            return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, global::ExpressiveSharp.Docs.Playground.Snippet.OrderSummary>>(expr_0, p_o);
        }
    }
}


// === ExpressiveSharp_Docs_Playground_Snippet_OrderSummaryBuilder.Attributes.g.cs ===
// <auto-generated/>

namespace ExpressiveSharp.Generated
{
    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
    static partial class ExpressiveSharp_Docs_Playground_Snippet_OrderSummaryBuilder { }
}


// === ExpressionRegistry.g.cs ===
// <auto-generated/>
#nullable disable

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace ExpressiveSharp.Generated
{
    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
    internal static class ExpressionRegistry
    {
        private static Dictionary<nint, LambdaExpression> Build()
        {
            const BindingFlags allFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
            var map = new Dictionary<nint, LambdaExpression>();
            
            Register(map, typeof(global::ExpressiveSharp.Docs.Playground.Snippet.OrderSummaryBuilder).GetMethod("From", allFlags, null, new global::System.Type[] { typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order) }, null), "ExpressiveSharp.Generated.ExpressiveSharp_Docs_Playground_Snippet_OrderSummaryBuilder", "From_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_Order_Expression");
            
            return map;
        }
        
        private static volatile Dictionary<nint, LambdaExpression> _map = Build();
        
        internal static void ResetMap() => _map = Build();
        
        public static LambdaExpression TryGet(MemberInfo member)
        {
            var handle = member switch
            {
                MethodInfo m      => (nint?)m.MethodHandle.Value,
                PropertyInfo p    => p.GetMethod?.MethodHandle.Value,
                ConstructorInfo c => (nint?)c.MethodHandle.Value,
                _                 => null
            };
            
            return handle.HasValue && _map.TryGetValue(handle.Value, out var expr) ? expr : null;
        }
        
        private static void Register(Dictionary<nint, LambdaExpression> map, MethodBase m, string exprClass, string exprMethodName)
        {
            if (m is null) return;
            var exprType = m.DeclaringType?.Assembly.GetType(exprClass) ?? typeof(ExpressionRegistry).Assembly.GetType(exprClass);
            var exprMethod = exprType?.GetMethod(exprMethodName, BindingFlags.Static | BindingFlags.NonPublic);
            if (exprMethod is null) return;
            var expr = (LambdaExpression)exprMethod.Invoke(null, null)!;
            
            // Apply declared transformers from the generated class (if any)
            const string expressionSuffix = "_Expression";
            if (exprMethodName.EndsWith(expressionSuffix, StringComparison.Ordinal))
            {
                var transformersSuffix = exprMethodName.Substring(0, exprMethodName.Length - expressionSuffix.Length) + "_Transformers";
                var transformersMethod = exprType.GetMethod(transformersSuffix, BindingFlags.Static | BindingFlags.NonPublic);
                if (transformersMethod?.Invoke(null, null) is global::ExpressiveSharp.IExpressionTreeTransformer[] transformers)
                {
                    Expression transformed = expr;
                    foreach (var t in transformers) transformed = t.Transform(transformed);
                    if (transformed is LambdaExpression lambdaResult) expr = lambdaResult;
                }
            }
            
            map[m.MethodHandle.Value] = expr;
        }
    }
}


// === PolyfillInterceptors_b1293e61.g.cs ===
// <auto-generated/>
#nullable disable

namespace ExpressiveSharp.Generated.Interceptors
{
    internal static partial class PolyfillInterceptors
    {
        [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "TqZX8AEmZHEkjg8xWyCkQH8BAABfX1NuaXBwZXQuY3M=")]
        internal static global::ExpressiveSharp.IExpressiveQueryable<global::ExpressiveSharp.Docs.Playground.Snippet.OrderSummary> __Polyfill_Select_3e61_14_21(
            this global::ExpressiveSharp.IExpressiveQueryable<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order> source,
            global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, global::ExpressiveSharp.Docs.Playground.Snippet.OrderSummary> __func)
        {
            // Source: o => OrderSummaryBuilder.From(o)
            var i3e6114c21_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order), "o");
            var i3e6114c21_expr_0 = global::System.Linq.Expressions.Expression.Call(typeof(global::ExpressiveSharp.Docs.Playground.Snippet.OrderSummaryBuilder).GetMethod("From", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order) }, null), new global::System.Linq.Expressions.Expression[] { i3e6114c21_p_o }); // OrderSummaryBuilder.From(o)
            var __lambda = global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, global::ExpressiveSharp.Docs.Playground.Snippet.OrderSummary>>(i3e6114c21_expr_0, i3e6114c21_p_o);
            return global::ExpressiveSharp.ExpressiveQueryableExtensions.AsExpressive(
                global::System.Linq.Queryable.Select(
                    (global::System.Linq.IQueryable<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order>)source,
                    __lambda));
        }
    }
}

namespace System.Runtime.CompilerServices
{
    [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)]
    file sealed class InterceptsLocationAttribute : global::System.Attribute
    {
        public InterceptsLocationAttribute(int version, string data) { }
    }
}

See DTO Projections with Constructors.

External Member Mapping

db
    .LineItems
    .Where(i => Math.Abs(i.Quantity) > 0)

// Setup
public static class MathExpressives
{
    [ExpressiveSharp.Mapping.ExpressiveFor(typeof(Math), nameof(Math.Abs))]
    static int Abs(int value) => value < 0 ? -value : value;
}
SELECT "l"."Id", "l"."OrderId", "l"."ProductId", "l"."Quantity", "l"."UnitPrice"
FROM "LineItems" AS "l"
WHERE CASE
    WHEN "l"."Quantity" < 0 THEN -"l"."Quantity"
    ELSE "l"."Quantity"
END > 0
SELECT l."Id", l."OrderId", l."ProductId", l."Quantity", l."UnitPrice"
FROM "LineItems" AS l
WHERE CASE
    WHEN l."Quantity" < 0 THEN -l."Quantity"
    ELSE l."Quantity"
END > 0
SELECT [l].[Id], [l].[OrderId], [l].[ProductId], [l].[Quantity], [l].[UnitPrice]
FROM [LineItems] AS [l]
WHERE CASE
    WHEN [l].[Quantity] < 0 THEN -[l].[Quantity]
    ELSE [l].[Quantity]
END > 0
playground.line_items.Aggregate([
    {
         "$match" : {
             "$expr" : {
                 "$gt" : [
                    {
                         "$cond" : {
                             "if" : {
                                 "$lt" : ["$Quantity", 0] 
                            },
                            "then" : {
                                 "$subtract" : [0, "$Quantity"] 
                            },
                            "else" : "$Quantity" 
                        } 
                    },
                    0
                ] 
            } 
        } 
    }
])
// === System_Math.Abs_P0_int.g.cs ===
// <auto-generated/>
#nullable disable

using System;
using System.Linq;
using System.Linq.Expressions;
using ExpressiveSharp;
using ExpressiveSharp.EntityFrameworkCore;
using ExpressiveSharp.Docs.PlaygroundModel.Webshop;
using System;

namespace ExpressiveSharp.Generated
{
    static partial class System_Math 
    {
        // [ExpressiveSharp.Mapping.ExpressiveFor(typeof(Math), nameof(Math.Abs))]
        // static int Abs(int value) => value < 0 ? -value : value;
        static global::System.Linq.Expressions.Expression<global::System.Func<int, int>> Abs_P0_int_Expression() 
        {
            var p_value = global::System.Linq.Expressions.Expression.Parameter(typeof(int), "value");
            var expr_2 = global::System.Linq.Expressions.Expression.Constant(0, typeof(int)); // 0
            var expr_1 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.LessThan, p_value, expr_2);
            var expr_3 = global::System.Linq.Expressions.Expression.MakeUnary(global::System.Linq.Expressions.ExpressionType.Negate, p_value, typeof(int)); // -value
            var expr_0 = global::System.Linq.Expressions.Expression.Condition(expr_1, expr_3, p_value, typeof(int));
            return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<int, int>>(expr_0, p_value);
        }
    }
}


// === System_Math.Attributes.g.cs ===
// <auto-generated/>

namespace ExpressiveSharp.Generated
{
    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
    static partial class System_Math { }
}


// === ExpressionRegistry.g.cs ===
// <auto-generated/>
#nullable disable

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace ExpressiveSharp.Generated
{
    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
    internal static class ExpressionRegistry
    {
        private static Dictionary<nint, LambdaExpression> Build()
        {
            const BindingFlags allFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
            var map = new Dictionary<nint, LambdaExpression>();
            
            Register(map, typeof(global::System.Math).GetMethod("Abs", allFlags, null, new global::System.Type[] { typeof(int) }, null), "ExpressiveSharp.Generated.System_Math", "Abs_P0_int_Expression");
            
            return map;
        }
        
        private static volatile Dictionary<nint, LambdaExpression> _map = Build();
        
        internal static void ResetMap() => _map = Build();
        
        public static LambdaExpression TryGet(MemberInfo member)
        {
            var handle = member switch
            {
                MethodInfo m      => (nint?)m.MethodHandle.Value,
                PropertyInfo p    => p.GetMethod?.MethodHandle.Value,
                ConstructorInfo c => (nint?)c.MethodHandle.Value,
                _                 => null
            };
            
            return handle.HasValue && _map.TryGetValue(handle.Value, out var expr) ? expr : null;
        }
        
        private static void Register(Dictionary<nint, LambdaExpression> map, MethodBase m, string exprClass, string exprMethodName)
        {
            if (m is null) return;
            var exprType = m.DeclaringType?.Assembly.GetType(exprClass) ?? typeof(ExpressionRegistry).Assembly.GetType(exprClass);
            var exprMethod = exprType?.GetMethod(exprMethodName, BindingFlags.Static | BindingFlags.NonPublic);
            if (exprMethod is null) return;
            var expr = (LambdaExpression)exprMethod.Invoke(null, null)!;
            
            // Apply declared transformers from the generated class (if any)
            const string expressionSuffix = "_Expression";
            if (exprMethodName.EndsWith(expressionSuffix, StringComparison.Ordinal))
            {
                var transformersSuffix = exprMethodName.Substring(0, exprMethodName.Length - expressionSuffix.Length) + "_Transformers";
                var transformersMethod = exprType.GetMethod(transformersSuffix, BindingFlags.Static | BindingFlags.NonPublic);
                if (transformersMethod?.Invoke(null, null) is global::ExpressiveSharp.IExpressionTreeTransformer[] transformers)
                {
                    Expression transformed = expr;
                    foreach (var t in transformers) transformed = t.Transform(transformed);
                    if (transformed is LambdaExpression lambdaResult) expr = lambdaResult;
                }
            }
            
            map[m.MethodHandle.Value] = expr;
        }
    }
}


// === PolyfillInterceptors_b1293e61.g.cs ===
// <auto-generated/>
#nullable disable

namespace ExpressiveSharp.Generated.Interceptors
{
    internal static partial class PolyfillInterceptors
    {
        [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "kNonVAk5c56xXyNozrFjPYIBAABfX1NuaXBwZXQuY3M=")]
        internal static global::ExpressiveSharp.IExpressiveQueryable<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem> __Polyfill_Where_3e61_14_24(
            this global::ExpressiveSharp.IExpressiveQueryable<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem> source,
            global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem, bool> __func)
        {
            // Source: i => Math.Abs(i.Quantity) > 0
            var i3e6114c24_p_i = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem), "i");
            var i3e6114c24_expr_2 = global::System.Linq.Expressions.Expression.Property(i3e6114c24_p_i, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem).GetProperty("Quantity", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // i.Quantity
            var i3e6114c24_expr_1 = global::System.Linq.Expressions.Expression.Call(typeof(global::System.Math).GetMethod("Abs", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(int) }, null), new global::System.Linq.Expressions.Expression[] { i3e6114c24_expr_2 });
            var i3e6114c24_expr_3 = global::System.Linq.Expressions.Expression.Constant(0, typeof(int)); // 0
            var i3e6114c24_expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, i3e6114c24_expr_1, i3e6114c24_expr_3);
            var __lambda = global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem, bool>>(i3e6114c24_expr_0, i3e6114c24_p_i);
            return global::ExpressiveSharp.ExpressiveQueryableExtensions.AsExpressive(
                global::System.Linq.Queryable.Where(
                    (global::System.Linq.IQueryable<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem>)source,
                    __lambda));
        }
    }
}

namespace System.Runtime.CompilerServices
{
    [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)]
    file sealed class InterceptsLocationAttribute : global::System.Attribute
    {
        public InterceptsLocationAttribute(int version, string data) { }
    }
}

See External Member Mapping.

Custom Transformers

db
    .LineItems
    .Select(i => new { i.Id, Adjusted = i.AdjustedTotal() })

// Setup
public class MyTransformer : ExpressiveSharp.IExpressionTreeTransformer
{
    public Expression Transform(Expression expression)
    {
        return expression; // your custom transformation
    }
}

public static class LineItemExt
{
    [Expressive(Transformers = new[] { typeof(MyTransformer) })]
    public static decimal AdjustedTotal(this LineItem i) => i.UnitPrice * i.Quantity * 1.1m;
}
SELECT "l"."Id", ef_multiply(ef_multiply("l"."UnitPrice", CAST("l"."Quantity" AS TEXT)), '1.1') AS "Adjusted"
FROM "LineItems" AS "l"
SELECT l."Id", l."UnitPrice" * l."Quantity"::numeric(18,2) * 1.1 AS "Adjusted"
FROM "LineItems" AS l
SELECT [l].[Id], [l].[UnitPrice] * CAST([l].[Quantity] AS decimal(18,2)) * 1.1 AS [Adjusted]
FROM [LineItems] AS [l]
playground.line_items.Aggregate([
    {
         "$project" : {
             "_id" : "$_id",
            "Adjusted" : {
                 "$multiply" : [
                    "$UnitPrice",
                    "$Quantity",
                    { "$numberDecimal" : "1.1" }
                ] 
            } 
        } 
    }
])
// === ExpressiveSharp_Docs_Playground_Snippet_LineItemExt.AdjustedTotal_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_LineItem.g.cs ===
// <auto-generated/>
#nullable disable

using System;
using System.Linq;
using System.Linq.Expressions;
using ExpressiveSharp;
using ExpressiveSharp.EntityFrameworkCore;
using ExpressiveSharp.Docs.PlaygroundModel.Webshop;
using ExpressiveSharp.Docs.Playground.Snippet;

namespace ExpressiveSharp.Generated
{
    static partial class ExpressiveSharp_Docs_Playground_Snippet_LineItemExt 
    {
        // [Expressive(Transformers = new[] { typeof(MyTransformer) })]
        // public static decimal AdjustedTotal(this LineItem i) => i.UnitPrice * i.Quantity * 1.1m;
        static global::System.Linq.Expressions.Expression<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem, decimal>> AdjustedTotal_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_LineItem_Expression() 
        {
            var p_i = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem), "i");
            var expr_2 = global::System.Linq.Expressions.Expression.Property(p_i, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem).GetProperty("UnitPrice", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // i.UnitPrice
            var expr_4 = global::System.Linq.Expressions.Expression.Property(p_i, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem).GetProperty("Quantity", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // i.Quantity
            var expr_3 = global::System.Linq.Expressions.Expression.Convert(expr_4, typeof(decimal));
            var expr_1 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Multiply, expr_2, expr_3);
            var expr_5 = global::System.Linq.Expressions.Expression.Constant(1.1m, typeof(decimal)); // 1.1m
            var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Multiply, expr_1, expr_5);
            return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem, decimal>>(expr_0, p_i);
        }

        static global::ExpressiveSharp.IExpressionTreeTransformer[] AdjustedTotal_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_LineItem_Transformers() => [new global::ExpressiveSharp.Docs.Playground.Snippet.MyTransformer()];
    }
}


// === ExpressiveSharp_Docs_Playground_Snippet_LineItemExt.Attributes.g.cs ===
// <auto-generated/>

namespace ExpressiveSharp.Generated
{
    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
    static partial class ExpressiveSharp_Docs_Playground_Snippet_LineItemExt { }
}


// === ExpressionRegistry.g.cs ===
// <auto-generated/>
#nullable disable

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace ExpressiveSharp.Generated
{
    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
    internal static class ExpressionRegistry
    {
        private static Dictionary<nint, LambdaExpression> Build()
        {
            const BindingFlags allFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
            var map = new Dictionary<nint, LambdaExpression>();
            
            Register(map, typeof(global::ExpressiveSharp.Docs.Playground.Snippet.LineItemExt).GetMethod("AdjustedTotal", allFlags, null, new global::System.Type[] { typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem) }, null), "ExpressiveSharp.Generated.ExpressiveSharp_Docs_Playground_Snippet_LineItemExt", "AdjustedTotal_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_LineItem_Expression");
            
            return map;
        }
        
        private static volatile Dictionary<nint, LambdaExpression> _map = Build();
        
        internal static void ResetMap() => _map = Build();
        
        public static LambdaExpression TryGet(MemberInfo member)
        {
            var handle = member switch
            {
                MethodInfo m      => (nint?)m.MethodHandle.Value,
                PropertyInfo p    => p.GetMethod?.MethodHandle.Value,
                ConstructorInfo c => (nint?)c.MethodHandle.Value,
                _                 => null
            };
            
            return handle.HasValue && _map.TryGetValue(handle.Value, out var expr) ? expr : null;
        }
        
        private static void Register(Dictionary<nint, LambdaExpression> map, MethodBase m, string exprClass, string exprMethodName)
        {
            if (m is null) return;
            var exprType = m.DeclaringType?.Assembly.GetType(exprClass) ?? typeof(ExpressionRegistry).Assembly.GetType(exprClass);
            var exprMethod = exprType?.GetMethod(exprMethodName, BindingFlags.Static | BindingFlags.NonPublic);
            if (exprMethod is null) return;
            var expr = (LambdaExpression)exprMethod.Invoke(null, null)!;
            
            // Apply declared transformers from the generated class (if any)
            const string expressionSuffix = "_Expression";
            if (exprMethodName.EndsWith(expressionSuffix, StringComparison.Ordinal))
            {
                var transformersSuffix = exprMethodName.Substring(0, exprMethodName.Length - expressionSuffix.Length) + "_Transformers";
                var transformersMethod = exprType.GetMethod(transformersSuffix, BindingFlags.Static | BindingFlags.NonPublic);
                if (transformersMethod?.Invoke(null, null) is global::ExpressiveSharp.IExpressionTreeTransformer[] transformers)
                {
                    Expression transformed = expr;
                    foreach (var t in transformers) transformed = t.Transform(transformed);
                    if (transformed is LambdaExpression lambdaResult) expr = lambdaResult;
                }
            }
            
            map[m.MethodHandle.Value] = expr;
        }
    }
}


// === PolyfillInterceptors_b1293e61.g.cs ===
// <auto-generated/>
#nullable disable

namespace ExpressiveSharp.Generated.Interceptors
{
    internal static partial class PolyfillInterceptors
    {
        [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "LCn6nYxzT4tVFIO3ia0sfoIBAABfX1NuaXBwZXQuY3M=")]
        internal static global::ExpressiveSharp.IExpressiveQueryable<T1> __Polyfill_Select_3e61_14_24<T0, T1>(
            this global::ExpressiveSharp.IExpressiveQueryable<T0> source,
            global::System.Func<T0, T1> __func)
        {
            // Source: i => new { i.Id, Adjusted = i.AdjustedTotal() }
            var i3e6114c24_p_i = global::System.Linq.Expressions.Expression.Parameter(typeof(T0), "i");
            var i3e6114c24_expr_1 = global::System.Linq.Expressions.Expression.Property(i3e6114c24_p_i, typeof(T0).GetProperty("Id", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // i.Id
            var i3e6114c24_expr_2 = global::System.Linq.Expressions.Expression.Call(typeof(global::ExpressiveSharp.Docs.Playground.Snippet.LineItemExt).GetMethod("AdjustedTotal", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(T0) }, null), new global::System.Linq.Expressions.Expression[] { i3e6114c24_p_i }); // i.AdjustedTotal()
            var i3e6114c24_expr_3 = typeof(T1).GetConstructors()[0];
            var i3e6114c24_expr_0 = global::System.Linq.Expressions.Expression.New(i3e6114c24_expr_3, new global::System.Linq.Expressions.Expression[] { i3e6114c24_expr_1, i3e6114c24_expr_2 }, new global::System.Reflection.MemberInfo[] { typeof(T1).GetProperty("Id"), typeof(T1).GetProperty("Adjusted") });
            var __lambda = global::System.Linq.Expressions.Expression.Lambda<global::System.Func<T0, T1>>(i3e6114c24_expr_0, i3e6114c24_p_i);
            return (global::ExpressiveSharp.IExpressiveQueryable<T1>)(object)
                global::ExpressiveSharp.ExpressiveQueryableExtensions.AsExpressive(
                    global::System.Linq.Queryable.Select(
                        (global::System.Linq.IQueryable<T0>)(object)source,
                        __lambda));
        }
    }
}

namespace System.Runtime.CompilerServices
{
    [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)]
    file sealed class InterceptsLocationAttribute : global::System.Attribute
    {
        public InterceptsLocationAttribute(int version, string data) { }
    }
}

SQL Window Functions

csharp
using ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.WindowFunctions;

var ranked = dbContext.Orders.Select(o => new
{
    o.Id,
    Rank = WindowFunction.Rank(
        Window.PartitionBy(o.CustomerId)
              .OrderByDescending(o.PlacedAt))
});

See Window Functions and Ranking.

Quick Migration Checklist

Before you begin

Make sure you have a clean working tree (commit or stash your changes) and a passing test suite on the Projectables codebase before starting the migration.

  1. Remove all EntityFrameworkCore.Projectables* NuGet packages
  2. Add ExpressiveSharp.EntityFrameworkCore
  3. Build -- the built-in migration analyzers will flag all Projectables API usage
  4. Use Fix All in Solution for each diagnostic (EXP1001, EXP1002, EXP1003) to auto-fix
  5. Remove any Projectables_* MSBuild properties from .csproj / Directory.Build.props
  6. Replace any UseMemberBody usage with [ExpressiveFor] (see Migrating UseMemberBody)
  7. Remove any ExpandEnumMethods, NullConditionalRewriteSupport, or CompatibilityMode settings
  8. Build again and fix any remaining compilation errors
  9. Run your test suite to verify query behavior is unchanged
  10. Optionally adopt new features: ExpressiveDbSet<T>, switch expressions, pattern matching, ExpressionPolyfill.Create

See Also

Released under the MIT License.