Cómo usar LINQ Distinct () con múltiples campos

Tengo la siguiente clase EF derivada de una base de datos (simplificada)

class Product { public string ProductId; public string ProductName; public string CategoryId; public string CategoryName; } 

ProductId es la clave principal de la tabla.

Para una mala decisión de diseño realizada por el diseñador de bases de datos (no puedo modificarlo), tengo CategoryId y CategoryName en esta tabla.

Necesito una DropDownList con (distinct) CategoryId como Value y CategoryName como texto . Por lo tanto, apliqué el siguiente código:

 product.Select(m => new {m.CategoryId, m.CategoryName}).Distinct(); 

que lógicamente debería crear un objeto anónimo con CategoryId y CategoryName como propiedades. Distinct() garantiza que no hay pares duplicados ( CategoryId , CategoryName ).

Pero en realidad no funciona. Por lo que yo entendí, Distinct() funciona justo cuando solo hay un campo en la colección; de lo contrario, simplemente los ignora … ¿es correcto? ¿Hay algún trabajo alrededor? ¡Gracias!

ACTUALIZAR

Lo siento product es:

 List product = new List(); 

Encontré una forma alternativa de obtener el mismo resultado que Distinct() :

 product.GroupBy(d => new {d.CategoryId, d.CategoryName}) .Select(m => new {m.Key.CategoryId, m.Key.CategoryName}) 

Supongo que usa distinto como una llamada a método en una lista. ToList utilizar el resultado de la consulta como fuente de datos para su DropDownList, por ejemplo materializándolo a través de ToList .

 var distinctCategories = product .Select(m => new {m.CategoryId, m.CategoryName}) .Distinct() .ToList(); DropDownList1.DataSource = distinctCategories; DropDownList1.DataTextField = "CategoryName"; DropDownList1.DataValueField = "CategoryId"; 

Distinct () garantiza que no hay pares duplicados (CategoryId, CategoryName).

– Exactamente eso

Los tipos anónimos ‘mágicamente’ implementan Equals y GetHashcode

Supongo que hay otro error en alguna parte. Sensibilidad de la caja? ¿Clases mutables? Campos no comparables?

El método distinto devuelve elementos distintos de una secuencia.

Si echas un vistazo a su implementación con Reflector, verás que crea DistinctIterator para tu tipo anónimo. El iterador distintivo agrega elementos a Set al enumerar sobre la colección. Este enumerador omite todos los elementos que ya están en Set . Set usa los métodos GetHashCode y Equals para definir si el elemento ya existe en Set .

¿Cómo se implementa GetHashCode e Equals para tipo anónimo? Como declaró en msdn :

Los métodos Equals y GetHashCode en tipos anónimos se definen en términos de los métodos Equals y GetHashcode de las propiedades, dos instancias del mismo tipo anónimo son iguales solo si todas sus propiedades son iguales.

Por lo tanto, definitivamente debe tener distintos objetos anónimos al iterar en colecciones distintas. Y el resultado no depende de cuántos campos use para su tipo anónimo.

Usar la palabra Key clave en su selección funcionará, como a continuación.

product.Select(m => new {Key m.CategoryId, Key m.CategoryName}).Distinct();

Me doy cuenta de que esto está sacando un viejo hilo, pero pensé que podría ayudar a algunas personas. En general, código en VB.NET cuando trabajo con .NET, por lo que Key puede traducir de manera diferente en C #.

Esta es mi solución, es compatible con keySelectors de diferentes tipos:

 public static IEnumerable DistinctBy(this IEnumerable source, params Func[] keySelectors) { // initialize the table var seenKeysTable = keySelectors.ToDictionary(x => x, x => new HashSet()); // loop through each element in source foreach (var element in source) { // initialize the flag to true var flag = true; // loop through each keySelector a foreach (var (keySelector, hashSet) in seenKeysTable) { // if all conditions are true flag = flag && hashSet.Add(keySelector(element)); } // if no duplicate key was added to table, then yield the list element if (flag) { yield return element; } } } 

Para usarlo:

 list.DistinctBy(d => d.CategoryId, d => d.CategoryName) 

Respondiendo el titular de la pregunta (qué atraía a la gente aquí) e ignorando que el ejemplo usaba tipos anónimos …

Esta solución también funcionará para tipos no anónimos. No debería ser necesario para tipos anónimos.

Clase de ayuda:

 ///  /// Allow IEqualityComparer to be configured within a lambda expression. /// From https://stackoverflow.com/questions/98033/wrap-a-delegate-in-an-iequalitycomparer ///  ///  public class LambdaEqualityComparer : IEqualityComparer { readonly Func _comparer; readonly Func _hash; ///  /// Simplest constructor, provide a conversion to string for type T to use as a comparison key (GetHashCode() and Equals(). /// https://stackoverflow.com/questions/98033/wrap-a-delegate-in-an-iequalitycomparer, user "orip" ///  ///  public LambdaEqualityComparer(Func toString) : this((t1, t2) => toString(t1) == toString(t2), t => toString(t).GetHashCode()) { } ///  /// Constructor. Assumes T.GetHashCode() is accurate. ///  ///  public LambdaEqualityComparer(Func comparer) : this(comparer, t => t.GetHashCode()) { } ///  /// Constructor, provide a equality comparer and a hash. ///  ///  ///  public LambdaEqualityComparer(Func comparer, Func hash) { _comparer = comparer; _hash = hash; } public bool Equals(T x, T y) { return _comparer(x, y); } public int GetHashCode(T obj) { return _hash(obj); } } 

El uso más simple:

 List products = duplicatedProducts.Distinct( new LambdaEqualityComparer(p => String.Format("{0}{1}{2}{3}", p.ProductId, p.ProductName, p.CategoryId, p.CategoryName)) ).ToList(); 

El uso más simple (pero no tan eficiente) es asignar a una representación de cadena para evitar el hash personalizado. Las cadenas iguales ya tienen los mismos códigos hash.

Referencia:
Ajustar un delegado en un IEqualityComparer

 public List GetBrandListByCat(int id) { var OBJ = (from a in db.Items join b in db.Brands on a.BrandId equals b.Id into abc1 where (a.ItemCategoryId == id) from b in abc1.DefaultIfEmpty() select new { ItemCategoryId = a.ItemCategoryId, Brand_Name = b.Name, Brand_Id = b.Id, Brand_Pic = b.Pic, }).Distinct(); List ob = new List(); foreach (var item in OBJ) { ItemCustom2 abc = new ItemCustom2(); abc.CategoryId = item.ItemCategoryId; abc.BrandId = item.Brand_Id; abc.BrandName = item.Brand_Name; abc.BrandPic = item.Brand_Pic; ob.Add(abc); } return ob; } 
 Employee emp1 = new Employee() { ID = 1, Name = "Narendra1", Salary = 11111, Experience = 3, Age = 30 };Employee emp2 = new Employee() { ID = 2, Name = "Narendra2", Salary = 21111, Experience = 10, Age = 38 }; Employee emp3 = new Employee() { ID = 3, Name = "Narendra3", Salary = 31111, Experience = 4, Age = 33 }; Employee emp4 = new Employee() { ID = 3, Name = "Narendra4", Salary = 41111, Experience = 7, Age = 33 }; List lstEmployee = new List(); lstEmployee.Add(emp1); lstEmployee.Add(emp2); lstEmployee.Add(emp3); lstEmployee.Add(emp4); var eemmppss=lstEmployee.Select(cc=>new {cc.ID,cc.Age}).Distinct();