我正在尝试扩展自定义种子例程(我知道EF Core 2.1本机支持种子,但是在转换时有blocker)以应用删除。如果数据库中存在一条记录,但种子数据中不再存在一条记录,我想删除它。我没有为每个DbSet
编写自定义删除例程,而是尝试使用泛型来实现(如果需要,还可以进行反射)。
我的第一次尝试:
private static void Delete<TEntity>(DbContext dbContext, IEnumerable<TEntity> seedRows) where TEntity : class, IBaseEntity
{
var toRemove = dbContext.Set<TEntity>().Except(seedRows);
dbContext.RemoveRange(toRemove);
dbContext.SaveChanges();
}
但是,由于TEntity
包含一些在种子数据中为空的属性(例如,在添加时生成的时间戳),因此我无法比较Except()
调用中的整个实体(使用默认值平等比较器)。我真的只在乎比较主键。
我正在解决此问题的工作如下。 TEntity
可以具有一个简单的Id
列的主键,也可以是具有两个<EntityName>Id
的复杂主键的多对多映射。 IBaseEntity
目前没有任何Id
/主键信息,因为它是由基本实体以及多对多/交接实体实现的。
private static void Delete<TEntity>(DbContext dbContext, IEnumerable<TEntity> seedRows) where TEntity : class, IBaseEntity
{
var idProperties = typeof(TEntity).GetProperties().Where(p => p.Name.Contains("Id"));
var toRemove = dbContext.Set<TEntity>().Select(s => idProperties).Except(seedRows.Select(s => idProperties));
dbContext.RemoveRange(toRemove);
dbContext.SaveChanges();
}
.Select(s => idProperties)
的两个实例显然不起作用。有没有办法选择要在Id
比较器中使用的DbSet<T>
的{{1}}属性(或主键)?我也对完全不同的方法持开放态度,因为我感觉自己像杂草丛生。
答案 0 :(得分:1)
EF Core元数据提供所需的所有必要信息。
您可以使用Expression
类来动态构建类似这样的标准(伪代码):
(seedRows1.Key1 == e.Key1 && seedRows1.Key2 == e.Key2 ... && seeedRows1.KeyM == e.KeyM)
||
(seedRows2.Key1 == e.Key1 && seedRows2.Key2 == e.Key2 ... && seeedRows2.KeyM == e.KeyM)
...
||
(seedRowsN.Key1 == e.Key1 && seedRowsN.Key2 == e.Key2 ... && seeedRowsN.KeyM == e.KeyM);
,它将从数据库返回匹配项。要获得不匹配的项目,可以将条件简单地反转并用作删除的谓词。请注意,对于单个PK,这将转换为NOT IN (...)
SQL标准。
将其付诸实践:
private static void Delete<TEntity>(DbContext dbContext, IEnumerable<TEntity> seedRows)
where TEntity : class//, IBaseEntity
{
var entityType = dbContext.Model.FindEntityType(typeof(TEntity));
var entityPK = entityType.FindPrimaryKey();
var dbEntity = Expression.Parameter(entityType.ClrType, "e");
Expression matchAny = null;
foreach (var entity in seedRows)
{
var match = entityPK.Properties
.Select(p => Expression.Equal(
Expression.Property(dbEntity, p.PropertyInfo),
Expression.Property(Expression.Constant(entity), p.PropertyInfo)))
.Aggregate(Expression.AndAlso);
matchAny = matchAny != null ? Expression.OrElse(matchAny, match) : match;
}
var dbQuery = dbContext.Set<TEntity>().AsQueryable();
if (matchAny != null)
{
var predicate = Expression.Lambda<Func<TEntity, bool>>(Expression.Not(matchAny), dbEntity);
dbQuery = dbQuery.Where(predicate);
}
var dbEntities = dbQuery.ToList();
if (dbEntities.Count == 0) return;
dbContext.RemoveRange(dbEntities);
dbContext.SaveChanges();
}