域逻辑与 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 外部访问谓词。
假设有一个简单的 class,例如:
internal sealed class Foo
{
public Foo(DateTimeOffset start)
{
Start = start;
}
public DateTimeOffset Start { get; }
}
在核心领域,我们想要一个单一的“定义”来定义“开始”的含义Foo
。因此我们做一些事情,比如在 Foo
class.
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 外部访问谓词。