【发布时间】:2016-01-24 01:58:08
【问题描述】:
基本上,我想实现一个存储库,即使通过导航属性也可以过滤所有软删除记录。所以我有一个基本实体,类似这样:
public abstract class Entity
{
public int Id { get; set; }
public bool IsDeleted { get; set; }
...
}
还有一个存储库:
public class BaseStore<TEntity> : IStore<TEntity> where TEntity : Entity
{
protected readonly ApplicationDbContext db;
public IQueryable<TEntity> GetAll()
{
return db.Set<TEntity>().Where(e => !e.IsDeleted)
.InterceptWith(new InjectConditionVisitor<Entity>(entity => !entity.IsDeleted));
}
public IQueryable<TEntity> GetAll(Expression<Func<TEntity, bool>> predicate)
{
return GetAll().Where(predicate);
}
public IQueryable<TEntity> GetAllWithDeleted()
{
return db.Set<TEntity>();
}
...
}
InterceptWith 函数来自以下项目:https://github.com/davidfowl/QueryInterceptor 和 https://github.com/StefH/QueryInterceptor(与异步实现相同)
IStore<Project> 的用法如下:
var project = await ProjectStore.GetAll()
.Include(p => p.Versions).SingleOrDefaultAsync(p => p.Id == projectId);
我实现了一个 ExpressionVisitor:
internal class InjectConditionVisitor<T> : ExpressionVisitor
{
private Expression<Func<T, bool>> queryCondition;
public InjectConditionVisitor(Expression<Func<T, bool>> condition)
{
queryCondition = condition;
}
public override Expression Visit(Expression node)
{
return base.Visit(node);
}
}
但这就是我被卡住的地方。我在 Visit 函数中设置了一个断点,以查看我得到了什么表达式,以及我什么时候应该做一些厚颜无耻的事情,但它永远不会到达我树的 Include(p => p.Versions) 部分。
我看到了一些其他可能有效的解决方案,但这些解决方案是“永久的”,例如 EntityFramework.Filters 似乎适用于大多数用例,但在配置 DbContext 时必须添加过滤器 - 但是,您可以禁用过滤器,但我不想为每个查询禁用和重新启用过滤器。像这样的另一个解决方案是订阅 ObjectContext 的 ObjectMaterialized 事件,但我也不喜欢它。
我的目标是“捕获”访问者中的包含并修改表达式树以向连接添加另一个条件,该条件仅在您使用商店的 GetAll 函数之一时检查记录的 IsDeleted 字段。任何帮助将不胜感激!
更新
我的存储库的目的是隐藏基本实体的一些基本行为 - 它还包含“created/lastmodified by”、“created/lastmodified-date”、时间戳等。我的 BLL 通过这个存储库获取所有数据所以不需要担心这些,商店会处理所有事情。也有可能从 BaseStore 继承特定类(然后我配置的 DI 会将继承的类注入到 IStore<Project> 如果存在),您可以在其中添加特定行为。比如你修改了一个项目,你需要把这些修改历史添加进去,那么你把这个添加到继承存储的更新函数中就可以了。
当您查询具有导航属性的类(因此任何类 :D )时,问题就开始了。有两个具体实体:
public class Project : Entity
{
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Platform> Platforms { get; set; }
//note: this version is not historical data, just the versions of the project, like: 1.0.0, 1.4.2, 2.1.0, etc.
public virtual ICollection<ProjectVersion> Versions { get; set; }
}
public class Platform : Entity
{
public string Name { get; set; }
public virtual ICollection<Project> Projects { get; set; }
public virtual ICollection<TestFunction> TestFunctions { get; set; }
}
public class ProjectVersion : Entity
{
public string Code { get; set; }
public virtual Project Project { get; set; }
}
所以如果我想列出项目的版本,我打电话给商店:await ProjectStore.GetAll().Include(p => p.Versions).SingleOrDefaultAsync(p => p.Id == projectId)。我不会删除项目,但如果项目存在,它将返回所有与之相关的版本,甚至是已删除的版本。在这种特定情况下,我可以从另一边开始并调用 ProjectVersionStore,但如果我想通过 2+ 导航属性进行查询,那么游戏就结束了:)
预期的行为是:如果我将版本包含到项目中,它应该只查询未删除的版本 - 所以生成的 sql 连接也应该包含 [Versions].[IsDeleted] = FALSE 条件。像Include(project => project.Platforms.Select(platform => platform.TestFunctions)) 这样的复杂包含更加复杂。
我尝试这样做的原因是我不想将 BLL 中的所有 Include 重构为其他内容。那是懒惰的部分:)另一个是我想要一个透明的解决方案,我不希望 BLL 知道所有这些。如果不是绝对必要,界面应该保持不变。我知道这只是一个扩展方法,但是这个行为应该在 store 层。
【问题讨论】:
标签: c# .net linq entity-framework-6 expression-trees