With the following example classes (which could map to a database table):
class Package
{
public string Name { get; set; }
public decimal Price { get; set; }
public bool Enabled { get; set; }
public static Expression<Func<Package, bool>> IsActive =>
x => x.Enabled;
}
class Schedule
{
public DateTimeOffset Start { get; set; }
public DateTimeOffset End { get; set; }
public bool Enabled { get; set; }
public static Expression<Func<Schedule, bool>> IsActive =>
x => x.Enabled && x.Start > DateTimeOffset.Now;
}
and the following (which could be a join table):
class SchedulePackage
{
public Package Package { get; init; }
public Schedule Schedule { get; init; }
}
How can I merge the two expressions with AndAlso? An active SchedulePackage is one that both its package and schedule are active (whatever those mean). So, in order to avoid code duplication, it's better to reuse the IsActive logic of each entity.
Let's say I have the following:
public static Expression<Func<SchedulePackage, bool>> IsActive =>
x => x.Package.Enabled && x.Schedule.Enabled && x.Schedule.Start > DateTimeOffset.Now;
Now if I update the package logic
public static Expression<Func<Package, bool>> IsActive =>
x => x.Enabled && x.Price > 0;
I have to update SchedulePackage as well:
public static Expression<Func<SchedulePackage, bool>> IsActive =>
x => x.Package.Enabled && x.Package.Price > 0 && x.Schedule.Enabled && x.Schedule.Start > DateTimeOffset.Now;
In the context of Entity Framework (or other LINQ to Entities applications) it is generally not possible for one expression to invoke another for reasons explained in "The LINQ expression node type 'Invoke' is not supported in LINQ to Entities" - stumped!. Instead what you can do is subclass
ExpressionVisitorto create modified copies of the bodies ofSchedule.IsActiveandPackage.IsActivethat takeSchedulePackage.ScheduleandSchedulePackage.Packageas inputs, then compose them with a binary&&expression for the final result.To accomplish this, first create the following extension methods for composing functional expressions:
And now you can write
SchedulePackage.IsActiveas follows:The resulting expression looks like:
Note that the inner
IsActiveexpressions have been inlined rather than invoked.I am lazily constructing the expression once and reusing it purely for performance reasons.
Demo fiddle here.