域逻辑与 Linq-to-Entities 和表达式树

Domain Logic vs. Linq-to-Entites and Expression Trees

假设有一个简单的 class,例如:

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

    public DateTimeOffset Start { get; }
}

在核心领域,我们想要一个单一的“定义”来定义“开始”的含义Foo。因此我们做一些事情,比如在 Foo class.

中添加一个 属性 like public bool IsStarted => Start < DateTimeOffset.UtcNow

然而,Foo 也恰好表示数据库中的“实体”,这意味着我们希望能够使用 Linq-to-Entities 检索 Foo 的列表(即 Entity Framework).

(我知道我们可能不一定对在核心域和数据访问层之间共享 classes 的想法感到满意,但这不是这个问题的重点。)

当只获取那些“已经开始”的 Foo 时,我们可以去:

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

但这显然意味着重复逻辑,“a started Foo”的“定义”将不再是单一的。

我们希望能够做到:

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

唉,这不会用 Linq-to-Entities 翻译,Linq-to-Entities 目前还没有“已启动”概念的概念。

问题:什么是封装 Foo 的“启动”定义的好方法,使其在应用程序域 在查询数据库级别?

定义一个表达式来表示测试。将此表达式与 Linq-to-Entities 一起使用,使用已编译的委托执行。

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);

我建议使用 LINQKit 来完成这样的任务:

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

定义您的属性:

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;
}

然后你可以使用这个属性作为过滤器:

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

也许尝试将启动逻辑作为谓词并从 IsStarted public 接口调用它并将其传递给 Linq 代码?

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;
}
   

并且在使用 Linq 时 var entities = _someDatabaseContext.Foos.Where(_startedPredicate);

根据您访问数据库的位置,可能需要在 Foo 外部访问谓词。