1

I just started using Redis and I'm using it for one of my personal projects. The Redis DB contains about 10k objects of

public Class FileList
{
    public string FileName { get; set;} 
    public string FolderName { get; set;}
}

I'm displaying this list in the ListBox ResultsView initially on load. I've a TextBox where I can enter characters and on the TextBoxChanged_Event I'm calling a function which will query the RedisDB for all the FileList objects containing the character I typed in the TextBox and store it in a ResultsList<FileList> which will be looped in a foreach and then added to the ListBox.

It takes at least a second to start displaying results and is not very fast.

Now, If I do a same query on a MasterList<FileList>, then it's a bit faster but still not fast enough.

Yesterday, I tried with RavenDb which was painstakingly taking long times to do the same tasks.

Is it that I'm adding the Items to the ListBox in a forach that is taking so much time or is there anything that can be done to speed it up like ItemSource which I tried but gave me error that ItemList should be empty before binding

I did try most of the answers for almost similar questions but none helped me.

Code

ResultsView.Items.Clear();
var redisClient = new RedisClient("localhost");
using (var client = redisClient.As<FileList>())
{
    var foldersFromRedis = client.GetAll().Where(fileList => fileList.FileName.Contains(this.Search.Text.ToLower()));
    foreach (FileList fileList in foldersFromRedis)
    {
        var listViewItem = new ListViewItem { Content = fileList.FileName , Tag = fileList.FolderName  };
        this.ResultsView.Items.Add(listViewItem);
    }
}
//this.ResultsView.ItemsSource = ResultsFileList;
<ListBox Height="374" ScrollViewer.VerticalScrollBarVisibility="Disabled" HorizontalAlignment="Left" Margin="10,0,0,0" Name="ResultsView" VerticalAlignment="Bottom" Width="405" BorderThickness="0" SelectionChanged="MovieNameSelectionChanged" FontFamily="Nobile" FontSize="13" Background="#A6FCFCFC" Foreground="Black" FontStretch="Normal">
  <GridView>
        <GridViewColumn Header="FileName" DisplayMemberBinding="{Binding FileName}"/>
        </GridView>
</ListBox>

UPDATE 1:

Added the ViewCollectionSource as below

 private void ApplyViewCollectionSource()
    {
        _viewSource.Filter += ViewSourceFilter;
        _viewSource.Source = _fileList = (List<FileList>)PopulateFileListEnglishWithReturn();
        ResultsView.ItemsSource = _viewSource.View;
        _timer.Interval = new TimeSpan(0, 0, 0, 0, 200);
        _timer.Tick += (o, e) =>
        {
            _timer.Stop();
            _viewSource.View.Refresh();
        };
        Search.TextChanged += (o, e) => _timer.Start();
    }

which is being called in the Constructor. The filter is as below:

private void ViewSourceFilter(object sender, FilterEventArgs e)
{

    var src = e.Item as FileList;
    e.Accepted = src != null;
    if (string.IsNullOrEmpty(Search.Text)) return;
    var regex = new Regex(Search.Text, RegexOptions.IgnoreCase);
    e.Accepted = regex.IsMatch(src.FileName);
}

This works perfect for my requirement but only after the 3rd character in the TextBox. After typing the 1st char it takes 2 seconds for the ListBox to get updated and the 2nd char takes 1 second. After this, its almost instant. There are about 5000 items in the master list _fileList.

Any ways to improve the speed during the first 2 chars search ?

Dev
  • 987
  • 2
  • 14
  • 32
  • How did you use the binding to get the `ItemList should be empty before binding` ? and how does `AlbumName` fit in whole this? – Noctis Oct 27 '13 at 08:40
  • 1) Do you reload all the data in TextChanged even? If so-consider cachingfor few seoconds. 2) Cache value of `this.Search.Text.ToLower()` into local variable and use in `Where()` – sll Oct 27 '13 at 08:51
  • @Noctis it was typo, corrected it. I just set the ItemSource={Binding} and in code-behind set the ItemSource. – Dev Oct 27 '13 at 10:24
  • remove the GridView from inside the `ListBox`. It's completely wrong. – Federico Berasategui Oct 27 '13 at 23:15

3 Answers3

0

Don't reload the data every time you want to filter. WPF has mechanisms in place to filter the existing data without a round-trip to the server. consider using a CollectionView to do the filtering for you. A collection view allows you to change what's presented to the user without touching the data itself.

You can find a working example here: http://social.msdn.microsoft.com/Forums/vstudio/en-US/0d2b882d-cf56-4385-9b76-dd280c4c35b1/filter-a-readonlycollection-for-binding-a-listview?forum=wpf#213dda80-5d29-47fa-b832-ea7e098590d7

CKII
  • 1,439
  • 14
  • 26
  • I agree with the CollectionView, but funnily enough, we had an issue at work once, and I actually benchmarked the differences between searching for a string in a collection of 10K, VS. using the collection view filter ... searching the simple way was way faster... – Noctis Oct 27 '13 at 10:04
0

First, I personally don't like the empty binding ... I'd give the Window / UserControl a name

<Window ...
    Name="your_name_here">

Then in the binding, bind to a property on the code behind (assuming it's called property_for_binding), you'll bind it like:

<Listbox ...
   ItemSource={Binding ElementName=your_name_here , Path=property_for_binding} />

Having said that, I do prefer the MVVM approach, but nevermind , back to business ... Did you try to put some stopwatches on your function? You could just print to the console before call to DB, after call to DB (ruling out it's your DB fault), then before/after sorting, and so on. This way, you can see what's the slowest factor.

As comments said, You probably shouldn't query the DB for every change in the textbox (assuming you do it on text change, and not through a button once you finished typing) since usually your database will give the same results for all searches within the page (unless it's being heavily modified from somewhere else, in which case, you have no other option i guess).

So, cache the results once, search on that variable, and fill the property you'll be binding to with the results.

Edit:
You could always run the search only after the user puts in 3 characters, solving the issue (Assuming that of 10K items, one letter won't matter much anyways, and he'll continue to search).

Other options would be to only search after some time have passed since last keystroke.

And last but not least, here's a link that might help as well (addressing your first loop for adding items to the list) : Why is AddRange faster than using a foreach loop?

Community
  • 1
  • 1
Noctis
  • 11,507
  • 3
  • 43
  • 82
0

These sorts of sorts of problems are pretty common with ListBox I've found, have you checked to make sure that the data virtualization hasn't been disabled and that VirtualizationMode is set to "Recycling"?

Another thing that I find helps is to use a DispatchTimer to trigger the search a couple of seconds after the last character has been typed. Most users don't mind a short wait at the end but they don't want the GUI to feel sluggish while they're typing.

Mark Feldman
  • 15,731
  • 3
  • 31
  • 58