Generic method for updating EFCore joins

丶灬走出姿态 提交于 2019-12-03 08:56:50

"All very simple stuff", but not so simple to factorize, especially taking into account different key types, explicit or shadow FK properties etc., at the same time keeping the minimum method arguments.

Here is the best factorized method I can think of, which works for link (join) entities having 2 explicit int FKs:

public static void UpdateLinks<TLink>(this DbSet<TLink> dbSet, 
    Expression<Func<TLink, int>> fromIdProperty, int fromId, 
    Expression<Func<TLink, int>> toIdProperty, int[] toIds)
    where TLink : class, new()
{
    // link => link.FromId == fromId
    var filter = Expression.Lambda<Func<TLink, bool>>(
        Expression.Equal(fromIdProperty.Body, Expression.Constant(fromId)),
        fromIdProperty.Parameters);
    var existingLinks = dbSet.Where(filter).ToList();

    var toIdFunc = toIdProperty.Compile();
    var deleteLinks = existingLinks
        .Where(link => !toIds.Contains(toIdFunc(link)));

    // toId => new TLink { FromId = fromId, ToId = toId }
    var toIdParam = Expression.Parameter(typeof(int), "toId");
    var createLink = Expression.Lambda<Func<int, TLink>>(
        Expression.MemberInit(
            Expression.New(typeof(TLink)),
            Expression.Bind(((MemberExpression)fromIdProperty.Body).Member, Expression.Constant(fromId)),
            Expression.Bind(((MemberExpression)toIdProperty.Body).Member, toIdParam)),
        toIdParam);
    var addLinks = toIds
        .Where(toId => !existingLinks.Any(link => toIdFunc(link) == toId))
        .Select(createLink.Compile());

    dbSet.RemoveRange(deleteLinks);
    dbSet.AddRange(addLinks);
}

All it needs is the join entity DbSet, two expressions representing the FK properties, and the desired values. The property selector expressions are used to dynamically build query filters as well as composing and compiling a functor to create and initialize new link entity.

The code is not that hard, but requires System.Linq.Expressions.Expression methods knowledge.

The only difference with handwritten code is that

Expression.Constant(fromId)

inside filter expression will cause EF generating a SQL query with constant value rather than parameter, which will prevent query plan caching. It can be fixed by replacing the above with

Expression.Property(Expression.Constant(new { fromId }), "fromId")

With that being said, the usage with your sample would be like this:

public static void UpdateCars(int personId, int[] carIds)
{
    using (var db = new PersonCarDbContext())
    {
        db.PersonCars.UpdateLinks(pc => pc.PersonId, personId, pc => pc.CarId, carIds);
        db.SaveChanges();
    }
}

and also other way around:

public static void UpdatePersons(int carId, int[] personIds)
{
    using (var db = new PersonCarDbContext())
    {
        db.PersonCars.UpdateLinks(pc => pc.CarId, carId, pc => pc.PersonId, personIds);
        db.SaveChanges();
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!