Linq do sql, filtrowanie wyników w datagridview

Mam bardzo prostą bazę danych, dla której używam linq do sql. Mam datagridview, aby wyświetlić zawartość tabeli. Chcę, aby użytkownik mógł filtrować wiersze pojawiające się w datagridview, jeśli to możliwe, bez tworzenia kolejnego zapytania do bazy danych (brakuje mi zasobów, więc rozwiązanie musi być tak szybkie, jak to możliwe).

Myślałem o użyciu właściwości Filter klasy BindingSource, więc stworzyłem ją, ustaw właściwość DataSource na linq na wyrażenie sql. Gdy użytkownik dodał filtr, ustawiam właściwość Filtr. Po około pół godzinie dowiedziałem się, że BindingSource nie obsługuje filtrowania. Do diabła, świetnie; ale co wtedy? Po spędzeniu kolejnych pół godziny na korzystaniu z Google i znalezieniu praktycznie niczego użytecznego, oszukałem użyć listy System.Collections.Generic.List do przechowywania wierszy, ponieważ jestem w stanie to filtrować. Wszystko było w porządku, ale musiałem także przechowywać oryginalną listę (na wypadek, gdyby użytkownik usunął filtr), a także muszę obsługiwać wiele filtrów.

Miałem więc dwie listy: jedną z wszystkimi wierszami, z których wynikało zapytanie, a drugą z wierszami spełniającymi warunki filtru. Jednak nie testowałem go z wieloma filtrami.

To zadziałało, chociaż nie było to naprawdę miłe rozwiązanie (przynajmniej nie podobało mi się), ale to było wszystko, co miałem. Oszustwałem napisać klasę wrappera, ponieważ może być konieczne ponowne użycie tego rozwiązania w dowolnym momencie później. Pomyślałem o utworzeniu klasy FilteredList (po tym, jak przeprowadziłem kilka wyszukiwań w Google i nie znalazłem żadnych istniejących implementacji), w oparciu o następującą teorię:

Przechowuję listę ze wszystkimi wierszami w tabeli,Przechowuję filtry (które są wyrażeniami predykcyjnymi) w BindingList (więc mogę wiedzieć, czy lista się zmieniła i ponownie filtrować wiersze),Przechowuję filtrowane wiersze na liście, służąc jako pamięć podręczna, gdy nie ma żadnych modyfikacji na liście źródłowej lub filtrach,Zachowuję wartość logiczną (_NeedsRefiltering), co oznacza, czy istniejące filtry muszą być stosowane w wierszach źródłowych, aby zregenerować pamięć podręczną,Klasa musi zaimplementować interfejs IList, aby mógł służyć jako źródło danych dla DataGridView.

Oto kod źródłowy mojej klasy FilteredList:

public class FilteredList<T> : IList<T>
{
    private bool _NeedsReFiltering = false;
    private BindingList<Predicate<T>> _Filters;

    public BindingList<Predicate<T>> Filters
    {
        get
        {
            if (this._Filters == null)
            {
                this._Filters = new BindingList<Predicate<T>>();
                this._Filters.RaiseListChangedEvents = true;
                this._Filters.ListChanged += delegate(object sender, ListChangedEventArgs e)
                {
                    this._NeedsReFiltering = true;
                };
            }
            return this._Filters;
        }
        set
        {
            this._Filters = value;
            this._NeedsReFiltering = true;
        }
    }

    private List<T> _Source;
    public List<T> Source
    {
        get
        {
            return this._Source;
        }
        set
        {
            this._Source = value;
            this._NeedsReFiltering = true;
        }
    }

    private List<T> __FilteredSource = new List<T>();
    private List<T> _FilteredSource
    {
        get
        {
            if (this._NeedsReFiltering)
            {
                this._NeedsReFiltering = false;
                this.Refilter();
            }
            return this.__FilteredSource;
        }
        set
        {
            this.__FilteredSource = value;
        }
    }

    public List<T> FilteredSource // Only for setting it as the DataGridView's DataSource - see my comments after the code
    {
        get
        {
            return this._FilteredSource;
        }
    }

    public FilteredList()
    {
        this._Source = new List<T>();
    }

