Home > Enterprise >  Domain Logic vs. Linq-to-Entites and Expression Trees
Domain Logic vs. Linq-to-Entites and Expression Trees

Time:09-17

Lets say there's a simple class, something like:

internal sealed class Foo
{
    public Foo(DateTimeOffset start)
    {
        Start = start;
    }

    public DateTimeOffset Start { get; }
}

In the core domain, we want a single "definition" of what it means to having a "started" Foo. Hence we do something like add a property like public bool IsStarted => Start < DateTimeOffset.UtcNow to the Foo class.

However, Foo also happens to represent an "entity" in a database, meaning we expect to be able to retrieve lists of Foo using Linq-to-Entities (i.e. Entity Framework).

(I know we may not necessarily be happy with the idea of sharing classes between the core domain and data access layer, but that's beside the point of this question.)

When fetching only those Foo that "has started", we could go:

var entities = _someDatabaseContext.Foos.Where(foo => foo.Start < DateTimeOffset.UtcNow);

But that would obviously mean duplicating logic, and the "definition" of what it means to be "a started Foo" would be singular no more.

We would rather like to be able to do:

var entities = _someDatabaseContext.Foos.Where(foo => foo.IsStarted);

This, alas, would not translate with Linq-to-Entities, which as of yet has no notion of the "is started" concept.

The question: What would be a good way to encapsulate the definition of "started" for Foo, in a manner the makes it readily available both in the application domain and at the level of querying the database?

CodePudding user response:

Define an expression to represent the test. Use this expression with Linq-to-Entities, use the compiled delegate for execution.

Expression<Func<Foo, bool>> FooIsStartedPredicateExpression = foo => foo.Start < DateTimeOffset.UtcNow;
Func<Foo, bool> FooIsStartedPredicateDelegate = FooIsStartedPredicateExpression.Compile();

...

Foo someFooVar = ...;
var isStarted = FooIsStartedPredicateDelegate(someFooVar);
var foos = _someDatabaseContext.Foos.Where(FooIsStartedPredicateExpression);

CodePudding user response:

I would suggest to use LINQKit for such task:

builder
    .UseSqlServer(connectionString)
    .WithExpressionExpanding(); // enabling LINQKit extension

Define your proeprty:

internal sealed class Foo
{
    public Foo(DateTimeOffset start)
    {
        Start = start;
    }

    public DateTimeOffset Start { get; }

    [Expandable(name(IsStartedImpl))]
    public bool IsStarted { get; } = Start < DateTimeOffset.UtcNow;

    private static Expression<Func<Foo, bool>> IsStartedImpl()
        => foo => foo.Start < DateTimeOffset.UtcNow;
}

Then you can use this property as filter:

var entities = _someDatabaseContext.Foos.Where(foo => foo.IsStarted);

CodePudding user response:

Maybe try carrying started logic as a predicate and call it from both IsStarted public interface and pass it to the Linq code?

internal sealed class Foo
{
   public Foo(DateTimeOffset start)
   {
      Start = start;
   }

   public DateTimeOffset Start { get; }

   public bool IsStarted()
   {
      return _startedPredicate(this);
   }

   private readonly Func<Foo, bool> _startedPredicate = 
                     foo => foo.Start < DateTimeOffset.UtcNow;
}
   

And when using Linq var entities = _someDatabaseContext.Foos.Where(_startedPredicate);

Depending on where you are accessing the DB the predicate may need to be accessible outside Foo.

  • Related