【发布时间】:2013-09-08 13:20:41
【问题描述】:
对于未来的访问者:对于 EF6,您可能最好使用过滤器,例如通过这个项目:https://github.com/jbogard/EntityFramework.Filters
在我们正在构建的应用程序中,我们应用“软删除”模式,其中每个类都有一个“已删除”布尔值。实际上,每个类都简单地继承自这个基类:
public abstract class Entity
{
public virtual int Id { get; set; }
public virtual bool Deleted { get; set; }
}
举个简单的例子,假设我有 GymMember 和 Workout 类:
public class GymMember: Entity
{
public string Name { get; set; }
public virtual ICollection<Workout> Workouts { get; set; }
}
public class Workout: Entity
{
public virtual DateTime Date { get; set; }
}
当我从数据库中获取健身房成员列表时,我可以确保没有获取任何“已删除”的健身房成员,如下所示:
var gymMembers = context.GymMembers.Where(g => !g.Deleted);
但是,当我遍历这些健身房成员时,他们的 Workouts 是从数据库中加载的,而不考虑他们的 Deleted 标志。虽然我不能责怪 Entity Framework 没有注意到这一点,但我想以某种方式配置或拦截延迟属性加载,以便永远不会加载已删除的导航属性。
我一直在考虑我的选择,但它们似乎很少:
-
Going to
Database Firstand use conditional mapping for every object for every one-to-many property。
这根本不是一种选择,因为这将是太多的手动工作。 (我们的应用程序很大,而且每天都在变大)。我们也不想放弃使用 Code First 的优势(其中有很多)
同样,不是一个选项。此配置仅适用于每个实体。总是急切地加载实体也会造成严重的性能损失。
我实际上在概念验证应用程序中对此进行了测试,效果非常好。
这是一个非常有趣的选项,但是很遗憾,它无法将过滤应用于延迟加载的导航属性。这很明显,因为那些惰性属性不会出现在表达式/查询中,因此无法替换。我想知道 Entity Framework 是否允许在他们的 DynamicProxy 类中的某处有一个注入点来加载惰性属性。
我还担心其他后果,例如破坏 EF 中的Include 机制的可能性。
- 编写一个自定义类来实现 ICollection 但会自动过滤
Deleted实体。
这实际上是我的第一个方法。这个想法是为每个内部使用自定义 Collection 类的集合属性使用一个支持属性:
public class GymMember: Entity
{
public string Name { get; set; }
private ICollection<Workout> _workouts;
public virtual ICollection<Workout> Workouts
{
get { return _workouts ?? (_workouts = new CustomCollection()); }
set { _workouts = new CustomCollection(value); }
}
}
虽然这种方法实际上还不错,但我仍然有一些问题:
它仍然将所有
Workouts 加载到内存中,并在属性设置器被命中时过滤Deleteds。在我看来,这太迟了。执行的查询与加载的数据之间存在逻辑不匹配。
想象一个场景,我想要一份自上周以来进行锻炼的健身房成员的列表:
var gymMembers = context.GymMembers.Where(g => g.Workouts.Any(w => w.Date >= DateTime.Now.AddDays(-7).Date));
此查询可能会返回一个健身房成员,该成员仅具有已删除但同时满足谓词的锻炼。一旦它们被加载到内存中,就好像这个健身房成员根本没有锻炼!
您可以说开发人员应该知道Deleted 并始终将其包含在他的查询中,但这是我真正想避免的事情。或许 ExpressionVisitor 可以再次在这里提供答案。
- 在使用CustomCollection 时,实际上不可能将导航属性标记为
Deleted。
想象一下这个场景:
var gymMember = context.GymMembers.First();
gymMember.Workouts.First().Deleted = true;
context.SaveChanges();`
您会期望数据库中更新了相应的Workout 记录,但您错了!由于ChangeTracker 正在检查gymMember 是否有任何更改,属性gymMember.Workouts 将突然返回少1 个锻炼。那是因为 CustomCollection 会自动过滤已删除的实例,记得吗?所以现在Entity Framework认为需要删除锻炼,EF会尝试将FK设置为null,或者实际删除记录。 (取决于您的数据库的配置方式)。这就是我们一开始就试图避免的软删除模式!!!
我偶然发现了一个interesting blog 帖子,它覆盖了DbContext 的默认SaveChanges 方法,因此任何带有EntityState.Deleted 的条目都改回EntityState.Modified,但这又让人感觉“hacky”而且相当不安全.但是,如果它解决了没有任何意外副作用的问题,我愿意尝试一下。
所以我在这里是 StackOverflow。如果我自己可以这么说的话,我已经非常广泛地研究了我的选择,而且我已经束手无策了。所以现在我转向你。您是如何在企业应用程序中实现软删除的?
重申一下,这些是我正在寻找的要求:
- 查询应自动排除数据库级别的
Deleted实体 - 删除实体并调用“SaveChanges”应该只是更新相应的记录并且没有其他副作用。
- 加载导航属性时,无论是惰性还是急切,
Deleted都应自动排除。
我期待任何和所有的建议,提前谢谢你。
【问题讨论】:
-
您是否尝试过在初始加载时调用 .ToList() 以强制 EF 加载到内存中?
-
恐怕你的评论没有多大意义。您真的在不到 1 分钟的时间内阅读了我的整个问题吗?这令人印象深刻!
-
感谢有人试图帮助你时的讽刺。
-
抱歉,我不是故意冒犯你的。在健身房成员上调用 ToList 确实会将健身房成员加载到内存中,但我真的不明白这将如何解决我的问题?
-
我认为你需要回归基础。您所解释的不是我对 EF 的经验。 “但是,当我遍历这些健身房成员时,他们的锻炼是从数据库中加载的,而不考虑他们的已删除标志”……这里有问题。它不得包括这些记录。检查你的谓词。 2. 在您的上下文中禁用延迟加载。完成排序后,在 for 循环中显式加载每个健身房成员的锻炼。使用他 IQueryable 返回类型。
标签: c# ef-code-first entity-framework-5 soft-delete