我需要编写一个通用方法,它将为两个或多个字段返回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()
挑战在于当字段名称列表作为输入时。
这是选择不同值的实现。一个限制是,检查唯一性的属性应该是可写的(使用 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);
}
}