Использование .Select и .Where в одном операторе LINQ - PullRequest
23 голосов
/ 23 февраля 2012

Мне нужно собрать Distinct Id из определенной таблицы, используя LINQ. Загвоздка в том, что мне также нужен оператор WHERE, который должен фильтровать результаты, основываясь только на установленных мной требованиях. Относительно новичок в том, что нужно так много использовать LINQ, но я использую следующий код более или менее:

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

Я попытался добавить .Distinct для var linq, но он по-прежнему возвращает повторяющиеся элементы (то есть с теми же идентификаторами). Я прочитал много сайтов и пытался добавить .Select в запрос, но вместо этого предложение .Where ломается. Есть и другие статьи, в которых запрос каким-то образом отличается от способа получения значений и помещения их в переменную. Я также пытался использовать .GroupBy, но при использовании Id в качестве ключа получаю «По крайней мере, один объект должен реализовывать IComparable».

Запрос на самом деле работает, и я могу вывести данные из столбцов с требуемыми спецификациями, но я просто не могу заставить работать .Distinct (единственное, чего на самом деле не хватает). Я попытался создать две переменные, одна из которых инициировала бы отдельный вызов, а затем имела вложенный foreach, чтобы гарантировать, что значения просто уникальны, но тысячи записей для сбора влияния на производительность слишком велики.

Я также не уверен, если бы мне пришлось переопределить или использовать IEnumerable для моего требования, и подумал, что я бы задал вопрос на всякий случай, если есть более простой способ, или если возможно иметь оба. Выберите и .Где работает только одно утверждение?

Ответы [ 4 ]

35 голосов
/ 23 февраля 2012

Вы добавили Select() после Where() или раньше?

Вы должны добавить его после, из-за логики параллелизма:

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

Если вы сначала делаете выбор, предложение Where может содержать только атрибут ID, потому что все остальные атрибуты уже отредактированы.

Обновление: для ясности этот порядок операторов должен работать:

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

Возможно, вы хотите добавить .toList() в конце, но это необязательно:)

11 голосов
/ 23 февраля 2012

Чтобы Enumerable.Distinct работал для вашего типа, вы можете реализовать IEquatable<T> и предоставить подходящие определения для Equals и GetHashCode, в противном случае будет использоваться реализация по умолчанию: равенство ссылок (при условии, что вы используете ссылочный тип).

Из руководства:

Метод Distinct (IEnumerable) возвращает неупорядоченную последовательность, которая не содержит повторяющихся значений. Он использует компаратор по умолчанию для сравнения значений по умолчанию.

Средство сравнения по умолчанию, Default, используется для сравнения значений типов, которые реализуют универсальный интерфейс IEquatable. Чтобы сравнить пользовательский тип данных, вам нужно реализовать этот интерфейс и предоставить собственные методы GetHashCode и Equals для этого типа.

В вашем случае, похоже, вам просто нужно сравнить идентификаторы, но вы также можете сравнить и другие поля в зависимости от того, что для вас означает, что два объекта "одинаковы".

Вы также можете рассмотреть возможность использования DistinctBy из morelinq .

Обратите внимание, что это только LINQ to Objects, но я предполагаю, что это то, что вы используете.

Еще один вариант - объединить GroupBy и First:

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

Это также будет работать в LINQ to SQL, например.

1 голос
/ 23 февраля 2012

Поскольку вы пытаетесь сравнить два разных объекта, вам сначала нужно будет реализовать интерфейс IEqualityComparer. Вот пример кода для простого консольного приложения, в котором используется отдельная и простая реализация 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; }
    }
}

Надеюсь, это поможет.

1 голос
/ 23 февраля 2012

Вы передали IEqualityComparer<T> на .Distinct()?

Примерно так:

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

Использование:

list.Distinct(new DetailComparer())
...