Apply deletes during custom seed of DbSet

Apply deletes during custom seed of DbSet<T>



I am trying to extend my custom seed routine (I know EF Core 2.1 supports seeding natively, but I have a blocker in converting) to apply deletions. If a record exists in the database, but no longer in the seed data, I want to delete it. Rather than writing a custom delete routine for each DbSet, I am trying to implement with generics (and possibly reflection if needed).


DbSet



My first attempt:


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();



However, since TEntity contains some properties that are null in the seed data (such as timestamps generated on add), I can't compare the entire entities in the Except() call (with the default equality comparer, anyway). I really only care about comparing the primary key.


TEntity


Except()



My work in progress for addressing this issue is below. TEntity could have a primary key of a simple Id column, or it could be a many-to-many mapping with a complex primary key of two <EntityName>Ids. IBaseEntity currently does not have any Id/primary key information since it is implemented both by basic entities as well as many-to-many/junction entities.


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();



full source/context



The two instances of .Select(s => idProperties), however, obviously do not work. Is there a way to select the Id properties (or, alternatively, the primary key) of a DbSet<T> to be used in the Except() comparer? I am open to a completely different approach as well, since I feel like I am off in the weeds.


.Select(s => idProperties)


Id


DbSet<T>


Except()






Maybe you can create interface like IEntityWithKey with one property that will be your id. Just inherit all entities from that interface

– Ihor
Sep 10 '18 at 17:58






@Ihor I thought about that, and I think it would work for basic entities. But, how would I make that work for many-to-many/junction entities whose primary key is a composite of two unknown <EntityName>Ids? I don't know how to make those many-to-many entities implement this proposed interface since the property names would be different.

– Collin M. Barrett
Sep 10 '18 at 18:01



<EntityName>Id






There is no need of interfaces because DbContext contains all the necessary metadata, including the PK mapping. However checking for existence by more than one property is not easy. Note that EF (Core) requires entities to be loaded in the context in order to be deleted. Your attempt loads the whole table in memory, is this acceptable?

– Ivan Stoev
Sep 10 '18 at 18:14


DbContext






@IvanStoev Thanks, that makes sense. Yes, in this case, the seed happens once on app boot and deals with relatively small datasets. So, loading into memory is acceptable.

– Collin M. Barrett
Sep 10 '18 at 18:16




1 Answer
1



EF Core metadata provides all the necessary information needed.



Instead of reflection, you can use Expression class to dynamically build criteria like this (pseudocode):


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);



which would return the matching items from the db. To get the non matching items, the criteria can simply be inverted and used as predicate for deleting. Note that for the single PK this would translate to NOT IN (...) SQL criteria.


NOT IN (...)



Putting it into action:


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();






Wow, I'm going to be staring at this for some time wrapping my head around it. But, initial testing seems to confirm that it works. I've never used the Expression class (in a static context) before. I have some reading to do. Thanks.

– Collin M. Barrett
Sep 10 '18 at 20:30




Thanks for contributing an answer to Stack Overflow!



But avoid



To learn more, see our tips on writing great answers.



Required, but never shown



Required, but never shown




By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

ャフサォクコ ケウ,コ,ワ メ,ロスョノ゙,クネ,フムカヤヲニ,エコ゚ツ ウイオン゙ケワサネォキモュキォウイノンコチ゚メヌナイゥフュ,カヒウネェ ネ,ホノケ,ムュキ ッボーミュハ,チ ツス ィ メウイマヤ,゙ウチ ヅ ロ,ォジヌェ ャヌット ェ,マャ,チナエヒネソキツテ トホヲヲミーァ

How do I collapse sections of code in Visual Studio Code for Windows?