Nullable Navigation Properties
This recipe covers how to work with optional (nullable) navigation properties in [Expressive] members and in LINQ chains, using null-conditional operators (?.) that are normally forbidden in expression trees.
The Challenge
Navigation properties are often nullable -- either because the relationship is optional, or because the related entity is not loaded. Expression trees cannot represent the ?. operator directly:
error CS8072: An expression tree lambda may not contain a null propagating operatorExpressiveSharp eliminates this restriction entirely.
How ExpressiveSharp Handles Null-Conditional Operators
ExpressiveSharp always generates a faithful ternary pattern for ?.:
A?.B -> A != null ? A.B : defaultWhen using EF Core with UseExpressives(), the RemoveNullConditionalPatterns transformer strips this ternary before the query reaches the database. SQL handles null propagation natively (a LEFT JOIN produces NULL for missing relationships), so the explicit check is unnecessary.
No configuration needed
Unlike some other libraries, ExpressiveSharp does not expose a NullConditionalRewriteSupport enum or per-member null-handling options. UseExpressives() applies the RemoveNullConditionalPatterns transformer globally. This is the correct behavior for all major SQL providers (SQL Server, PostgreSQL, SQLite, MySQL, Oracle).
Single-Level Example
db
.Orders
.Select(o => new { o.Id, CustomerEmail = o.CustomerEmail() })
// Setup
public static class OrderNullNavExt
{
[Expressive]
public static string? CustomerEmail(this Order o) => o.Customer?.Email;
}SELECT "o"."Id", "c"."Email" AS "CustomerEmail"
FROM "Orders" AS "o"
INNER JOIN "Customers" AS "c" ON "o"."CustomerId" = "c"."Id"SELECT o."Id", c."Email" AS "CustomerEmail"
FROM "Orders" AS o
INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id"SELECT [o].[Id], [c].[Email] AS [CustomerEmail]
FROM [Orders] AS [o]
INNER JOIN [Customers] AS [c] ON [o].[CustomerId] = [c].[Id]playground.orders.Aggregate([
{
"$project" : { "_id" : "$_id", "CustomerEmail" : "$Customer.Email" }
}
])// === ExpressiveSharp_Docs_Playground_Snippet_OrderNullNavExt.CustomerEmail_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_OrderNullNavExt
{
// [Expressive]
// public static string? CustomerEmail(this Order o) => o.Customer?.Email;
static global::System.Linq.Expressions.Expression<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, string>> CustomerEmail_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_0 = global::System.Linq.Expressions.Expression.Property(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 expr_1 = global::System.Linq.Expressions.Expression.Property(expr_0, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer).GetProperty("Email", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // .Email
var expr_3 = global::System.Linq.Expressions.Expression.Constant(null, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer));
var expr_4 = global::System.Linq.Expressions.Expression.NotEqual(expr_0, expr_3);
var expr_5 = global::System.Linq.Expressions.Expression.Default(typeof(string));
var expr_2 = global::System.Linq.Expressions.Expression.Condition(expr_4, expr_1, expr_5, typeof(string));
return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, string>>(expr_2, p_o);
}
}
}
// === ExpressiveSharp_Docs_Playground_Snippet_OrderNullNavExt.Attributes.g.cs ===
// <auto-generated/>
namespace ExpressiveSharp.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
static partial class ExpressiveSharp_Docs_Playground_Snippet_OrderNullNavExt { }
}
// === 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.OrderNullNavExt).GetMethod("CustomerEmail", allFlags, null, new global::System.Type[] { typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order) }, null), "ExpressiveSharp.Generated.ExpressiveSharp_Docs_Playground_Snippet_OrderNullNavExt", "CustomerEmail_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, "QPd4k8J0NtJOrnMQmBXgEH8BAABfX1NuaXBwZXQuY3M=")]
internal static global::ExpressiveSharp.IExpressiveQueryable<T1> __Polyfill_Select_3e61_14_21<T0, T1>(
this global::ExpressiveSharp.IExpressiveQueryable<T0> source,
global::System.Func<T0, T1> __func)
{
// Source: o => new { o.Id, CustomerEmail = o.CustomerEmail() }
var i3e6114c21_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(T0), "o");
var i3e6114c21_expr_1 = global::System.Linq.Expressions.Expression.Property(i3e6114c21_p_o, typeof(T0).GetProperty("Id", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // o.Id
var i3e6114c21_expr_2 = global::System.Linq.Expressions.Expression.Call(typeof(global::ExpressiveSharp.Docs.Playground.Snippet.OrderNullNavExt).GetMethod("CustomerEmail", 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[] { i3e6114c21_p_o }); // o.CustomerEmail()
var i3e6114c21_expr_3 = typeof(T1).GetConstructors()[0];
var i3e6114c21_expr_0 = global::System.Linq.Expressions.Expression.New(i3e6114c21_expr_3, new global::System.Linq.Expressions.Expression[] { i3e6114c21_expr_1, i3e6114c21_expr_2 }, new global::System.Reflection.MemberInfo[] { typeof(T1).GetProperty("Id"), typeof(T1).GetProperty("CustomerEmail") });
var __lambda = global::System.Linq.Expressions.Expression.Lambda<global::System.Func<T0, T1>>(i3e6114c21_expr_0, i3e6114c21_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));
}
}
}
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
.Select(o => new { o.Id, CustomerEmail = o.CustomerEmail() })
// Setup
public static class OrderNullNavExt
{
[Expressive]
public static string? CustomerEmail(this Order o) => o.Customer?.Email;
}Generated SQL:
SELECT "o"."Id", "c"."Email" AS "CustomerEmail"
FROM "Orders" AS "o"
INNER JOIN "Customers" AS "c" ON "o"."CustomerId" = "c"."Id"The ?. operator is removed by the transformer (for SQL providers), and EF Core produces a clean LEFT JOIN. If Customer is NULL, the result is NULL for Email -- exactly matching the C# semantics.
Multi-Level Chain
Deeply nested nullable navigation chains work the same way. For the webshop model, the chain Order -> Customer -> Country traverses one nullable level:
db
.Orders
.Select(o => new { o.Id, Country = o.CustomerCountry() })
// Setup
public static class OrderMultiNav
{
[Expressive]
public static string? CustomerCountry(this Order o) => o.Customer?.Country;
}SELECT "o"."Id", "c"."Country"
FROM "Orders" AS "o"
INNER JOIN "Customers" AS "c" ON "o"."CustomerId" = "c"."Id"SELECT o."Id", c."Country"
FROM "Orders" AS o
INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id"SELECT [o].[Id], [c].[Country]
FROM [Orders] AS [o]
INNER JOIN [Customers] AS [c] ON [o].[CustomerId] = [c].[Id]playground.orders.Aggregate([
{
"$project" : { "_id" : "$_id", "Country" : "$Customer.Country" }
}
])// === ExpressiveSharp_Docs_Playground_Snippet_OrderMultiNav.CustomerCountry_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_OrderMultiNav
{
// [Expressive]
// public static string? CustomerCountry(this Order o) => o.Customer?.Country;
static global::System.Linq.Expressions.Expression<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, string>> CustomerCountry_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_0 = global::System.Linq.Expressions.Expression.Property(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 expr_1 = global::System.Linq.Expressions.Expression.Property(expr_0, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer).GetProperty("Country", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // .Country
var expr_3 = global::System.Linq.Expressions.Expression.Constant(null, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer));
var expr_4 = global::System.Linq.Expressions.Expression.NotEqual(expr_0, expr_3);
var expr_5 = global::System.Linq.Expressions.Expression.Default(typeof(string));
var expr_2 = global::System.Linq.Expressions.Expression.Condition(expr_4, expr_1, expr_5, typeof(string));
return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, string>>(expr_2, p_o);
}
}
}
// === ExpressiveSharp_Docs_Playground_Snippet_OrderMultiNav.Attributes.g.cs ===
// <auto-generated/>
namespace ExpressiveSharp.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
static partial class ExpressiveSharp_Docs_Playground_Snippet_OrderMultiNav { }
}
// === 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.OrderMultiNav).GetMethod("CustomerCountry", allFlags, null, new global::System.Type[] { typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order) }, null), "ExpressiveSharp.Generated.ExpressiveSharp_Docs_Playground_Snippet_OrderMultiNav", "CustomerCountry_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, "ppVMUZz33vehSuJZLVtWdX8BAABfX1NuaXBwZXQuY3M=")]
internal static global::ExpressiveSharp.IExpressiveQueryable<T1> __Polyfill_Select_3e61_14_21<T0, T1>(
this global::ExpressiveSharp.IExpressiveQueryable<T0> source,
global::System.Func<T0, T1> __func)
{
// Source: o => new { o.Id, Country = o.CustomerCountry() }
var i3e6114c21_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(T0), "o");
var i3e6114c21_expr_1 = global::System.Linq.Expressions.Expression.Property(i3e6114c21_p_o, typeof(T0).GetProperty("Id", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // o.Id
var i3e6114c21_expr_2 = global::System.Linq.Expressions.Expression.Call(typeof(global::ExpressiveSharp.Docs.Playground.Snippet.OrderMultiNav).GetMethod("CustomerCountry", 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[] { i3e6114c21_p_o }); // o.CustomerCountry()
var i3e6114c21_expr_3 = typeof(T1).GetConstructors()[0];
var i3e6114c21_expr_0 = global::System.Linq.Expressions.Expression.New(i3e6114c21_expr_3, new global::System.Linq.Expressions.Expression[] { i3e6114c21_expr_1, i3e6114c21_expr_2 }, new global::System.Reflection.MemberInfo[] { typeof(T1).GetProperty("Id"), typeof(T1).GetProperty("Country") });
var __lambda = global::System.Linq.Expressions.Expression.Lambda<global::System.Func<T0, T1>>(i3e6114c21_expr_0, i3e6114c21_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));
}
}
}
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
.Select(o => new { o.Id, Country = o.CustomerCountry() })
// Setup
public static class OrderMultiNav
{
[Expressive]
public static string? CustomerCountry(this Order o) => o.Customer?.Country;
}Generated SQL:
SELECT "o"."Id", "c"."Country"
FROM "Orders" AS "o"
INNER JOIN "Customers" AS "c" ON "o"."CustomerId" = "c"."Id"Each ?. in the chain produces a LEFT JOIN. The transformer strips all the ternaries, and the database handles null propagation naturally.
Using with IExpressiveQueryable (Modern Syntax)
You do not need an [Expressive] member to use ?. in queries. With IExpressiveQueryable<T> or ExpressiveDbSet<T>, you can write null-conditional operators directly in your LINQ lambdas:
db
.Orders
.Where(o => o.Customer?.Email != null)
.Select(o => new
{
o.Id,
Name = o.Customer?.Name ?? "Unknown",
Country = o.Customer?.Country
})SELECT "o"."Id", "c"."Name", "c"."Country"
FROM "Orders" AS "o"
INNER JOIN "Customers" AS "c" ON "o"."CustomerId" = "c"."Id"
WHERE "c"."Email" IS NOT NULLSELECT o."Id", c."Name", c."Country"
FROM "Orders" AS o
INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id"
WHERE c."Email" IS NOT NULLSELECT [o].[Id], [c].[Name], [c].[Country]
FROM [Orders] AS [o]
INNER JOIN [Customers] AS [c] ON [o].[CustomerId] = [c].[Id]
WHERE [c].[Email] IS NOT NULLplayground.orders.Aggregate([
{
"$match" : {
"Customer.Email" : { "$ne" : null }
}
},
{
"$project" : {
"_id" : "$_id",
"Name" : {
"$ifNull" : ["$Customer.Name", "Unknown"]
},
"Country" : "$Customer.Country"
}
}
])// === PolyfillInterceptors_b1293e61.g.cs ===
// <auto-generated/>
#nullable disable
namespace ExpressiveSharp.Generated.Interceptors
{
internal static partial class PolyfillInterceptors
{
[global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "wXO0a/WdUxTcYu11ewqFiq8BAABfX1NuaXBwZXQuY3M=")]
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", Country = o.Customer?.Country }
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_3 = 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_4 = global::System.Linq.Expressions.Expression.Property(i3e6116c5_expr_3, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer).GetProperty("Name", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // .Name
var i3e6116c5_expr_6 = global::System.Linq.Expressions.Expression.Constant(null, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer));
var i3e6116c5_expr_7 = global::System.Linq.Expressions.Expression.NotEqual(i3e6116c5_expr_3, i3e6116c5_expr_6);
var i3e6116c5_expr_8 = global::System.Linq.Expressions.Expression.Default(typeof(string));
var i3e6116c5_expr_5 = global::System.Linq.Expressions.Expression.Condition(i3e6116c5_expr_7, i3e6116c5_expr_4, i3e6116c5_expr_8, typeof(string));
var i3e6116c5_expr_9 = global::System.Linq.Expressions.Expression.Constant("Unknown", typeof(string)); // "Unknown"
var i3e6116c5_expr_2 = global::System.Linq.Expressions.Expression.Coalesce(i3e6116c5_expr_5, i3e6116c5_expr_9);
var i3e6116c5_expr_10 = 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_11 = global::System.Linq.Expressions.Expression.Property(i3e6116c5_expr_10, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer).GetProperty("Country", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // .Country
var i3e6116c5_expr_13 = global::System.Linq.Expressions.Expression.Constant(null, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer));
var i3e6116c5_expr_14 = global::System.Linq.Expressions.Expression.NotEqual(i3e6116c5_expr_10, i3e6116c5_expr_13);
var i3e6116c5_expr_15 = global::System.Linq.Expressions.Expression.Default(typeof(string));
var i3e6116c5_expr_12 = global::System.Linq.Expressions.Expression.Condition(i3e6116c5_expr_14, i3e6116c5_expr_11, i3e6116c5_expr_15, typeof(string));
var i3e6116c5_expr_16 = typeof(T1).GetConstructors()[0];
var i3e6116c5_expr_0 = global::System.Linq.Expressions.Expression.New(i3e6116c5_expr_16, new global::System.Linq.Expressions.Expression[] { i3e6116c5_expr_1, i3e6116c5_expr_2, i3e6116c5_expr_12 }, new global::System.Reflection.MemberInfo[] { typeof(T1).GetProperty("Id"), typeof(T1).GetProperty("Name"), typeof(T1).GetProperty("Country") });
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, "wXO0a/WdUxTcYu11ewqFioQBAABfX1NuaXBwZXQuY3M=")]
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_1 = 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_2 = global::System.Linq.Expressions.Expression.Property(i3e6115c5_expr_1, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer).GetProperty("Email", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // .Email
var i3e6115c5_expr_4 = global::System.Linq.Expressions.Expression.Constant(null, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer));
var i3e6115c5_expr_5 = global::System.Linq.Expressions.Expression.NotEqual(i3e6115c5_expr_1, i3e6115c5_expr_4);
var i3e6115c5_expr_6 = global::System.Linq.Expressions.Expression.Default(typeof(string));
var i3e6115c5_expr_3 = global::System.Linq.Expressions.Expression.Condition(i3e6115c5_expr_5, i3e6115c5_expr_2, i3e6115c5_expr_6, typeof(string));
var i3e6115c5_expr_8 = global::System.Linq.Expressions.Expression.Constant(null, typeof(object)); // null
var i3e6115c5_expr_7 = global::System.Linq.Expressions.Expression.Convert(i3e6115c5_expr_8, typeof(string));
var i3e6115c5_expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.NotEqual, i3e6115c5_expr_3, i3e6115c5_expr_7);
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.Customer?.Email != null)
.Select(o => new
{
o.Id,
Name = o.Customer?.Name ?? "Unknown",
Country = o.Customer?.Country
})Generated SQL:
SELECT "o"."Id", "c"."Name", "c"."Country"
FROM "Orders" AS "o"
INNER JOIN "Customers" AS "c" ON "o"."CustomerId" = "c"."Id"
WHERE "c"."Email" IS NOT NULLSee Modern Syntax in LINQ Chains for more examples.
Null-Conditional with Null-Coalescing
Combine ?. with ?? for default values:
db
.Orders
.Select(o => new
{
CustomerName = o.CustomerName(),
ShippingCountry = o.ShippingCountry()
})
// Setup
public static class OrderNullCoalesce
{
[Expressive]
public static string CustomerName(this Order o) => o.Customer?.Name ?? "Guest";
[Expressive]
public static string ShippingCountry(this Order o) => o.Customer?.Country ?? "Unknown";
}SELECT "c"."Name" AS "CustomerName", COALESCE("c"."Country", 'Unknown') AS "ShippingCountry"
FROM "Orders" AS "o"
INNER JOIN "Customers" AS "c" ON "o"."CustomerId" = "c"."Id"SELECT c."Name" AS "CustomerName", COALESCE(c."Country", 'Unknown') AS "ShippingCountry"
FROM "Orders" AS o
INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id"SELECT [c].[Name] AS [CustomerName], COALESCE([c].[Country], N'Unknown') AS [ShippingCountry]
FROM [Orders] AS [o]
INNER JOIN [Customers] AS [c] ON [o].[CustomerId] = [c].[Id]playground.orders.Aggregate([
{
"$project" : {
"CustomerName" : {
"$ifNull" : ["$Customer.Name", "Guest"]
},
"ShippingCountry" : {
"$ifNull" : ["$Customer.Country", "Unknown"]
},
"_id" : 0
}
}
])// === ExpressiveSharp_Docs_Playground_Snippet_OrderNullCoalesce.CustomerName_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_OrderNullCoalesce
{
// [Expressive]
// public static string CustomerName(this Order o) => o.Customer?.Name ?? "Guest";
static global::System.Linq.Expressions.Expression<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, string>> CustomerName_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.Property(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 expr_2 = global::System.Linq.Expressions.Expression.Property(expr_1, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer).GetProperty("Name", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // .Name
var expr_4 = global::System.Linq.Expressions.Expression.Constant(null, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer));
var expr_5 = global::System.Linq.Expressions.Expression.NotEqual(expr_1, expr_4);
var expr_6 = global::System.Linq.Expressions.Expression.Default(typeof(string));
var expr_3 = global::System.Linq.Expressions.Expression.Condition(expr_5, expr_2, expr_6, typeof(string));
var expr_7 = global::System.Linq.Expressions.Expression.Constant("Guest", typeof(string)); // "Guest"
var expr_0 = global::System.Linq.Expressions.Expression.Coalesce(expr_3, expr_7);
return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, string>>(expr_0, p_o);
}
}
}
// === ExpressiveSharp_Docs_Playground_Snippet_OrderNullCoalesce.ShippingCountry_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_OrderNullCoalesce
{
// [Expressive]
// public static string ShippingCountry(this Order o) => o.Customer?.Country ?? "Unknown";
static global::System.Linq.Expressions.Expression<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, string>> ShippingCountry_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.Property(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 expr_2 = global::System.Linq.Expressions.Expression.Property(expr_1, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer).GetProperty("Country", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // .Country
var expr_4 = global::System.Linq.Expressions.Expression.Constant(null, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer));
var expr_5 = global::System.Linq.Expressions.Expression.NotEqual(expr_1, expr_4);
var expr_6 = global::System.Linq.Expressions.Expression.Default(typeof(string));
var expr_3 = global::System.Linq.Expressions.Expression.Condition(expr_5, expr_2, expr_6, typeof(string));
var expr_7 = global::System.Linq.Expressions.Expression.Constant("Unknown", typeof(string)); // "Unknown"
var expr_0 = global::System.Linq.Expressions.Expression.Coalesce(expr_3, expr_7);
return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, string>>(expr_0, p_o);
}
}
}
// === ExpressiveSharp_Docs_Playground_Snippet_OrderNullCoalesce.Attributes.g.cs ===
// <auto-generated/>
namespace ExpressiveSharp.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
static partial class ExpressiveSharp_Docs_Playground_Snippet_OrderNullCoalesce { }
}
// === 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.OrderNullCoalesce).GetMethod("CustomerName", allFlags, null, new global::System.Type[] { typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order) }, null), "ExpressiveSharp.Generated.ExpressiveSharp_Docs_Playground_Snippet_OrderNullCoalesce", "CustomerName_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_Order_Expression");
Register(map, typeof(global::ExpressiveSharp.Docs.Playground.Snippet.OrderNullCoalesce).GetMethod("ShippingCountry", allFlags, null, new global::System.Type[] { typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order) }, null), "ExpressiveSharp.Generated.ExpressiveSharp_Docs_Playground_Snippet_OrderNullCoalesce", "ShippingCountry_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, "HcBk6BSOd+gWpOhRyFA55X8BAABfX1NuaXBwZXQuY3M=")]
internal static global::ExpressiveSharp.IExpressiveQueryable<T1> __Polyfill_Select_3e61_14_21<T0, T1>(
this global::ExpressiveSharp.IExpressiveQueryable<T0> source,
global::System.Func<T0, T1> __func)
{
// Source: o => new { CustomerName = o.CustomerName(), ShippingCountry = o.ShippingCountry() }
var i3e6114c21_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(T0), "o");
var i3e6114c21_expr_1 = global::System.Linq.Expressions.Expression.Call(typeof(global::ExpressiveSharp.Docs.Playground.Snippet.OrderNullCoalesce).GetMethod("CustomerName", 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[] { i3e6114c21_p_o }); // o.CustomerName()
var i3e6114c21_expr_2 = global::System.Linq.Expressions.Expression.Call(typeof(global::ExpressiveSharp.Docs.Playground.Snippet.OrderNullCoalesce).GetMethod("ShippingCountry", 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[] { i3e6114c21_p_o }); // o.ShippingCountry()
var i3e6114c21_expr_3 = typeof(T1).GetConstructors()[0];
var i3e6114c21_expr_0 = global::System.Linq.Expressions.Expression.New(i3e6114c21_expr_3, new global::System.Linq.Expressions.Expression[] { i3e6114c21_expr_1, i3e6114c21_expr_2 }, new global::System.Reflection.MemberInfo[] { typeof(T1).GetProperty("CustomerName"), typeof(T1).GetProperty("ShippingCountry") });
var __lambda = global::System.Linq.Expressions.Expression.Lambda<global::System.Func<T0, T1>>(i3e6114c21_expr_0, i3e6114c21_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));
}
}
}
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
.Select(o => new
{
CustomerName = o.CustomerName(),
ShippingCountry = o.ShippingCountry()
})
// Setup
public static class OrderNullCoalesce
{
[Expressive]
public static string CustomerName(this Order o) => o.Customer?.Name ?? "Guest";
[Expressive]
public static string ShippingCountry(this Order o) => o.Customer?.Country ?? "Unknown";
}Generated SQL:
SELECT "c"."Name" AS "CustomerName", COALESCE("c"."Country", 'Unknown') AS "ShippingCountry"
FROM "Orders" AS "o"
INNER JOIN "Customers" AS "c" ON "o"."CustomerId" = "c"."Id"Without EF Core: Applying the Transformer Manually
If you are not using EF Core (and therefore not using UseExpressives()), you can apply the transformer per-member or globally:
Per-member
db
.Orders
.Select(o => o.CustomerNameSafe())
// Setup
public static class OrderPerMemberTransformer
{
[Expressive(Transformers = new[] { typeof(ExpressiveSharp.Transformers.RemoveNullConditionalPatterns) })]
public static string? CustomerNameSafe(this Order o) => o.Customer?.Name;
}SELECT "c"."Name"
FROM "Orders" AS "o"
INNER JOIN "Customers" AS "c" ON "o"."CustomerId" = "c"."Id"SELECT c."Name"
FROM "Orders" AS o
INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id"SELECT [c].[Name]
FROM [Orders] AS [o]
INNER JOIN [Customers] AS [c] ON [o].[CustomerId] = [c].[Id]playground.orders.Aggregate([
{
"$project" : { "_v" : "$Customer.Name", "_id" : 0 }
}
])// === ExpressiveSharp_Docs_Playground_Snippet_OrderPerMemberTransformer.CustomerNameSafe_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_OrderPerMemberTransformer
{
// [Expressive(Transformers = new[] { typeof(ExpressiveSharp.Transformers.RemoveNullConditionalPatterns) })]
// public static string? CustomerNameSafe(this Order o) => o.Customer?.Name;
static global::System.Linq.Expressions.Expression<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, string>> CustomerNameSafe_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_0 = global::System.Linq.Expressions.Expression.Property(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 expr_1 = global::System.Linq.Expressions.Expression.Property(expr_0, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer).GetProperty("Name", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // .Name
var expr_3 = global::System.Linq.Expressions.Expression.Constant(null, typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Customer));
var expr_4 = global::System.Linq.Expressions.Expression.NotEqual(expr_0, expr_3);
var expr_5 = global::System.Linq.Expressions.Expression.Default(typeof(string));
var expr_2 = global::System.Linq.Expressions.Expression.Condition(expr_4, expr_1, expr_5, typeof(string));
return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, string>>(expr_2, p_o);
}
static global::ExpressiveSharp.IExpressionTreeTransformer[] CustomerNameSafe_P0_ExpressiveSharp_Docs_PlaygroundModel_Webshop_Order_Transformers() => [new global::ExpressiveSharp.Transformers.RemoveNullConditionalPatterns()];
}
}
// === ExpressiveSharp_Docs_Playground_Snippet_OrderPerMemberTransformer.Attributes.g.cs ===
// <auto-generated/>
namespace ExpressiveSharp.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
static partial class ExpressiveSharp_Docs_Playground_Snippet_OrderPerMemberTransformer { }
}
// === 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.OrderPerMemberTransformer).GetMethod("CustomerNameSafe", allFlags, null, new global::System.Type[] { typeof(global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order) }, null), "ExpressiveSharp.Generated.ExpressiveSharp_Docs_Playground_Snippet_OrderPerMemberTransformer", "CustomerNameSafe_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, "K8BCeV+J5siWqy5xLCuVP38BAABfX1NuaXBwZXQuY3M=")]
internal static global::ExpressiveSharp.IExpressiveQueryable<string> __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, string> __func)
{
// Source: o => o.CustomerNameSafe()
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.OrderPerMemberTransformer).GetMethod("CustomerNameSafe", 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 }); // o.CustomerNameSafe()
var __lambda = global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::ExpressiveSharp.Docs.PlaygroundModel.Webshop.Order, string>>(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) { }
}
}db
.Orders
.Select(o => o.CustomerNameSafe())
// Setup
public static class OrderPerMemberTransformer
{
[Expressive(Transformers = new[] { typeof(ExpressiveSharp.Transformers.RemoveNullConditionalPatterns) })]
public static string? CustomerNameSafe(this Order o) => o.Customer?.Name;
}Generated SQL:
SELECT "c"."Name"
FROM "Orders" AS "o"
INNER JOIN "Customers" AS "c" ON "o"."CustomerId" = "c"."Id"Globally
ExpressiveOptions.Default.AddTransformers(new RemoveNullConditionalPatterns());
// All subsequent ExpandExpressives() calls strip null-conditional patterns
Expression<Func<Order, string?>> expr = o => o.CustomerNameSafe();
var expanded = expr.ExpandExpressives();With ExpressionPolyfill.Create
var expr = ExpressionPolyfill.Create(
(Order o) => o.Customer?.Email,
new RemoveNullConditionalPatterns());Tips
UseExpressives() handles everything
If you are using EF Core with UseExpressives(), null-conditional handling is fully automatic. No per-member configuration needed.
Non-SQL providers
If your LINQ provider does not handle null propagation natively (for example, an in-memory provider used in tests), you may want to not apply RemoveNullConditionalPatterns. The faithful ternary pattern will evaluate correctly in those environments.
See Also
- Computed Entity Properties -- building blocks that can include nullable navigation
- Modern Syntax in LINQ Chains --
?.directly in Where/Select - Reusable Query Filters -- filters that guard against null navigation
