【问题标题】:Linq to sql, filtering results in a datagridviewLinq to sql,在datagridview中过滤结果
【发布时间】:2009-07-23 14:51:55
【问题描述】:

我有一个非常简单的数据库,我正在使用 linq to sql。 我有一个 datagridview 来显示表格的内容。如果可能的话,我希望用户能够过滤出现在 datagridview 中的行,而无需对数据库进行另一次查询(我的资源真的很低,所以解决方案必须尽可能快)。

我想过使用BindingSource类的Filter属性,所以我创建了一个,将DataSource属性设置为linq to sql表达式。当用户添加过滤器时,我设置了 Filter 属性。半小时后,我发现 BindingSource 不支持过滤。地狱,太棒了;但那又如何呢?在使用谷歌又花了半个小时并发现基本上没有什么可用的之后,我决定使用 System.Collections.Generic.List 来存储行,因为我能够过滤它。没关系,但我还需要存储原始列表(以防用户删除过滤器),并且我还需要支持多个过滤器。

所以我有两个列表:一个包含查询结果的所有行,另一个包含满足过滤器条件的行。不过,我没有使用多个过滤器对其进行测试。

这行得通,虽然它不是一个很好的解决方案(至少我觉得它不吸引人),但我能得到的就是这些。我决定编写一个包装类,因为我以后可能需要重新使用这个解决方案。我考虑过创建一个 FilteredList 类(在我用 Google 进行了一些搜索并且没有找到任何现有的实现之后),基于以下理论:

  • 我存储了一个包含表中所有行的列表,
  • 我将过滤器(Predictate 表达式)存储在 BindingList 中(这样我就可以知道列表是否更改并重新过滤行),
  • 我将过滤后的行存储在一个列表中,当源列表或过滤器没有进行任何修改时用作缓存,
  • 我保留一个布尔值 (_NeedsRefiltering),表示是否必须对源行应用现有过滤器以重新生成缓存,
  • 该类必须实现 IList 接口,因此它可以作为 DataGridView 的数据源。

下面是我的 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();
    }
}

由于两个列表(源列表和过滤列表),我遇到了一些问题,但我认为我已经正确处理了它们。或者我可能没有,因为 DataGridView 似乎不接受它作为 DataSource:没有抛出异常,简单地说,什么都没有出现(没有出现空的 datagridview,但什么也没有出现 - 不是列,也不是空行添加更多项目)。嗯,嗯,这很奇怪。我尝试将 _FilteredSource 直接设置为 DataSource,这很好 - 直到我添加了一个过滤器并在收到错误时尝试向下滚动:System.IndexOutOfRangeException:索引 180 没有值。

截图: alt text http://shadow.crysis.hu/dgv_error.png

说实话,我不知道出了什么问题。我尝试调用 DataGridView 的 Invalidate、Update 和 Refresh 方法 - 结果相同。

所以...

  • 如何使用 linq to sql 有效过滤 DataGridView 中出现的结果?
  • 为什么不能将我的 FilteredList 用作 DataGridView 的数据源?
  • 上面的代码有什么问题?

非常感谢您抽出宝贵时间(如果您阅读了所有这些内容)和帮助(提前)!


因此,我尝试遵循 Marc Gravell 的建议,并实现了 System.Collections.IList 接口而不是通用接口。它起作用了,所以我可以将它绑定到 DataGridView 的 DataSource 属性,它显示所有行,但是当我添加一个过滤器并开始向下滚动时(由于某种原因,直到我开始滚动列表才会刷新 - Invalidate()、Refresh() 和 Update() 没有帮助)它开始将那些奇怪的 IndexOutOfRangeException-s 作为 DataError-s。

任何想法如何做这些东西?我不敢相信带有 datagridview 的 linq to sql 如此糟糕(对不起,但这越来越荒谬了)......

【问题讨论】:

    标签: c# linq-to-sql datagridview


    【解决方案1】:

    要使用DataGridView,您需要实现非泛型IList,而不是泛型IList&lt;T&gt;(或者更简单更好:继承自BindingList&lt;T&gt;,它通过INotifyPropertyChanged 提供更改通知等内容)。对于使用 LINQ-to-SQL,我有一些 info on usenet 可能有用(假设它仍然有效 - 已经有一段时间了)。

    关于“剩下的问题”...你能说得更具体点吗?

    重新高效过滤LINQ-to-SQL,你不想用Predicate&lt;T&gt;;你想使用Expression&lt;Func&lt;T,bool&gt;&gt;;这允许您通过Queryable.Where 将其传递给数据库,即(您有IQueryable&lt;T&gt; 源)类似于:

    IQueryable<T> data = tableSource;
    // then for each filter "expr"
    {
      data = data.Where(expr);
    }
    

    编写一个真正的过滤列表非常棘手。我已经为内存中的对象完成了它(虽然我不能发布代码) - 但它需要很多对象跟踪等。除非你绝对需要这个,否则它可能更容易保持简单,只显示简单的快照,只跟踪添加/删除。对于简单的快照,只需ToBindingList() 就足够了...

    【讨论】:

    • 谢谢,我会用它重写我的 FilteredList 类。关于其余问题的任何想法?那么,你会如何过滤呢?那 IndexOutOfRangeException 是什么?
    • 我明白了......所以我应该忘记过滤获取的数据,并在设置过滤器时重新查询数据库。使用 Expression> 是个好主意,谢谢!我会等一下,看看其他人是否有其他选择......如果没有,那么我会接受你的回答。无论如何,感谢您的时间和帮助!
    • 实现 IList 非泛型接口允许我将 FilteredList 对象绑定为 DataSource,但没有解决 IndexOutOfRange 问题...
    猜你喜欢
    • 2011-02-01
    • 2010-10-10
    • 1970-01-01
    • 1970-01-01
    • 2012-07-08
    • 2014-09-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多