Pregunta Uso de .Select y .Where en una sola declaración LINQ


Necesito reunir identificaciones distintivas de una tabla particular usando LINQ. El problema es que también necesito una declaración WHERE que filtre los resultados basados ​​únicamente en los requisitos que he establecido. Relativamente nuevo para tener que usar tanto LINQ, pero estoy usando el siguiente código más o menos:

private void WriteStuff(SqlHelper db, EmployeeHelper emp)
{
    String checkFieldChange;
    AnIList tableClass = new AnIList(db, (int)emp.PersonId);
    var linq = tableClass.Items
        .Where(
           x => x.UserId == emp.UserId 
             && x.Date > DateBeforeChanges 
             && x.Date < DateAfterEffective 
             && (
                     (x.Field == Inserted)
                  || (x.Field == Deleted)))
                )
             ).OrderByDescending(x => x.Id);

    if (linq != null)
    {
        foreach (TableClassChanges item in linq)
        {
            AnotherIList payTxn = new AnotherIList(db, item.Id);
            checkFieldChange = GetChangeType(item.FieldName);

            // Other codes that will retrieve data from each item 
            // and write it into a text file
        }
    }
}

Traté de agregar .Distinct para var linq pero sigue devolviendo elementos duplicados (lo que significa que tengo los mismos Id). He leído muchos sitios y he intentado agregar un .Seleccione en la consulta, pero en su lugar se rompe la cláusula .Where. Hay otros artículos donde la consulta es de alguna manera diferente con la forma en que recupera los valores y lo coloca en una var. También traté de usar .GroupBy pero obtengo un "Al menos un objeto debe implementar IComparable" cuando se usa Id como clave.

La consulta realmente funciona y puedo mostrar los datos de las columnas con las especificaciones que necesito, pero parece que no puedo hacer el trabajo .Distintivo (que es lo único que realmente falta). Traté de crear dos vars con uno activando una llamada distinta y luego he creado un foreach anidado para garantizar que los valores sean únicos, pero miles de registros para recopilar el impacto en el rendimiento son demasiado.

Tampoco estoy seguro de si tendría que anular o usar IEnumerable para mis requerimientos, y pensé que haría la pregunta por si acaso hay una manera más fácil, o si es posible tener ambos. Seleccionar y. Dónde funciona en una sola declaración?


20
2018-02-23 09:34


origen


Respuestas:


Para poder Enumerable.Distinct para trabajar para su tipo, puede implementar IEquatable<T> y proporcionar definiciones adecuadas para Equals y GetHashCodede lo contrario, usará la implementación predeterminada: comparando para la igualdad de referencia (suponiendo que está utilizando un tipo de referencia).

Del manual:

El método Distinct (IEnumerable) devuelve una secuencia desordenada que no contiene valores duplicados. Utiliza el comparador de igualdad predeterminado, Predeterminado, para comparar valores.

El comparador de igualdad predeterminado, Predeterminado, se usa para comparar valores de los tipos que implementan la interfaz genérica IEquatable. Para comparar un tipo de datos personalizado, debe implementar esta interfaz y proporcionar sus propios métodos GetHashCode e Igual para el tipo.

En su caso, parece que podría necesitar comparar los ID, pero también puede comparar otros campos, según lo que signifique para usted que dos objetos sean "iguales".

También puedes considerar usar DistinctBy de morelinq.

Tenga en cuenta que esto es solo LINQ to Objects, pero supongo que eso es lo que está usando.

Sin embargo, otra opción es combinar GroupBy y First:

 var query = // your query here...
    .GroupBy(x => x.Id)
    .Select(g => g.First());

Esto también funcionaría en LINQ to SQL, por ejemplo.


10
2018-02-23 09:36



¿Agregaste el Select() después de la Where() ¿o antes?

Debería agregarlo después, debido a la lógica de concurrencia:

 1 Take the entire table  
 2 Filter it accordingly  
 3 Select only the ID's  
 4 Make them distinct.  

Si selecciona Primero, la cláusula Where solo puede contener el atributo ID porque todos los demás atributos ya se han eliminado.

Actualización: para mayor claridad, este orden de operadores debería funcionar:

db.Items.Where(x=> x.userid == user_ID).Select(x=>x.Id).Distinct();

Probablemente quiera agregar un .toList() al final, pero eso es opcional :)


28
2018-02-23 09:42



¿Pasaste un IEqualityComparer<T> a .Distinct()?

Algo como esto:

internal abstract class BaseComparer<T> : IEqualityComparer<T> {
    public bool Equals(T x, T y) {
        return GetHashCode(x) == GetHashCode(y);
    }

    public abstract int GetHashCode(T obj);
}

internal class DetailComparer : BaseComparer<StyleFeatureItem> {
    public override int GetHashCode(MyClass obj) {
        return obj.ID.GetHashCode();
    }
}

Uso:

list.Distinct(new DetailComparer())

1
2018-02-23 09:40



Como intenta comparar dos objetos diferentes, primero deberá implementar la interfaz IEqualityComparer. Aquí hay un código de ejemplo en una aplicación de consola simple que usa una implementación distinta y simple de IEqualityComparer:

 class Program
{
    static void Main(string[] args)
    {
        List<Test> testData = new List<Test>()
        {
            new Test(1,"Test"),
            new Test(2, "Test"),
            new Test(2, "Test")
        };

        var result = testData.Where(x => x.Id > 1).Distinct(new MyComparer());
    }
}

public class MyComparer : IEqualityComparer<Test>
{
    public bool Equals(Test x, Test y)
    {
        return x.Id == y.Id;
    }

    public int GetHashCode(Test obj)
    {
        return string.Format("{0}{1}", obj.Id, obj.Name).GetHashCode();
    }
}


public class Test
{
    public Test(int id, string name)
    {
        this.id = id;
        this.name = name;
    }

    private int id;

    public int Id
    {
        get { return id; }
        set { id = value; }
    }
    private string name;

    public string Name
    {
        get { return name; }
        set { name = value; }
    }
}

Espero que eso ayude.


0
2018-02-23 09:45