我正在尝试制作一个通用的 Duplicate linq 扩展方法。但是我不能正确地得到表达式树。这是我试图模仿的 linq 语句。
var query = cars.AsQueryable().GroupBy(x => new { x.Color, x.Length }).Where(g => g.Count() > 1).SelectMany(p => p);
但我想这样称呼我的分机。请注意,我可以发送任意数量的属性。(颜色、长度)等等……</p>
var test = cars.AsQueryable().GetDuplicates2(new[] { "Color", "Length" });
当我试图计算匿名类型时,我陷入了 where 表达式中。groupby 表达式已经按预期工作。
请注意,我知道还有很多其他方法可以做到这一点,但是我正在尝试使用这些表达式来获得经验。所以请保持针对此的答案。
这是我当前的代码:
public static IEnumerable<TSource> GetDuplicates2<TSource>(this IQueryable<TSource> source, IEnumerable<string> fieldNames)
{
IQueryable groups = null;
try
{
Dictionary<string, PropertyInfo> sourceProperties = fieldNames.ToDictionary(name => name, name => source.ElementType.GetProperty(name));
Type dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(sourceProperties.Values);
ParameterExpression sourceItem = Expression.Parameter(typeof(TSource), "x");
IEnumerable<MemberBinding> bindings = dynamicType.GetFields().Select(p => Expression.Bind(p, Expression.Property(sourceItem, sourceProperties[p.Name]))).OfType<MemberBinding>();
Expression e1 = Expression.Lambda(Expression.MemberInit(
Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)), bindings), sourceItem);
MethodCallExpression groupByExpression = Expression.Call(typeof(Queryable), "GroupBy", new Type[] { source.ElementType, dynamicType },
Expression.Constant(source), e1);
sourceItem = Expression.Parameter(source.ElementType, "group");
Expression left = Expression.Call(sourceItem, typeof(Queryable).GetMethods().FirstOrDefault(p => p.Name == "Count"));
Expression right = Expression.Constant(0);
Expression e2 = Expression.GreaterThan(left, right);
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { typeof(TSource) },
groupByExpression,
Expression.Lambda<Func<TSource, bool>>(e2, new ParameterExpression[] { sourceItem }));
sourceItem = Expression.Parameter(typeof(TSource), "p");
MethodCallExpression selectManyCallExpression = Expression.Call(
typeof(IQueryable<TSource>),
"SelectMany",
null,
whereCallExpression,
Expression.Lambda<Func<TSource, TSource>>(sourceItem, new ParameterExpression[] { sourceItem }));
groups = source.Provider.CreateQuery(selectManyCallExpression);
}
catch (Exception ex) { }
if (groups != null)
foreach (var group in groups)
foreach (var item in @group)
yield return item;
}
public static IQueryable SelectDynamic(this IQueryable source, IEnumerable<string> fieldNames)
{
Dictionary<string, PropertyInfo> sourceProperties = fieldNames.ToDictionary(name => name, name => source.ElementType.GetProperty(name));
Type dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(sourceProperties.Values);
ParameterExpression sourceItem = Expression.Parameter(source.ElementType, "t");
IEnumerable<MemberBinding> bindings = dynamicType.GetFields().Select(p => Expression.Bind(p, Expression.Property(sourceItem, sourceProperties[p.Name]))).OfType<MemberBinding>();
Expression selector = Expression.Lambda(Expression.MemberInit(
Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)), bindings), sourceItem);
return source.Provider.CreateQuery(Expression.Call(typeof(Queryable), "Select", new Type[] { source.ElementType, dynamicType },
Expression.Constant(source), selector));
}
public static class LinqRuntimeTypeBuilder
{
private static AssemblyName assemblyName = new AssemblyName() { Name = "DynamicLinqTypes" };
private static ModuleBuilder moduleBuilder = null;
private static Dictionary<string, Type> builtTypes = new Dictionary<string, Type>();
static LinqRuntimeTypeBuilder()
{
moduleBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run).DefineDynamicModule(assemblyName.Name);
}
private static string GetTypeKey(Dictionary<string, Type> fields)
{
//TODO: optimize the type caching -- if fields are simply reordered, that doesn't mean that they're actually different types, so this needs to be smarter
string key = string.Empty;
foreach (var field in fields)
key += field.Key + ";" + field.Value.Name + ";";
return key;
}
public static Type GetDynamicType(Dictionary<string, Type> fields)
{
if (null == fields)
throw new ArgumentNullException("fields");
if (0 == fields.Count)
throw new ArgumentOutOfRangeException("fields", "fields must have at least 1 field definition");
try
{
Monitor.Enter(builtTypes);
string className = GetTypeKey(fields);
if (builtTypes.ContainsKey(className))
return builtTypes[className];
TypeBuilder typeBuilder = moduleBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Serializable);
foreach (var field in fields)
typeBuilder.DefineField(field.Key, field.Value, FieldAttributes.Public);
builtTypes[className] = typeBuilder.CreateType();
return builtTypes[className];
}
catch (Exception ex)
{
}
finally
{
Monitor.Exit(builtTypes);
}
return null;
}
private static string GetTypeKey(IEnumerable<PropertyInfo> fields)
{
return GetTypeKey(fields.ToDictionary(f => f.Name, f => f.PropertyType));
}
public static Type GetDynamicType(IEnumerable<PropertyInfo> fields)
{
return GetDynamicType(fields.ToDictionary(f => f.Name, f => f.PropertyType));
}
}
}
public class Car
{
public int Length { set; get; }
public int Width { set; get; }
public string Color { set; get; }
public string Model { set; get; }
public string Make { set; get; }
}
}