    public FilteredList(int capacity)
    {
        this._Source = new List<T>(capacity);
    }

    public FilteredList(IEnumerable<T> source)
    {
        this._Source = new List<T>(source);
        this._NeedsReFiltering = true;
    }

    public void Refilter()
    {
        this.__FilteredSource = this._Source;

        if (this._Filters == null)
        {
            return;
        }

        foreach (var filter in this._Filters)
        {
            this.__FilteredSource.RemoveAll(item => !filter(item));
        }
    }

    public int IndexOf(T item)
    {
        return this._FilteredSource.IndexOf(item);
    }

    public void Insert(int index, T item)
    {
        this._FilteredSource.Insert(index, item);
        this._Source.Add(item);
    }

    public void RemoveAt(int index)
    {
        //this._Source.RemoveAt(index);
        this._Source.Remove(this.__FilteredSource[index]);
        this._NeedsReFiltering = true;
    }

    public T this[int index]
    {
        get
        {
            return this._FilteredSource[index];
        }
        set
        {
            this._Source[this._Source.FindIndex(item => item.Equals(this._FilteredSource[index]))] = value;
            this._NeedsReFiltering = true;
        }
    }

    public void Add(T item)
    {
        this._Source.Add(item);
        this._NeedsReFiltering = true;
    }

    public void Clear()
    {
        this._Source.Clear();
        this._FilteredSource.Clear();
        this._NeedsReFiltering = false;
    }

    public bool Contains(T item)
    {
        return this._FilteredSource.Contains(item);
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        this._FilteredSource.CopyTo(array, arrayIndex);
    }

    public int Count
    {
        get { return this._FilteredSource.Count; }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public bool Remove(T item)
    {
        var r = this._Source.Remove(item);
        this._FilteredSource.Remove(item);
        return r;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return this._FilteredSource.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this._FilteredSource.GetEnumerator();
    }
}

Miałem pewne problemy z powodu dwóch list (listy źródeł i listy filtrowanej), ale myślę, że poradziłem sobie z nimi poprawnie. A może nie, ponieważ DataGridView nie wydaje się akceptować go jako DataSource: nie ma wyjątku, po prostu nic się nie pojawia (nie pojawia się pusty datagridview, ale nic - nie kolumny ani pusty wiersz, aby dodać więcej przedmiotów). Cóż, to dziwne. Próbowałem ustawić _FilteredSource bezpośrednio jako DataSource i było dobrze - dopóki nie dodałem filtra i próbowałem przewinąć w dół, gdy otrzymałem błąd: System.IndexOutOfRangeException: Indeks 180 nie ma wartości.

Zrzut ekranu:alt text http://shadow.crysis.hu/dgv_error.png

Szczerze mówiąc, nie mam pojęcia, co jest nie tak. Próbowałem wywołać metody Invalidate, Update i Refresh DataGridView - takie same wyniki.

Więc...

W jaki sposób można skutecznie filtrować wyniki pojawiające się w DataGridView za pomocą linq do sql?Dlaczego nie mogę użyć mojego FilteredList jako źródła danych dla DataGridView?Jaki jest problem z powyższym kodem?

Dziękuję bardzo za poświęcony czas (jeśli przeczytałeś to wszystko) i pomóż (z wyprzedzeniem)!

Próbowałem więc śledzić to, co doradził Marc Gravell, i zaimplementowałem interfejs System.Collections.IList zamiast ogólnego. Zadziałało, więc mogłem powiązać go z właściwością DataSource obiektu DataGridView i wyświetlić wszystkie wiersze, ale gdy dodałem filtr i zaczęłem przewijać w dół (z jakiegoś powodu lista nie jest odświeżana, dopóki nie zacznę przewijać - Invalidate (), Refresh () i Update () nie pomagają) zaczęło podawać te dziwne IndexOutOfRangeException-s jako DataError-s.

Masz jakieś pomysły, jak to zrobić? Nie mogę uwierzyć, że linq do sql z datagridview jest tak do bani (przepraszam, ale to jest śmieszne) ...

questionAnswers(1)

yourAnswerToTheQuestion