Extension Members
ExpressiveSharp supports [Expressive] on both traditional extension methods (any .NET version) and C# 14 extension members (.NET 10+). This lets you define query logic outside of your entity classes -- useful for keeping entities clean, applying logic to types you don't own, or grouping related query helpers.
Extension Methods
Add [Expressive] to any extension method in a static class and use it inside your queries:
db
.Orders
.Where(o => o.IsHighValue(500m))
.Select(o => new { o.Id, Email = o.SafeCustomerEmail() })
// Setup
public static class OrderExtensions
{
[Expressive]
public static bool IsHighValue(this Order order, decimal threshold)
=> order.Items.Sum(i => i.UnitPrice * i.Quantity) > threshold;
[Expressive]
public static string? SafeCustomerEmail(this Order order)
=> order.Customer != null ? order.Customer.Email : null;
}SELECT "o"."Id", "c"."Email"
FROM "Orders" AS "o"
INNER JOIN "Customers" AS "c" ON "o"."CustomerId" = "c"."Id"
WHERE ef_compare((
SELECT COALESCE(ef_sum(ef_multiply("l"."UnitPrice", CAST("l"."Quantity" AS TEXT))), '0.0')
FROM "LineItems" AS "l"
WHERE "o"."Id" = "l"."OrderId"), '500.0') > 0SELECT o."Id", c."Email"
FROM "Orders" AS o
INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id"
WHERE (
SELECT COALESCE(sum(l."UnitPrice" * l."Quantity"::numeric(18,2)), 0.0)
FROM "LineItems" AS l
WHERE o."Id" = l."OrderId") > 500.0SELECT [o].[Id], [c].[Email]
FROM [Orders] AS [o]
INNER JOIN [Customers] AS [c] ON [o].[CustomerId] = [c].[Id]
WHERE (
SELECT COALESCE(SUM([l].[UnitPrice] * CAST([l].[Quantity] AS decimal(18,2))), 0.0)
FROM [LineItems] AS [l]
WHERE [o].[Id] = [l].[OrderId]) > 500.0playground.orders.Aggregate([
{
"$match" : {
"$expr" : {
"$gt" : [
{
"$sum" : {
"$map" : {
"input" : "$Items",
"as" : "i",
"in" : {
"$multiply" : ["$$i.UnitPrice", "$$i.Quantity"]
}
}
}
},
{ "$numberDecimal" : "500" }
]
}
}
},
{
"$project" : {
"_id" : "$_id",
"Email" : {
"$cond" : {
"if" : {
"$ne" : ["$Customer", null]
},
"then" : "$Customer.Email",
"else" : null
}
}
}
}
])// === ExpressiveSharp_Docs_Playground_Snippet_OrderExtensions.IsHighValue_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_Order_P1_decimal.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_OrderExtensions
{
// [Expressive]
// public static bool IsHighValue(this Order order, decimal threshold) => order.Items.Sum(i => i.UnitPrice * i.Quantity) > threshold;
static global::System.Linq.Expressions.Expression<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, decimal, bool>> IsHighValue_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_Order_P1_decimal_Expression()
{
var p_order = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order), "order");
var p_threshold = global::System.Linq.Expressions.Expression.Parameter(typeof(decimal), "threshold");
var expr_2 = global::System.Linq.Expressions.Expression.Property(p_order, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order).GetProperty("Items", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // order.Items
var p_i_3 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem), "i"); // i => i.UnitPrice * i.Quantity
var expr_5 = global::System.Linq.Expressions.Expression.Property(p_i_3, 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_7 = global::System.Linq.Expressions.Expression.Property(p_i_3, 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_6 = global::System.Linq.Expressions.Expression.Convert(expr_7, typeof(decimal));
var expr_4 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Multiply, expr_5, expr_6);
var expr_8 = global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem, decimal>>(expr_4, p_i_3);
var expr_1 = 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_2, expr_8 });
var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, expr_1, p_threshold);
return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, decimal, bool>>(expr_0, p_order, p_threshold);
}
}
}
// === ExpressiveSharp_Docs_Playground_Snippet_OrderExtensions.SafeCustomerEmail_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_OrderExtensions
{
// [Expressive]
// public static string? SafeCustomerEmail(this Order order) => order.Customer != null ? order.Customer.Email : null;
static global::System.Linq.Expressions.Expression<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, string>> SafeCustomerEmail_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_Order_Expression()
{
var p_order = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order), "order");
var expr_3 = global::System.Linq.Expressions.Expression.Property(p_order, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order).GetProperty("Customer", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // order.Customer
var expr_2 = global::System.Linq.Expressions.Expression.Convert(expr_3, typeof(object));
var expr_5 = global::System.Linq.Expressions.Expression.Constant(null, typeof(object)); // null
var expr_4 = global::System.Linq.Expressions.Expression.Convert(expr_5, typeof(object));
var expr_1 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.NotEqual, expr_2, expr_4);
var expr_7 = global::System.Linq.Expressions.Expression.Property(p_order, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order).GetProperty("Customer", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // order.Customer
var expr_6 = global::System.Linq.Expressions.Expression.Property(expr_7, 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 expr_9 = global::System.Linq.Expressions.Expression.Constant(null, typeof(object)); // null
var expr_8 = global::System.Linq.Expressions.Expression.Convert(expr_9, typeof(string));
var expr_0 = global::System.Linq.Expressions.Expression.Condition(expr_1, expr_6, expr_8, typeof(string));
return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, string>>(expr_0, p_order);
}
}
}
// === ExpressiveSharp_Docs_Playground_Snippet_OrderExtensions.Attributes.g.cs ===
// <auto-generated/>
namespace ExpressiveSharp.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
static partial class ExpressiveSharp_Docs_Playground_Snippet_OrderExtensions { }
}
// === 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.OrderExtensions).GetMethod("IsHighValue", allFlags, null, new global::System.Type[] { typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order), typeof(decimal) }, null), "ExpressiveSharp.Generated.ExpressiveSharp_Docs_Playground_Snippet_OrderExtensions", "IsHighValue_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_Order_P1_decimal_Expression");
Register(map, typeof(global::ExpressiveSharp.Docs.Playground.Snippet.OrderExtensions).GetMethod("SafeCustomerEmail", allFlags, null, new global::System.Type[] { typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order) }, null), "ExpressiveSharp.Generated.ExpressiveSharp_Docs_Playground_Snippet_OrderExtensions", "SafeCustomerEmail_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, "o8iGD2IUSLg1e1pdxcKnqqkBAABfX1NuaXBwZXQuY3M=")]
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, Email = o.SafeCustomerEmail() }
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_2 = global::System.Linq.Expressions.Expression.Call(typeof(global::ExpressiveSharp.Docs.Playground.Snippet.OrderExtensions).GetMethod("SafeCustomerEmail", 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[] { i3e6116c5_p_o }); // o.SafeCustomerEmail()
var i3e6116c5_expr_3 = typeof(T1).GetConstructors()[0];
var i3e6116c5_expr_0 = global::System.Linq.Expressions.Expression.New(i3e6116c5_expr_3, new global::System.Linq.Expressions.Expression[] { i3e6116c5_expr_1, i3e6116c5_expr_2 }, new global::System.Reflection.MemberInfo[] { typeof(T1).GetProperty("Id"), typeof(T1).GetProperty("Email") });
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, "o8iGD2IUSLg1e1pdxcKnqoQBAABfX1NuaXBwZXQuY3M=")]
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.IsHighValue(500m)
var i3e6115c5_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order), "o");
var i3e6115c5_expr_1 = global::System.Linq.Expressions.Expression.Constant(500m, typeof(decimal)); // 500m
var i3e6115c5_expr_0 = global::System.Linq.Expressions.Expression.Call(typeof(global::ExpressiveSharp.Docs.Playground.Snippet.OrderExtensions).GetMethod("IsHighValue", 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), typeof(decimal) }, null), new global::System.Linq.Expressions.Expression[] { i3e6115c5_p_o, i3e6115c5_expr_1 });
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) { }
}
}db
.Orders
.Where(o => o.IsHighValue(500m))
.Select(o => new { o.Id, Email = o.SafeCustomerEmail() })
// Setup
public static class OrderExtensions
{
[Expressive]
public static bool IsHighValue(this Order order, decimal threshold)
=> order.Items.Sum(i => i.UnitPrice * i.Quantity) > threshold;
[Expressive]
public static string? SafeCustomerEmail(this Order order)
=> order.Customer != null ? order.Customer.Email : null;
}Generated SQL:
SELECT "o"."Id", "c"."Email"
FROM "Orders" AS "o"
INNER JOIN "Customers" AS "c" ON "o"."CustomerId" = "c"."Id"
WHERE ef_compare((
SELECT COALESCE(ef_sum(ef_multiply("l"."UnitPrice", CAST("l"."Quantity" AS TEXT))), '0.0')
FROM "LineItems" AS "l"
WHERE "o"."Id" = "l"."OrderId"), '500.0') > 0The extension method body is inlined into the expression tree -- the provider sees the expanded arithmetic and member access, not a method call. The query tabs above show how each provider translates the result.
Extension Methods on Non-Entity Types
Extension methods work on any type, not just entities:
db
.Products
.Where(p => p.Name.ContainsIgnoreCase("widget"))
// Setup
public static class StringExtensions
{
[Expressive]
public static bool ContainsIgnoreCase(this string source, string value)
=> source.ToLower().Contains(value.ToLower());
}SELECT "p"."Id", "p"."Category", "p"."ListPrice", "p"."Name", "p"."StockQuantity"
FROM "Products" AS "p"
WHERE instr(lower("p"."Name"), 'widget') > 0SELECT p."Id", p."Category", p."ListPrice", p."Name", p."StockQuantity"
FROM "Products" AS p
WHERE lower(p."Name") LIKE '%widget%'SELECT [p].[Id], [p].[Category], [p].[ListPrice], [p].[Name], [p].[StockQuantity]
FROM [Products] AS [p]
WHERE LOWER([p].[Name]) LIKE N'%widget%'playground.products.Aggregate([
{
"$match" : {
"Name" : {
"$regularExpression" : { "pattern" : "widget", "options" : "is" }
}
}
}
])// === ExpressiveSharp_Docs_Playground_Snippet_StringExtensions.ContainsIgnoreCase_P0_string_P1_string.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_StringExtensions
{
// [Expressive]
// public static bool ContainsIgnoreCase(this string source, string value) => source.ToLower().Contains(value.ToLower());
static global::System.Linq.Expressions.Expression<global::System.Func<string, string, bool>> ContainsIgnoreCase_P0_string_P1_string_Expression()
{
var p_source = global::System.Linq.Expressions.Expression.Parameter(typeof(string), "source");
var p_value = global::System.Linq.Expressions.Expression.Parameter(typeof(string), "value");
var expr_1 = global::System.Linq.Expressions.Expression.Call(p_value, typeof(string).GetMethod("ToLower", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance, null, new global::System.Type[] { }, null), global::System.Array.Empty<global::System.Linq.Expressions.Expression>()); // value.ToLower()
var expr_2 = global::System.Linq.Expressions.Expression.Call(p_source, typeof(string).GetMethod("ToLower", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance, null, new global::System.Type[] { }, null), global::System.Array.Empty<global::System.Linq.Expressions.Expression>()); // source.ToLower()
var expr_0 = global::System.Linq.Expressions.Expression.Call(expr_2, typeof(string).GetMethod("Contains", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance, null, new global::System.Type[] { typeof(string) }, null), new global::System.Linq.Expressions.Expression[] { expr_1 });
return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<string, string, bool>>(expr_0, p_source, p_value);
}
}
}
// === ExpressiveSharp_Docs_Playground_Snippet_StringExtensions.Attributes.g.cs ===
// <auto-generated/>
namespace ExpressiveSharp.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
static partial class ExpressiveSharp_Docs_Playground_Snippet_StringExtensions { }
}
// === 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.StringExtensions).GetMethod("ContainsIgnoreCase", allFlags, null, new global::System.Type[] { typeof(string), typeof(string) }, null), "ExpressiveSharp.Generated.ExpressiveSharp_Docs_Playground_Snippet_StringExtensions", "ContainsIgnoreCase_P0_string_P1_string_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, "z7dqg/30Ght1qWL+6CDalYEBAABfX1NuaXBwZXQuY3M=")]
internal static global::ExpressiveSharp.IExpressiveQueryable<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Product> __Polyfill_Where_3e61_14_23(
this global::ExpressiveSharp.IExpressiveQueryable<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Product> source,
global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Product, bool> __func)
{
// Source: p => p.Name.ContainsIgnoreCase("widget")
var i3e6114c23_p_p = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Product), "p");
var i3e6114c23_expr_1 = global::System.Linq.Expressions.Expression.Property(i3e6114c23_p_p, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Product).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.Constant("widget", typeof(string)); // "widget"
var i3e6114c23_expr_0 = global::System.Linq.Expressions.Expression.Call(typeof(global::ExpressiveSharp.Docs.Playground.Snippet.StringExtensions).GetMethod("ContainsIgnoreCase", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(string), typeof(string) }, null), new global::System.Linq.Expressions.Expression[] { i3e6114c23_expr_1, i3e6114c23_expr_2 });
var __lambda = global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Product, bool>>(i3e6114c23_expr_0, i3e6114c23_p_p);
return global::ExpressiveSharp.ExpressiveQueryableExtensions.AsExpressive(
global::System.Linq.Queryable.Where(
(global::System.Linq.IQueryable<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Product>)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
.Products
.Where(p => p.Name.ContainsIgnoreCase("widget"))
// Setup
public static class StringExtensions
{
[Expressive]
public static bool ContainsIgnoreCase(this string source, string value)
=> source.ToLower().Contains(value.ToLower());
}Generated SQL:
SELECT "p"."Id", "p"."Category", "p"."ListPrice", "p"."Name", "p"."StockQuantity"
FROM "Products" AS "p"
WHERE instr(lower("p"."Name"), 'widget') > 0Primitive extensions compose the same way:
db
.LineItems
.Select(i => new { i.Id, SquaredQty = i.Quantity.Squared() })
// Setup
public static class IntExtensions
{
[Expressive]
public static int Squared(this int i) => i * i;
}SELECT "l"."Id", "l"."Quantity" * "l"."Quantity" AS "SquaredQty"
FROM "LineItems" AS "l"SELECT l."Id", l."Quantity" * l."Quantity" AS "SquaredQty"
FROM "LineItems" AS lSELECT [l].[Id], [l].[Quantity] * [l].[Quantity] AS [SquaredQty]
FROM [LineItems] AS [l]playground.line_items.Aggregate([
{
"$project" : {
"_id" : "$_id",
"SquaredQty" : {
"$multiply" : ["$Quantity", "$Quantity"]
}
}
}
])// === ExpressiveSharp_Docs_Playground_Snippet_IntExtensions.Squared_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 ExpressiveSharp.Docs.Playground.Snippet;
namespace ExpressiveSharp.Generated
{
static partial class ExpressiveSharp_Docs_Playground_Snippet_IntExtensions
{
// [Expressive]
// public static int Squared(this int i) => i * i;
static global::System.Linq.Expressions.Expression<global::System.Func<int, int>> Squared_P0_int_Expression()
{
var p_i = global::System.Linq.Expressions.Expression.Parameter(typeof(int), "i");
var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Multiply, p_i, p_i); // i * i
return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<int, int>>(expr_0, p_i);
}
}
}
// === ExpressiveSharp_Docs_Playground_Snippet_IntExtensions.Attributes.g.cs ===
// <auto-generated/>
namespace ExpressiveSharp.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
static partial class ExpressiveSharp_Docs_Playground_Snippet_IntExtensions { }
}
// === 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.IntExtensions).GetMethod("Squared", allFlags, null, new global::System.Type[] { typeof(int) }, null), "ExpressiveSharp.Generated.ExpressiveSharp_Docs_Playground_Snippet_IntExtensions", "Squared_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, "DXo3tMnPIjU9x5Qq7/UC/IIBAABfX1NuaXBwZXQuY3M=")]
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, SquaredQty = i.Quantity.Squared() }
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_3 = global::System.Linq.Expressions.Expression.Property(i3e6114c24_p_i, typeof(T0).GetProperty("Quantity", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // i.Quantity
var i3e6114c24_expr_2 = global::System.Linq.Expressions.Expression.Call(typeof(global::ExpressiveSharp.Docs.Playground.Snippet.IntExtensions).GetMethod("Squared", 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_3 });
var i3e6114c24_expr_4 = typeof(T1).GetConstructors()[0];
var i3e6114c24_expr_0 = global::System.Linq.Expressions.Expression.New(i3e6114c24_expr_4, new global::System.Linq.Expressions.Expression[] { i3e6114c24_expr_1, i3e6114c24_expr_2 }, new global::System.Reflection.MemberInfo[] { typeof(T1).GetProperty("Id"), typeof(T1).GetProperty("SquaredQty") });
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) { }
}
}db
.LineItems
.Select(i => new { i.Id, SquaredQty = i.Quantity.Squared() })
// Setup
public static class IntExtensions
{
[Expressive]
public static int Squared(this int i) => i * i;
}Generated SQL:
SELECT "l"."Id", "l"."Quantity" * "l"."Quantity" AS "SquaredQty"
FROM "LineItems" AS "l"Composing Extension Methods
Extension methods can reference other [Expressive] members -- properties, methods, or other extension methods. ExpandExpressives() resolves them transitively:
db
.Customers
.Where(c => c.IsVip())
// Setup
public static class CustomerExtensions
{
[Expressive]
public static decimal TotalSpent(this Customer c)
=> c.Orders.Sum(o => o.Items.Sum(i => i.UnitPrice * i.Quantity));
[Expressive]
public static bool IsVip(this Customer c)
=> c.TotalSpent() > 10000m; // calls another [Expressive] extension
}SELECT "c"."Id", "c"."Country", "c"."Email", "c"."JoinedAt", "c"."Name"
FROM "Customers" AS "c"
WHERE ef_compare((
SELECT COALESCE(ef_sum((
SELECT COALESCE(ef_sum(ef_multiply("l"."UnitPrice", CAST("l"."Quantity" AS TEXT))), '0.0')
FROM "LineItems" AS "l"
WHERE "o"."Id" = "l"."OrderId")), '0.0')
FROM "Orders" AS "o"
WHERE "c"."Id" = "o"."CustomerId"), '10000.0') > 0SELECT c."Id", c."Country", c."Email", c."JoinedAt", c."Name"
FROM "Customers" AS c
WHERE (
SELECT COALESCE(sum((
SELECT COALESCE(sum(l."UnitPrice" * l."Quantity"::numeric(18,2)), 0.0)
FROM "LineItems" AS l
WHERE o."Id" = l."OrderId")), 0.0)
FROM "Orders" AS o
WHERE c."Id" = o."CustomerId") > 10000.0SELECT [c].[Id], [c].[Country], [c].[Email], [c].[JoinedAt], [c].[Name]
FROM [Customers] AS [c]
WHERE (
SELECT COALESCE(SUM([s].[value]), 0.0)
FROM [Orders] AS [o]
OUTER APPLY (
SELECT COALESCE(SUM([l].[UnitPrice] * CAST([l].[Quantity] AS decimal(18,2))), 0.0) AS [value]
FROM [LineItems] AS [l]
WHERE [o].[Id] = [l].[OrderId]
) AS [s]
WHERE [c].[Id] = [o].[CustomerId]) > 10000.0playground.customers.Aggregate([
{
"$match" : {
"$expr" : {
"$gt" : [
{
"$sum" : {
"$map" : {
"input" : "$Orders",
"as" : "o",
"in" : {
"$sum" : {
"$map" : {
"input" : "$$o.Items",
"as" : "i",
"in" : {
"$multiply" : ["$$i.UnitPrice", "$$i.Quantity"]
}
}
}
}
}
}
},
{ "$numberDecimal" : "10000" }
]
}
}
}
])// === ExpressiveSharp_Docs_Playground_Snippet_CustomerExtensions.TotalSpent_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_Customer.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_CustomerExtensions
{
// [Expressive]
// public static decimal TotalSpent(this Customer c) => c.Orders.Sum(o => o.Items.Sum(i => i.UnitPrice * i.Quantity));
static global::System.Linq.Expressions.Expression<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer, decimal>> TotalSpent_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_Customer_Expression()
{
var p_c = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer), "c");
var expr_1 = global::System.Linq.Expressions.Expression.Property(p_c, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer).GetProperty("Orders", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // c.Orders
var p_o_2 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order), "o"); // o => o.Items.Sum(i => i.UnitPrice * i.Quantity)
var expr_4 = global::System.Linq.Expressions.Expression.Property(p_o_2, 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_5 = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem), "i"); // i => i.UnitPrice * i.Quantity
var expr_7 = global::System.Linq.Expressions.Expression.Property(p_i_5, 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_9 = global::System.Linq.Expressions.Expression.Property(p_i_5, 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_8 = global::System.Linq.Expressions.Expression.Convert(expr_9, typeof(decimal));
var expr_6 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Multiply, expr_7, expr_8);
var expr_10 = global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.LineItem, decimal>>(expr_6, p_i_5);
var expr_3 = 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_4, expr_10 });
var expr_11 = global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, decimal>>(expr_3, p_o_2);
var expr_0 = 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.Order)), new global::System.Linq.Expressions.Expression[] { expr_1, expr_11 });
return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer, decimal>>(expr_0, p_c);
}
}
}
// === ExpressiveSharp_Docs_Playground_Snippet_CustomerExtensions.IsVip_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_Customer.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_CustomerExtensions
{
// [Expressive]
// public static bool IsVip(this Customer c) => c.TotalSpent() > 10000m; // calls another [Expressive] extension
//
static global::System.Linq.Expressions.Expression<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer, bool>> IsVip_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_Customer_Expression()
{
var p_c = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer), "c");
var expr_1 = global::System.Linq.Expressions.Expression.Call(typeof(global::ExpressiveSharp.Docs.Playground.Snippet.CustomerExtensions).GetMethod("TotalSpent", 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.Customer) }, null), new global::System.Linq.Expressions.Expression[] { p_c }); // c.TotalSpent()
var expr_2 = global::System.Linq.Expressions.Expression.Constant(10000m, typeof(decimal)); // 10000m
var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, expr_1, expr_2);
return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer, bool>>(expr_0, p_c);
}
}
}
// === ExpressiveSharp_Docs_Playground_Snippet_CustomerExtensions.Attributes.g.cs ===
// <auto-generated/>
namespace ExpressiveSharp.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
static partial class ExpressiveSharp_Docs_Playground_Snippet_CustomerExtensions { }
}
// === 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.CustomerExtensions).GetMethod("TotalSpent", allFlags, null, new global::System.Type[] { typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer) }, null), "ExpressiveSharp.Generated.ExpressiveSharp_Docs_Playground_Snippet_CustomerExtensions", "TotalSpent_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_Customer_Expression");
Register(map, typeof(global::ExpressiveSharp.Docs.Playground.Snippet.CustomerExtensions).GetMethod("IsVip", allFlags, null, new global::System.Type[] { typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer) }, null), "ExpressiveSharp.Generated.ExpressiveSharp_Docs_Playground_Snippet_CustomerExtensions", "IsVip_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_Customer_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, "8WP7gYAhAMt5kw01AKs0cIIBAABfX1NuaXBwZXQuY3M=")]
internal static global::ExpressiveSharp.IExpressiveQueryable<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer> __Polyfill_Where_3e61_14_24(
this global::ExpressiveSharp.IExpressiveQueryable<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer> source,
global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer, bool> __func)
{
// Source: c => c.IsVip()
var i3e6114c24_p_c = global::System.Linq.Expressions.Expression.Parameter(typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer), "c");
var i3e6114c24_expr_0 = global::System.Linq.Expressions.Expression.Call(typeof(global::ExpressiveSharp.Docs.Playground.Snippet.CustomerExtensions).GetMethod("IsVip", 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.Customer) }, null), new global::System.Linq.Expressions.Expression[] { i3e6114c24_p_c }); // c.IsVip()
var __lambda = global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer, bool>>(i3e6114c24_expr_0, i3e6114c24_p_c);
return global::ExpressiveSharp.ExpressiveQueryableExtensions.AsExpressive(
global::System.Linq.Queryable.Where(
(global::System.Linq.IQueryable<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer>)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
.Customers
.Where(c => c.IsVip())
// Setup
public static class CustomerExtensions
{
[Expressive]
public static decimal TotalSpent(this Customer c)
=> c.Orders.Sum(o => o.Items.Sum(i => i.UnitPrice * i.Quantity));
[Expressive]
public static bool IsVip(this Customer c)
=> c.TotalSpent() > 10000m; // calls another [Expressive] extension
}Generated SQL:
SELECT "c"."Id", "c"."Country", "c"."Email", "c"."JoinedAt", "c"."Name"
FROM "Customers" AS "c"
WHERE ef_compare((
SELECT COALESCE(ef_sum((
SELECT COALESCE(ef_sum(ef_multiply("l"."UnitPrice", CAST("l"."Quantity" AS TEXT))), '0.0')
FROM "LineItems" AS "l"
WHERE "o"."Id" = "l"."OrderId")), '0.0')
FROM "Orders" AS "o"
WHERE "c"."Id" = "o"."CustomerId"), '10000.0') > 0C# 14 Extension Members (.NET 10+)
On .NET 10 with C# 14, you can use the new extension(T) syntax to define extension properties and methods. This is cleaner than traditional extension methods for property-like logic:
public static class OrderExtensions
{
extension(Order o)
{
[Expressive]
public decimal Total => o.Items.Sum(i => i.UnitPrice * i.Quantity);
[Expressive]
public string Grade => o.Items.Sum(i => i.UnitPrice * i.Quantity) switch
{
>= 1000m => "Premium",
>= 100m => "Standard",
_ => "Budget",
};
[Expressive]
public int ScaledItemCount(int factor) => o.Items.Count() * factor;
}
}
// Use like any other property/method:
var orders = db.Orders
.Where(o => o.Total > 500m)
.Select(o => new { o.Id, o.Total, o.Grade });Extension Members on Primitives and Interfaces
C# 14 extensions work on any type, including primitives and interfaces:
public static class IntExtensions
{
extension(int i)
{
[Expressive]
public int Squared => i * i;
}
}
db.LineItems.Select(i => new { i.Id, SquaredQty = i.Quantity.Squared });Block Bodies and Switch Expressions
C# 14 extension members support all the same features as regular [Expressive] members -- block bodies, switch expressions, pattern matching, and null-conditional operators:
public static class OrderExtensions
{
extension(Order o)
{
[Expressive(AllowBlockBody = true)]
public string GetStatus()
{
if (o.Status == OrderStatus.Delivered && o.Items.Count() > 0)
return "Completed";
return "In Progress";
}
[Expressive]
public bool IsHighValue => o.Items.Sum(i => i.UnitPrice * i.Quantity) is > 100m;
}
}.NET 10+ Only
C# 14 extension members require .NET 10 or later. On .NET 8, use traditional extension methods in static classes instead.
Extension Methods vs [ExpressiveFor]
Both let you add query logic to types you don't own, but they serve different purposes:
| Extension Methods | [ExpressiveFor] | |
|---|---|---|
| Purpose | Add new query logic | Provide an expression body for an existing method |
| Call site | entity.MyExtension() | Math.Clamp(value, min, max) (original call site) |
| When to use | You're adding new functionality | You want an existing method (BCL, third-party) to become translatable |
See [ExpressiveFor] Mapping for details on mapping existing members.
Important Rules
- Traditional extension methods must be in a static class.
- C# 14 extension members must be in a static class with an
extension(T)block. - The
thisparameter (orextension(T)parameter) represents the entity instance in the generated expression. - All standard
[Expressive]features work:AllowBlockBody,Transformers, composition with other[Expressive]members.
See Also
- [Expressive] Properties -- defining computed properties on entities directly
- [Expressive] Methods -- defining computed methods on entities
- Reusable Query Filters -- practical example of extension methods as reusable filters
- [ExpressiveFor] Mapping -- mapping existing methods on types you don't own
