在我的应用程序中使用LINQ2DB时,我尝试使用使用Expression<Func<Entity, DTO>>
的实体- dto映射,反之亦然,如下所述:https://github.com/linq2db/linq2db/issues/1283#issuecomment-413509043
这适用于使用选择投影,但是当我需要更新/插入新记录时,我该怎么办?我已经浏览了更新和设置扩展方法,但找不到任何内容。
我试图实现的基本上是实体类和DTO之间基于表达式的双向映射,有点像AutoMapper对EF的投影,但手动编写每个DTO,以两个表达式的形式进行双向转换。
遗憾的是,我不是表达式树和LINQ到SQL转换方面的专家,所以如果有人建议这样做,我会很感激:
Expression<Func<SomeDTO, SomeEntityTable>> projectExpr =
x => new SomeEntity
{
ID = x.ID,
Name = x.Name,
// ...
}; // this is just so that I can write two mapping expressions per DTO and don't ever repeat them, for stuff like CRUD
// ...
using var db = ConnectionFactory.Instance.GetMainDB();
await db.SomeEntityTable
.Where(e => e.ID == dto.ID)
.Set(dto, projectExpr) // dto is of SomeDTO type here; this will set ONLY the values that are written in the expression
.Set(e => e.LastEditedAt, DateTime.Now()) // able to append some more stuff after
.UpdateAsync();
// similar for insert operation, using the same expression
这些扩展方法应该提供所需的映射:
using var db = ConnectionFactory.Instance.GetMainDB();
await db.SomeEntityTable
.Where(e => e.ID == dto.ID)
.AsUpdatable()
.Set(dto, projectExpr) // new extension method
.Set(e => e.LastEditedAt, DateTime.Now())
.UpdateAsync();
await db.SomeEntityTable
.AsValueInsertable()
.Values(dto, projectExpr) // new extension method
.Value(e => e.LastEditedAt, DateTime.Now())
.InsertAsync();
和实现:
public static class InsertUpdateExtensions
{
private static MethodInfo _withIUpdatable = Methods.LinqToDB.Update.SetUpdatableExpression;
private static MethodInfo _withIValueInsertable = Methods.LinqToDB.Insert.VI.ValueExpression;
public static IUpdatable<TEntity> Set<TEntity, TDto>(
this IUpdatable<TEntity> updatable,
TDto obj,
Expression<Func<TDto, TEntity>> projection)
{
var body = projection.GetBody(Expression.Constant(obj));
var entityParam = Expression.Parameter(typeof(TEntity), "e");
var pairs = EnumeratePairs(body, entityParam);
foreach (var pair in pairs)
{
updatable = (IUpdatable<TEntity>)_withIUpdatable.MakeGenericMethod(typeof(TEntity), pair.Item1.Type)
.Invoke(null,
new object?[]
{
updatable,
Expression.Lambda(pair.Item1, entityParam),
Expression.Lambda(pair.Item2)
})!;
}
return updatable;
}
public static IValueInsertable<TEntity> Values<TEntity, TDto>(
this IValueInsertable<TEntity> insertable,
TDto obj,
Expression<Func<TDto, TEntity>> projection)
{
var body = projection.GetBody(Expression.Constant(obj));
var entityParam = Expression.Parameter(typeof(TEntity), "e");
var pairs = EnumeratePairs(body, entityParam);
foreach (var pair in pairs)
{
insertable = (IValueInsertable<TEntity>)_withIValueInsertable.MakeGenericMethod(typeof(TEntity), pair.Item1.Type)
.Invoke(null,
new object?[]
{
insertable,
Expression.Lambda(pair.Item1, entityParam),
Expression.Lambda(pair.Item2)
})!;
}
return insertable;
}
private static IEnumerable<Tuple<Expression, Expression>> EnumeratePairs(Expression projection, Expression entityPath)
{
switch (projection.NodeType)
{
case ExpressionType.MemberInit:
{
var mi = (MemberInitExpression)projection;
foreach (var b in mi.Bindings)
{
if (b.BindingType == MemberBindingType.Assignment)
{
var assignment = (MemberAssignment)b;
foreach (var p in EnumeratePairs(Expression.MakeMemberAccess(entityPath, assignment.Member),
assignment.Expression))
{
yield return p;
}
}
}
break;
}
case ExpressionType.New:
{
var ne = (NewExpression)projection;
if (ne.Members != null)
{
for (var index = 0; index < ne.Arguments.Count; index++)
{
var expr = ne.Arguments[index];
var member = ne.Members[index];
foreach (var p in EnumeratePairs(Expression.MakeMemberAccess(entityPath, member), expr))
{
yield return p;
}
}
}
break;
}
case ExpressionType.MemberAccess:
{
yield return Tuple.Create(projection, entityPath);
break;
}
default:
throw new NotImplementedException();
}
}
}