鉴于下面 trailmax 的评论,是的,下面的原始答案确实将所有数据加载到内存中,因为它暴露了链式枚举。
尝试将IQueryable 调用链接在一起时会出现两个问题。
- EF 不知道如何翻译生成的
.Invoke() 调用
- 如果您尝试维护像
.Where() 这样的命名约定,您最终会选择错误的扩展名,因为 IDeletable 只公开了一个可以在其上构建谓词的列。
以下依赖于 LinqKit 库来链接表达式,语法不再流畅,但它确实避免了任何立即执行。
var users = context.Users.WhereIsNotDeleted(x => x.Id > 0).ToList();
public static class Extension
{
public static IEnumerable<T> WhereIsNotDeleted<T>(this IQueryable<T> source,
Expression<Func<T, bool>> predicate) where T : IDeletable
{
var query = source.AsExpandable().Where(x => !x.IsDeleted);
return query.Where(predicate);
}
}
假设IDeletable 保证IsDeleted 存在,您可以创建一个始终为您执行此检查的扩展方法。
public interface IDeletable
{
bool IsDeleted { get; set; }
}
public static class Extension
{
public static IEnumerable<T> WhereNotDeleted<T>(this IEnumerable<T> source,
Func<T, bool> predicate) where T : IDeletable
{
return source.Where(x => !x.IsDeleted).Where(predicate);
}
}
在下面的简单测试中,仅返回两条记录,因为第三条记录已被软删除。
void Main()
{
var x = new List<Test>();
x.Add(new Test{ Number = "one" });
x.Add(new Test{ Number = "two" });
x.Add(new Test{ Number = "three", IsDeleted = true });
var y = x.WhereNotDeleted(a => a != null);
y.Count().Dump();
}
public class Test : IDeletable
{
public string Number { get; set; }
public bool IsDeleted { get; set; }
}
根据您的评论,如果您希望它与 EXISTING LINQ 扩展方法一起使用,您实际上无法做到,因为您会遇到调用歧义。
如果您想调用 .Where() 并将其转换为您的条件,仅为实现 IDeletable 的对象,您需要包装每个 LINQ 扩展方法。
public static class Extension
{
public static IEnumerable<IDeletable> Where(this IEnumerable<IDeletable> source,
Func<IDeletable, bool> predicate)
{
return System.Linq.Enumerable.Where(source, (x => predicate(x) && !x.IsDeleted));
}
}
在这些扩展方法中,您必须通过实例调用而不是作为扩展方法调用基本 LINQ 方法,否则您将遇到堆栈溢出异常。