2012-05-10 6 views
1

J'essaie de créer une méthode générique d'extension Duplicate linq. Mais je ne peux pas obtenir l'arbre d'expression correctement. Voici la déclaration linq que j'essaie d'imiter.Linq to Objects Générique Duplicate Method

var query = cars.AsQueryable().GroupBy(x => new { x.Color, x.Length }).Where(g => g.Count() > 1).SelectMany(p => p); 

Mais je veux appeler mon extension comme celui-ci. Notez que je peux envoyer autant de propriétés que je veux. (Couleur, longueur) etc ...

var test = cars.AsQueryable().GetDuplicates2(new[] { "Color", "Length" }); 

Je suis coincé dans l'expression où que je suis en train d'obtenir un compte sur un type anonyme. L'expression groupby fonctionne déjà comme prévu.

S'il vous plaît noter que je sais qu'il ya beaucoup d'autres façons de le faire mais j'essaie d'acquérir de l'expérience en utilisant les expressions. alors s'il vous plaît gardez les réponses dirigées à ceci.

Voici mon code actuel:

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; } 
} 

}

Répondre

4

Tu fais ce trop compliqué. Il suffit de dire:

public static IEnumerable<TSource> GetDuplicatesByKey<TSource, TKey>(
    this IEnumerable<TSource> source, 
    Func<TSource, TKey> keySelector 
) { 
    return source.GroupBy(keySelector) 
        .Where(g => g.Skip(1).Any()) 
        .SelectMany(g => g); 
} 

Vous pouvez même avoir une surcharge qui prennent IEqualityComparer<TKey>, etc.

+3

Vous pouvez utiliser 'Sauter (1) .Tout()' 'plutôt que Count()> 1' pour éviter l'énumération le groupe entier juste pour voir s'il a plus d'un élément. – Servy

+0

@Servy: Bon appel. – jason

+0

Parfait MERCI! – retslig