EF Core 6:从列表中选择不同字段的通用方法

问题描述 投票:0回答:1

我需要编写一个通用方法,它将为两个或多个字段返回distinct值,由用户在

List<string>
中给出。

在一些帮助下,我有以下内容,它给出了“Linq cannot be translated”错误。

public static Expression<Func<TSource, IDictionary<string, object>>> BuildSelectExpression<TSource>(List<string> fieldNames)
{
    var sourceType = typeof(TSource);
    var sourceParameter = Expression.Parameter(sourceType, "x");
    var dictionaryConstructor = typeof(Dictionary<string, object>).GetConstructor(Type.EmptyTypes);
    var dictionaryAddMethod = typeof(Dictionary<string, object>).GetMethod("Add");

    var elements = fieldNames.Select(fieldName =>
    {
        var property = sourceType.GetProperty(fieldName);
        if (property == null)
        {
            throw new ArgumentException($"Property '{fieldName}' does not exist on type '{sourceType}'.");
        }

        var key = Expression.Constant(fieldName);
        var value = Expression.Convert(Expression.Property(sourceParameter, property), typeof(object));
        var addMethodCall = Expression.Call(Expression.Variable(typeof(Dictionary<string, object>)), dictionaryAddMethod, key, value);
        return addMethodCall;
    }).ToArray();

    var body = Expression.Block(new[] { Expression.Variable(typeof(Dictionary<string, object>)) },
        Expression.Assign(Expression.Variable(typeof(Dictionary<string, object>)), Expression.New(dictionaryConstructor)),
        Expression.Block(elements),
        Expression.Variable(typeof(Dictionary<string, object>))
    );

    return Expression.Lambda<Func<TSource, IDictionary<string, object>>>(body, sourceParameter);
}
var selectExpression = BuildSelectExpression<MyEntityType>(fieldNames);

var distinctRecords = await _context.MyEntityDbSet
                     .Select(selectExpression)
                     .Distinct(new DictionaryComparer<string, object>())
                     .ToListAsync(cancellationToken);

DictionacyComparer
方法:

public class DictionaryComparer<TKey, TValue> : IEqualityComparer<IDictionary<TKey, TValue>>
{
    public bool Equals(IDictionary<TKey, TValue> x, IDictionary<TKey, TValue> y)
    {
        if (x == y) return true;
        if (x == null || y == null) return false;
        if (x.Count != y.Count) return false;

        return x.Keys.All(key => y.TryGetValue(key, out var value) && EqualityComparer<TValue>.Default.Equals(x[key], value));
    }

    public int GetHashCode(IDictionary<TKey, TValue> obj)
    {
        unchecked
        {
            int hash = 17;
            foreach (var entry in obj.OrderBy(e => e.Key.GetHashCode()))
            {
                hash = hash * 23 + entry.Key.GetHashCode();
                hash = hash * 23 + (entry.Value?.GetHashCode() ?? 0);
            }
            return hash;
        }
    }
}

编辑:

当我知道字段时,比方说“customer_id”,“customer_name”我可以简单地写:

dbSet
   .Select(x=> new {x.customer_id, x.customer_name})
   .Distinct()
   .ToListAsync()

挑战在于当字段名称列表作为输入时。

c# entity-framework-core .net-6.0 expression-trees ef-core-6.0
1个回答
0
投票

这是选择不同值的实现。一个限制是,检查唯一性的属性应该是可写的(使用 setter)。

用法很简单:

var result = await dbSet
    .SelectDistinctValues(new List<string> { "customer_id", "customer_name" })
    .ToListAsync();

作为奖励,还支持复杂的属性:

var result = await dbSet
    .SelectDistinctValues(new List<string> { "customer.id", "customer.name" })
    .ToListAsync();

和实施:

public static class QueryableExtensions
{
    static readonly ConstructorInfo _dictionaryConstructor = typeof(Dictionary<string, object>).GetConstructor(Type.EmptyTypes) ?? throw new InvalidOperationException();
    static readonly MethodInfo _dictionaryAddMethodInfo = typeof(Dictionary<string, object>).GetMethod("Add") ?? throw new InvalidOperationException();

    public static IQueryable<IDictionary<string, object>> SelectDistinctValues<TSource>(this IQueryable<TSource> source,
        List<string> fieldNames)
    {
        var fields = fieldNames.Select(f => f.Split('.')).ToList();

        var param = Expression.Parameter(typeof(TSource), "e");

        var selectorBody = BuildSelectExpression(param, fields, 0);
        var selector = Expression.Lambda<Func<TSource, TSource>>(selectorBody, param);

        var dictionarySelectorBody = BuildDictionarySelectExpression(param, fields);
        var dictionarySelector = Expression.Lambda<Func<TSource, IDictionary<string, object>>>(dictionarySelectorBody, param);

        var query = source
            .Select(selector)
            .Distinct()
            .Select(dictionarySelector);

        return query;
    }


    static Expression BuildDictionarySelectExpression(Expression objParam, List<string[]> fieldNames)
    {
        var body = (Expression)Expression.ListInit(
            Expression.New(_dictionaryConstructor),
            fieldNames.Select(propName =>
                Expression.ElementInit(_dictionaryAddMethodInfo, Expression.Constant(string.Join(".", propName)),
                    Expression.Convert(MakePropPath(objParam, propName), typeof(object)))
        ));

        return body;
    }


    static Expression BuildSelectExpression(Expression objPath, List<string[]> fieldNames, int level)
    {
        if (fieldNames.Count == 0)
            throw new InvalidOperationException();

        var objType = objPath.Type;

        var grouped = fieldNames.GroupBy(path => path[level])
            .Select(x => new
            {
                PropInfo = objType.GetProperty(x.Key) ?? throw new InvalidOperationException(),
                NexPath = x.Where(x => x.Length > level + 1).ToList()
            }).OrderBy(x => x.NexPath.Count > 0);

        var assignments = grouped.Select(x =>
        {
            var access = Expression.MakeMemberAccess(objPath, x.PropInfo);
            return Expression.Bind(x.PropInfo,
                x.NexPath.Count == 0
                    ? access
                    : BuildSelectExpression(access, x.NexPath, level + 1));
        });

        var newExpr = Expression.MemberInit(
            Expression.New(objType),
            assignments
        );

        return newExpr;
    }       

    static Expression MakePropPath(Expression objExpression, string[] path)
    {
        return path.Aggregate(objExpression, Expression.PropertyOrField);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.