My linq query goes slow when I try to loop through the results to create an Xelement, which I later process XSLT based on the XElement.
Here is my code
public override XElement Search(SearchCriteria searchCriteria)
{
XElement root = new XElement("Root");
using (ReportOrderLogsDataContext dataContext = DataConnection.GetLinqDataConnection<ReportOrderLogsDataContext>(searchCriteria.GetConnectionString()))
{
try
{
IQueryable<vw_udisclosedDriverResponsePart> results = from a in dataContext.vw_udisclosedDriverResponseParts
where
(a.CreateDt.HasValue &&
a.CreateDt >= Convert.ToDateTime(searchCriteria.BeginDt) &&
a.CreateDt <= Convert.ToDateTime(searchCriteria.EndDt))
select a;
if (!string.IsNullOrEmpty(searchCriteria.AgentNumber))
{
results = results.Where(request => request.LgAgentNumber == searchCriteria.AgentNumber);
}
if (!string.IsNullOrEmpty(searchCriteria.AgentTitle))
{
results = results.Where(a => a.LgTitle == searchCriteria.AgentTitle);
}
if (!string.IsNullOrEmpty(searchCriteria.QuotePolicyNumber))
{
results = results.Where(a => a.QuotePolicyNumber == searchCriteria.QuotePolicyNumber);
}
if (!string.IsNullOrEmpty(searchCriteria.InsuredName))
{
results = results.Where(a => a.LgInsuredName.Contains(searchCriteria.InsuredName));
}
foreach (var match in results) // goes slow here, specifically times out before evaluating the first match when results are too large.
{
DateTime date;
string strDate = string.Empty;
if (DateTime.TryParse(match.CreateDt.ToString(), out date))
{
strDate = date.ToString("MM/dd/yyyy");
}
root.Add(new XElement("Record",
new XElement("System", "Not Supported"),
new XElement("Date", strDate),
new XElement("Agent", match.LgAgentNumber),
new XElement("UserId", match.LgUserId),
new XElement("UserTitle", match.LgTitle),
new XElement("QuoteNum", match.QuotePolicyNumber),
new XElement("AddressLine1", match.AddressLine1),
new XElement("AddressLine2", match.AddressLine2),
new XElement("City", match.City),
new XElement("State", match.State),
new XElement("Zip", match.Zip),
new XElement("DriverName", string.Concat(match.GivenName, " ", match.SurName)),
new XElement("DriverLicense", match.LicenseNumber),
new XElement("LicenseState", match.LicenseState)));
;
}
}
catch (Exception es)
{
throw es;
}
}
return root;
// return GetSearchedCriteriaFromStoredPocedure(searchCriteria);
}
I assume there is a better way to convert the results object into an XElement. Processing the view itself only takes about 2 seconds. Trying to loop through the results object is resulting in a timeout, even when many results are not returned.
Any help would be appreciated.
Thanks!
-James
AMENDED 7/10/2012
The issue is not with the linq query itself but its with the execution of the view when specifying a date range. Executing the view by itself takes about 4-6 seconds. When a small date range (07/05/2012 - 07/10/2012) is used the view takes around 1:30. Does anyone have any suggestions of how to increase performance of the query with a date range specified. Its faster if I got all of the results and looped through them checking the date.
i.e.
IQueryable<vw_udisclosedDriverResponsePart> results = from a in dataContext.vw_udisclosedDriverResponseParts select a;
foreach (var match in results) //results only takes 3 seconds to enumerate, before would timeout
{
// eval search criteria date here.
}
I can code it like I suggested above, but does anyone have a better way?
How does the database perform? The simplest test is to run a sample query - a query that will retrieve the data you need from the database, just to test database indexing and performance - because in 99% of cases that's the cause of slowness.
I would guess that the slowness is occurring because
you are iterating from the database, rather than retrieving all the rows up front, and
you are selecting on bad WHERE conditions (are your indexes correct?)
Firstly, call ToList to get the results to determine that the slowness is happening in the database, not in the XML construction
if (!string.IsNullOrEmpty(searchCriteria.InsuredName))
{
//...
}
var matches = results.ToList();
foreach (var match in matches)
{
//...
Assuming that the var matches = results.ToList() is very slow, I'd look at the functions in the WHERE clause
(a.CreateDt.HasValue &&
a.CreateDt >= Convert.ToDateTime(searchCriteria.BeginDt) &&
a.CreateDt <= Convert.ToDateTime(searchCriteria.EndDt))
to check that they aren't being executed for every row.
If you use SQL Server, run Profiler (in the Tools menu) to trace the SQL that LINQ-to-SQL.
And, of course, do the conversion outside the linq. criteria won't change
during the runtime of the Linq expression.
From what you posted, I made this example:
var begin = Convert.ToDateTime(searchCriteria.BeginDt);
var end = Convert.ToDateTime(searchCriteria.EndDt);
var results = from a in searchList
where ((a.CreateDt.HasValue &&
a.CreateDt >= begin &&
a.CreateDt <= end)
&& (string.IsNullOrEmpty(searchCriteria.AgentNumber) || a.LgAgentNumber == searchCriteria.AgentNumber)
&& (string.IsNullOrEmpty(searchCriteria.AgentTitle) || a.LgTitle == searchCriteria.AgentTitle)
&& (string.IsNullOrEmpty(searchCriteria.QuotePolicyNumber) || a.LgTitle == searchCriteria.QuotePolicyNumber)
&& (string.IsNullOrEmpty(searchCriteria.InsuredName) || a.LgInsuredName.Contains(searchCriteria.InsuredName))
)
select a;
Perhaps this is helpful for you.
For measuring the time I used the following:
var watch = new Stopwatch();
watch.Start();
var arr = results.ToArray(); // force evaluation of linq
watch.Stop();
var elapsed = watch.ElapsedTicks;
Seems the altered query is already about 30-40% faster on average, but i just
did some runs.
I would suggest few experiments:
One.
Put a
int count = results.Count();
before the foreach and see if this takes a long time.
Two.
Leave the the Count() call and see if the foreach is still slow. If it is fast it would suggest that the initial connection to the db is slow.
As others suggested - have a look how you query performs in the db (actually type in in the database, without c#).
You could also post a SHOW TABLE result so the community could inspect the indexes and help you with a fix.
Related
I have a query something like this
function List<CustomObject2> GetDataPoint(List<CustomObject> listDataPoints)
{
if(listDataPoints.Count == 0)
return;
var startPoint = new CustomObject();
startPoint = listDataPoint.First();
List<CustomObject2> cObjList = from r in listDataPoints
where r != null && r.GetDistance(startPoint) > 100
select new CustomObject2
{
Var1 = r.Var1
}.ToList()
}
The problem here is that, in the beginning the startPoint is set to the first object in listDataPoint. However, after the comparison in the query (GetDistance) I want to reassign startPoint to the value of "r" if the Distance is greater than 100.
Is there any way to do so?
Thanks in advance
No, there is no clean way to do that.
LINQ is essentially a piece of functional programming that has been brought into C#. In functional programming values are immutable (they cannot be changed). Thanks to being functional and using immutality, LINQ queries can be lazily evaluated. It is not uncommon for a LINQ query to be only partly run, or for some parts of the sequence to be evaluated several times. That is safe to do thanks to immutability.
As soon as you want to change a value, you are working against LINQ. In this case you are much better off with a for loop.
Of course there are ways to solve this in a functional manner, as it is possible to solve this in a purely functional language. But in C# it is much cleaner to use a for loop.
You can use a fold:
var cObjList = listDataPoints.Where(r => r != null)
.Aggregate(Tuple.Create(startPoint, new List<CustomObject2>()), (acc, r) => {
if(r.GetDistance(acc.Item1)) {
acc.Item2.Add(new CustomObject2 { Var1 = r.Var1 });
return Tuple.Create(r, acc.Item2);
}
else return acc;
}).Item2;
Since you were not-null checking the elements from listDataPoints, so I assume it may contain null objects. In this case, your code may be vulnerable when the First() element from the list is empty.
//there is no function or procedure in c#;
//function List<CustomObject2> GetDataPoint(List<CustomObject> listDataPoints)
List<CustomObject2> GetDataPoint(List<CustomObject> listDataPoints)
{
var dataPoints = listDataPoints.Where(r => r != null);
if (dataPoints.Empty())
//return; you cant not return anything in a function
return null; //or return an empty list
//return new List<CustomObject2>();
var cObjList = dataPoints.Aggregate(
new Stack<CustomObject>(),
(results, r) =>
{
if (r.GetDistance(results.Peek()) > 100)
results.Add(r);
return results;
})
.Select(r => new CustomObject2(){ Var1 = r.Var1 })
.ToList();
//return directly the line above or do more work with cObjList...
}
Yet, this is still messy and not easily maintained. Like Anders Abel suggests, you are best to go with the for loop for this case :
var cObjList= new List<CustomObject2>();
foreach(var r in dataPoints)
{
if (r.GetDistance(results.Peek()) > 100)
results.Add(new CustomObject2(){ Var1 = r.Var1 });
}
//...
return cObjList;
I have a piece of code that's performing badly, and need to rewite it to introduce a proper where clause before starting the .ToList however, that's where I'm getting stuck.
Currently the code looks lke this (roughly, I've taken some of the search criteria out to make it easier to display)
var Widgets = from b in _caspEntities.Widgets.Include("WidgetRegionLogs")
.Include("WidgetStatusLogs").Include("WidgetVoltageTests")
select b;
IEnumerable<Widget> results = Widgets.ToList();
if (comboBoxRegion.SelectedValue.ToString() != "0")
{
results = from b in results
where b.CurrentRegionLog != null && b.CurrentRegionLog.RegionId == int.Parse(comboBoxRegion.SelectedValue.ToString())
select b;
}
if (comboBoxStatus.SelectedValue != null)
{
results = from b in results
where b.CurrentStatusLog != null && b.CurrentStatusLog.StatusId == comboBoxStatus.SelectedValue.ToString()
select b;
}
if (txtCode.Text.Trim().Length > 0)
{
results = from b in results
where b.CodeNumber == txtCode.Text.Trim()
select b;
}
dataGridViewWidget.DataSource = results.ToList();
I can write the SQL easily enough, essentially the model is simple, I have a Widget it has a RegionLog and a StatusLog, both of which store a history. The current region and status are retrieved from this by grouping by WidgetID and selecting the most recent Date Updated (and then going off to Region and Status tables to get the actual value).
So, I need to translate this into LINQ, but to be honest I don't have a clue but am ken and willing to learn. In my head, I think I need to add some better where clauses, and then do the Widget.toList after I have applied the where clauses. I'm struggling with the CurrentRegionLog and CurrentStatusLog concepts as they are not populated until I run the IEnumerable.
If anyone can give some pointers, I'd be grateful,
Thanks
Edit - Added
public BatteryRegionLog CurrentRegionLog
{
get { return _currentRegionLog; }
}
private BatteryRegionLog _currentRegionLog
{
get
{
if (this.BatteryRegionLogs.Count > 0)
{
BatteryRegionLog log = this.BatteryRegionLogs.OrderByDescending(item => item.LastModifiedDate).First();
return log;
}
else
{
return null;
}
}
}
You can compose the query like this:
if (comboBoxRegion.SelectedValue.ToString() != "0")
{
var id = int.Parse(comboBoxRegion.SelectedValue.ToString()
Widgets = from b in Widgets
let currentRegionLog =
b.BatteryRegionLogs
.OrderByDescending(item => item.LastModifiedDate)
.FirstOrDefault()
where currentRegionLog.RegionId == id)
select b;
}
... // Same for the other criteria.
dataGridViewWidget.DataSource = Widgets.ToList();
The whole query is not executed before you do ToList(). As everything is translated to SQL you don't need the null check b.CurrentRegionLog != null. SQL will evaluate b.CurrentRegionLog.RegionId == id just fine when there is no CurrentRegionLog.
Edit
Since CurrentRegionLog is a calculated property of your Widget class it cannot be translated into SQL. I made an effort to incorporate the code of calculated property into the query in a way that only the basic navigation property is used, so EF can translate it to SQL again.
try remove this line:
IEnumerable<Widget> results = Widgets.ToList();
and just use the Widgets variable you get in at the top
The .ToList() goes to the database and materialiaze all the data into entities.
if you don't call the .ToList() the query is still "open" for a where clause
I have a LINQ query that run on datatable that has 5,00,000 plus records. This query returns me only one row but takes almost 30 seconds to run. This is my query
var callDetailsForNodes = from records in dtRowForNode.Select().Select(dr =>
new
{
caller1 = StringComparer.CurrentCultureIgnoreCase.Compare(dr["F1"], dr["F2"]) < 0 ? dr["F1"] : dr["F2"],
caller2 = StringComparer.CurrentCultureIgnoreCase.Compare(dr["F1"], dr["F2"]) < 0 ? dr["F2"] : dr["F1"],
time = dr["F3"],
filters = dr.Field<string>("F9")
}).Where(dr => (dtMin <= Convert.ToDateTime(dr.time)) && (dtMax >= Convert.ToDateTime(dr.time)) && (lstCallType.Contains(dr.filters))
&& (dtMinTime <= Convert.ToDateTime(dr.time).TimeOfDay) && (dtMaxTime >= Convert.ToDateTime(dr.time).TimeOfDay))
.GroupBy(drg => new { drg.caller1, drg.caller2 })
.Select(drg => new { drg.Key.caller1, drg.Key.caller2, count = drg.Count() }).AsEnumerable()
where (records.caller1.ToString() == VerSelected || records.caller2.ToString() == VerSelected)
select records;
Again i run a query to rearrange the data get it from above query as
var callDetailsForNodes_ReArrange = from records in callDetailsForNodes.Select(r => new
{
caller1 = r.caller1.ToString() == VerSelected ? r.caller1 : r.caller2,
caller2 = r.caller1.ToString() != VerSelected ? r.caller1 : r.caller2,
count = r.count
})
select records;
Then i am just binding this collection to gridview.
Is there any efficient way to query on such a large dataset
Edit
I have try to debug the programm step by step and find that this 2 queries actually runs fast and time is taken at the step when i add the result set of this query to ObservableCollection to bind it to gridview. Here is the code
foreach (var callDetailsForNode_ReArrange in callDetailsForNodes_ReArrange)
{
_CallForNodes.Add(new CallForNodeData
{
Caller1 = callDetailsForNode_ReArrange.caller1.ToString(),
Caller2 = callDetailsForNode_ReArrange.caller2.ToString(),
Count = callDetailsForNode_ReArrange.count
});
}
Here callDetailsForNodes_ReArrange has resultset count = 1
One thing that would help would be to convert dtMin, dtMax and dtMinTime before the call into the units of the data (dr.time). Then you can get rid of the Convert.ToDateTime that is happening multiple times on each record.
I have tidied up your query a little (although this won't make a massive amount of difference to performance, and there may be typos as I don't have VS to hand). From your edit it seems you are a little confused by deferred execution in LINQ. callDetailsForNodes does not represent your results - it is a query that will provide your results once it is executed.
If you have to do all this querying in process I suggest you add a ToList after the first select and run that in isolation. Then add ToList to the Where clause. Calling ToList will force your query to execute and you will see where the delays are.
One final note - you should pass your records directly to ObservableCollection constructor rather than calling Add for each item. Calling Add will (I think) cause the collection to raise a changed notification which is not a big deal for small lists but will slow things down for larger lists.
var callDetailsForNodes = dtRowForNode.AsEnumerable()
.Select(dr => new {
caller1 = StringComparer.CurrentCultureIgnoreCase.Compare(dr["F1"], dr["F2"]) < 0 ? dr["F1"] : dr["F2"],
caller2 = StringComparer.CurrentCultureIgnoreCase.Compare(dr["F1"], dr["F2"]) < 0 ? dr["F2"] : dr["F1"],
time = Convert.ToDateTime(dr["F3"]),
filters = dr.Field<string>("F9")})
.Where(dr => (dtMin <= dr.time)
&& (dtMax >= dr.time)
&& (lstCallType.Contains(dr.filters))
&& (dtMinTime <= dr.time.TimeOfDay)
&& (dtMaxTime >= dr.time.TimeOfDay)
&& caller1 == VerSelected || caller2 == VerSelected))
.GroupBy(drg => new { drg.caller1, drg.caller2 })
.Select(drg => new { drg.Key.caller1, drg.Key.caller2, count = drg.Count());
I have an ICriteria query like so:
var contentCriteria = DetachedCriteria.For<InvoiceItem>();
var countCriteria = DetachedCriteria.For<InvoiceItem>();
if (model.CurrentPage <= 0) model.CurrentPage = 1;
if (model.OnlyShowErrors)
{
contentCriteria.Add(Restrictions.Not(Restrictions.Eq("TroubleClass", TroubleClasses.Success)));
countCriteria.Add(Restrictions.Not(Restrictions.Eq("TroubleClass", TroubleClasses.Success)));
}
if (!string.IsNullOrEmpty(model.BatchId))
{
contentCriteria.Add(Restrictions.Eq("BatchId", model.BatchId));
countCriteria.Add(Restrictions.Eq("BatchId", model.BatchId));
}
if (model.DocumentStartDate != null)
{
contentCriteria.Add(Restrictions.Ge("DocumentDate", model.DocumentStartDate));
countCriteria.Add(Restrictions.Ge("DocumentDate", model.DocumentStartDate));
}
if (model.DocumentEndDate != null)
{
contentCriteria.Add(Restrictions.Le("DocumentDate", model.DocumentEndDate));
countCriteria.Add(Restrictions.Le("DocumentDate", model.DocumentEndDate));
}
if (!string.IsNullOrEmpty(model.VendorId))
{
contentCriteria.Add(Restrictions.Eq("VendorId", model.VendorId));
countCriteria.Add(Restrictions.Eq("VendorId", model.VendorId));
}
using (var session = GetSession())
{
var countC = countCriteria.GetExecutableCriteria(session)
.SetProjection(Projections.CountDistinct("RecordId"));
var contentC = contentCriteria
.AddOrder(Order.Desc("PersistedTimeStamp"))
.GetExecutableCriteria(session)
.SetResultTransformer(Transformers.DistinctRootEntity)
.SetFirstResult((model.CurrentPage * model.ItemsPerPage) - model.ItemsPerPage)
.SetMaxResults(model.ItemsPerPage);
var mq = session.CreateMultiCriteria()
.Add("total", countC)
.Add<InvoiceItem>("paged", contentC);
model.Invoices = ((IEnumerable<InvoiceItem>)mq.GetResult("paged"));
model.Invoices = model.Invoices
.OrderBy(x => x.PersistedTimeStamp);
model.TotalItems = (int)(mq.GetResult("total") as System.Collections.ArrayList)[0];
}
return model;
This returns results, but where I would expect the results to be in groups of model.ItemsPerPage, it rarely is. I think that the .SetResultTransformer(Transformers.DistinctRootEntity) transform is being run after the .SetMaxResults(model.ItemsPerPage) limit, and I don't know why or how to fix it. Can someone please enlighten me?
You need to see the SQL generated by NHibernate as this is not essentially NHibernate bug but behavior of SQL queries when ROWNUM and DISTINCT are applied together. This has been an issue in our known issues list from long.
Following URLs might enlighten you...
ROW NUMBER vs DISTINCT
How ROWNUM works
So this is directly related to what was written in this blog post. Additionally, I had the platform-specific complication of PostgreSQL not allowing a DISTINCT ordered set ordered by something not in the SELECT list. Ultimately, I had to make two calls to the database, like so:
using (var session = GetSession())
{
//I honestly hope I never have to reverse engineer this mess. Pagination in NHibernate
//when ordering by an additional column is a nightmare.
var countC = countCriteria.GetExecutableCriteria(session)
.SetProjection(Projections.CountDistinct("RecordId"));
var contentOrdered = contentCriteria
.SetProjection(Projections.Distinct(
Projections.ProjectionList()
.Add(Projections.Id())
.Add(Projections.Property("PersistedTimeStamp"))
))
.AddOrder(Order.Desc("PersistedTimeStamp"))
.SetFirstResult((model.CurrentPage * model.ItemsPerPage) - model.ItemsPerPage)
.SetMaxResults(model.ItemsPerPage);
var contentIds = contentOrdered.GetExecutableCriteria(session)
.List().OfType<IEnumerable<object>>()
.Select(s => (Guid)s.First())
.ToList();
var contentC = DetachedCriteria.For<InvoiceItem>()
.Add(Restrictions.In("RecordId", contentIds))
.SetResultTransformer(Transformers.DistinctRootEntity);
var mq = session.CreateMultiCriteria()
.Add("total", countC)
.Add("paged", contentC);
model.Invoices = (mq.GetResult("paged") as System.Collections.ArrayList)
.OfType<InvoiceItem>()
.OrderBy(x => x.PersistedTimeStamp);
model.TotalItems = (int)(mq.GetResult("total") as System.Collections.ArrayList)[0];
}
return model;
This is not pretty, but it worked; I think the folks over at NHibernate need to work on this and make it a tad bit easier.
I just had the weirdest debug experience in a very long time. It's a bit embarassing to admit, but it lead me to be believe that my Linq query produces MORE results when adding an additional Where clause.
I know it's not possible, so I've refactored my offending function plus the unit test belonging to it into this:
[Test]
public void LoadUserBySearchString()
{
//Setup
var AllUsers = new List<User>
{
new User
{
FirstName = "Luke",
LastName = "Skywalker",
Email = "luke#jedinet.org"
},
new User
{
FirstName = "Leia",
LastName = "Skywalker",
Email = "faeryprincess#winxmail.com"
}
};
//Execution
List<User> SearchResults = LoadUserBySearchString("princess", AllUsers.AsQueryable());
List<User> SearchResults2 = LoadUserBySearchString("princess Skywalker", AllUsers.AsQueryable());
//Assertion
Assert.AreEqual(1, SearchResults.Count); //test passed!
Assert.AreEqual(1, SearchResults2.Count); //test failed! got 2 instead of 1 User???
}
//search CustID, fname, lname, email for substring(s)
public List<User> LoadUserBySearchString(string SearchString, IQueryable<User> AllUsers)
{
IQueryable<User> Result = AllUsers;
//split into substrings and apply each substring as additional search criterium
foreach (string SubString in Regex.Split(SearchString, " "))
{
int SubStringAsInteger = -1;
if (SubString.IsInteger())
{
SubStringAsInteger = Convert.ToInt32(SubString);
}
if (SubString != null && SubString.Length > 0)
{
Result = Result.Where(c => (c.FirstName.Contains(SubString)
|| c.LastName.Contains(SubString)
|| c.Email.Contains(SubString)
|| (c.ID == SubStringAsInteger)
));
}
}
return Result.ToList();
}
I have debugged the LoadUserBySearchString function and asserted that the second call to the function actually produces a linq query with two where clauses instead of one. So it seems that the additional where clause is increasing the amount of results.
What's even more weird, the LoadUserBySearchString function works great when I test it by hand (with real users from the database). It only shows this weird behavior when running the unit test.
I guess I just need some sleep (or even an extended vacation). If anyone could please help me shed some light on this, I could go stop questioning my sanity and go back to work.
Thanks,
Adrian
Edit (to clarify on several responses I go so far): I know it looks like it is the or clause, but unfortuantely it is not that simple. LoadUserBySearchString splits the search string into several strings and attaches a Where clause for each of them. "Skywalker" matches both luke and Leia, but "princess" only matches Leia.
This is the Linq query for the search string "princess":
+ Result {System.Collections.Generic.List`1[TestProject.Models.User].Where(c => (((c.FirstName.Contains(value(TestProject.Controllers.SearchController+<>c__DisplayClass1).SubString) || c.LastName.Contains(value(TestProject.Controllers.SearchController+<>c__DisplayClass1).SubString)) || c.Email.Contains(value(TestProject.Controllers.SearchController+<>c__DisplayClass1).SubString)) || (c.ID = value(TestProject.Controllers.SearchController+<>c__DisplayClass3).SubStringAsInteger)))} System.Linq.IQueryable<TestProject.Models.User> {System.Linq.EnumerableQuery<TestProject.Models.User>}
And this is the Linq clause for the search string "princess Skywalker"
+ Result {System.Collections.Generic.List`1[TestProject.Models.User].Where(c => (((c.FirstName.Contains(value(TestProject.Controllers.SearchController+<>c__DisplayClass1).SubString) || c.LastName.Contains(value(TestProject.Controllers.SearchController+<>c__DisplayClass1).SubString)) || c.Email.Contains(value(TestProject.Controllers.SearchController+<>c__DisplayClass1).SubString)) || (c.ID = value(TestProject.Controllers.SearchController+<>c__DisplayClass3).SubStringAsInteger))).Where(c => (((c.FirstName.Contains(value(TestProject.Controllers.SearchController+<>c__DisplayClass1).SubString) || c.LastName.Contains(value(TestProject.Controllers.SearchController+<>c__DisplayClass1).SubString)) || c.Email.Contains(value(TestProject.Controllers.SearchController+<>c__DisplayClass1).SubString)) || (c.ID = value(TestProject.Controllers.SearchController+<>c__DisplayClass3).SubStringAsInteger)))} System.Linq.IQueryable<TestProject.Models.User> {System.Linq.EnumerableQuery<TestProject.Models.User>}
Same as above, just with one additional where clause.
This is a nice little gotcha.
What is happening is that, because of anonymous methods, and deferred execution, you're actually not filtering on "princess". Instead, you're building a filter that will filter on the contents of the subString variable.
But, you then change this variable, and build another filter, which again uses the same variable.
Basically, this is what you will execute, in short form:
Where(...contains(SubString)).Where(...contains(SubString))
so, you're actually only filtering on the last word, which exists in both, simply because by the time these filters are actually applied, there is only one SubString value left, the last one.
If you change the code so that you capture the SubString variables inside the scope of the loop, it'll work:
if (SubString != null && SubString.Length > 0)
{
String captured = SubString;
Int32 capturedId = SubStringAsInteger;
Result = Result.Where(c => (c.FirstName.Contains(captured)
|| c.LastName.Contains(captured)
|| c.Email.Contains(captured)
|| (c.ID == capturedId)
));
}
Your algorithm amounts to "select records which match any of the words in the search string".
This is because of deferred execution. The query is not actually performed until you call the .ToList(). If you move the .ToList() inside the loop, you'll get the behaviour you want.