Jmoran
Jmoran

Reputation: 3

Need to improve performance when searching a custom object and returning it to a new collection

The results are here:

Search Started: 9/20/2020 6:05:39 AM
Search Completed: Took 00:00:00
Collection Created: Took -00:01:18.4322494
DataSource Created: Took 00:00:00

What Im trying to do is search through a collection of objects for any that match a search term. The collection is used as a binding datasource for a datagridview in a winforms app. After the collection has been searched via linq, it returns the results back to an ObservableCollection and then sets it as the new datasource. Everything works fine except the SearchResults = new ObservableCollection<OrderLine>(SearchResultsQuery); is VERY slow as you can see from the benchmark I did earlier.

Any help is appreciated!!

The Code that produced those results is here:

DateTime startTime = DateTime.Now;
Console.WriteLine($"Search Started: {startTime}");
SearchTerm = SearchTerm.ToUpper();

var SearchResultsQuery = from orderLine in new ObservableCollection<OrderLines.OrderLine>(OrderLineCollection)
                         where ( orderLine.BatchNumber != null && orderLine.BatchNumber.ToString().ToUpper().Contains(SearchTerm) ) ||
                         ( orderLine.BatchStatus != null && orderLine.BatchStatus.ToString().ToUpper().Contains(SearchTerm) ) ||
                         ( orderLine.CustomerItem != null && orderLine.CustomerItem.ToString().ToUpper().Contains(SearchTerm) ) ||
                         ( orderLine.CustomerName != null && orderLine.CustomerName.ToString().ToUpper().Contains(SearchTerm) ) ||
                         ( orderLine.CustomerPurchaseOrder != null && orderLine.CustomerPurchaseOrder.ToString().ToUpper().Contains(SearchTerm) ) ||
                         ( orderLine.FGItem != null && orderLine.FGItem.ToString().ToUpper().Contains(SearchTerm) ) ||
                         ( orderLine.IngItem != null && orderLine.IngItem.ToString().ToUpper().Contains(SearchTerm) ) ||
                         ( orderLine.RawItem != null && orderLine.RawItem.ToString().ToUpper().Contains(SearchTerm) ) ||
                         ( orderLine.ItemDescription != null && orderLine.ItemDescription.ToString().ToUpper().Contains(SearchTerm) ) ||
                         ( orderLine.Machine != null && orderLine.Machine.ToString().ToUpper().Contains(SearchTerm) ) ||
                         ( orderLine.NextPONumber != null && orderLine.NextPONumber.ToString().ToUpper().Contains(SearchTerm) ) ||
                         ( orderLine.Note != null && orderLine.Note.Content.ToString().ToUpper().Contains(SearchTerm) ) ||
                         ( orderLine.OrderNumber != null && orderLine.OrderNumber.ToString().ToUpper().Contains(SearchTerm) ) ||
                         ( orderLine.Status_1 != null && orderLine.Status_1.ToString().ToUpper().Contains(SearchTerm) ) ||
                         ( orderLine.Status_2 != null && orderLine.Status_2.ToString().ToUpper().Contains(SearchTerm) ) ||
                         ( orderLine.Status_3 != null && orderLine.Status_3.ToString().ToUpper().Contains(SearchTerm) ) ||
                         ( orderLine.Status_4 != null && orderLine.Status_4.ToString().ToUpper().Contains(SearchTerm) ) ||
                         ( orderLine.Status_Today != null && orderLine.Status_Today.ToString().ToUpper().Contains(SearchTerm) )
                         select orderLine;

TimeSpan searchTime = startTime - DateTime.Now;
startTime = DateTime.Now;
Console.WriteLine($"Search Completed: Took {searchTime}");

SearchResults = new ObservableCollection<OrderLine>(SearchResultsQuery); //THIS LINE IS SLOW

TimeSpan collectionTime = startTime - DateTime.Now;
startTime = DateTime.Now;
Console.WriteLine($"Collection Created: Took {collectionTime}");

bs.DataSource = SearchResults;

TimeSpan dataSourceTime = startTime - DateTime.Now;
Console.WriteLine($"DataSource Created: Took {dataSourceTime}");

How can I improve the performance of leading the LINQ results into a new collection?

Upvotes: 0

Views: 166

Answers (1)

JonasH
JonasH

Reputation: 36361

Some things to consider:

Never use DateTime for measuring performance. Always use a stopwatch or benchmark.net. And never measure the first run of a algorithm, since it will include compilation time.

Another recommendation is to use a more detailed profiler that can provide line by line performance reports.

new ObservableCollection(SearchResultsQuery); //THIS LINE IS SLOW

this is slow since this is the actual line running the query. Nothing abnormal about this.

new ObservableCollection<OrderLines.OrderLine>(OrderLineCollection)

You should be able to just search thru the OrderLineCollection. Why are you copying everything to a ObservableCollection?

If many of the properties are strings, why are you running .ToString() on them?

.ToUpper() is quite inefficient since it has to create a new string, unfortenatly .Contains do not have an overload that takes a StringComparison parameter. Some suggest using .IndexOf, but this seem slower than .Contains when I test it.

One option would be to concatenate all the properties to a large string, a downside with this would be that the result could be different, Searching for "abcd" would be true if one property ends with "ab" and one begins with "cd". This may or not matter for your use case.

Another option would be to convert all the properties to a list of strings as a pre-process step, so searching would simply check all items in the list. This seem a bit slower than concatenating the properties.

Both of the above could be combined with .AsParallel() to run the query on multiple threads.

Third option would be to use something like lucene that is specifically made for fast searching.

When I do something similar I get about 700ms with the original code on 1000000 items. And about 15ms using string concatenation and AsParallel, (not including concatenating the strings). That should be sufficient for interactive performance. If you have many more items you might want to some third party solution for searching.

Upvotes: 1

Related Questions