I am getting some behavior from a LINQ Where clause that I can't quite grok.
For instance, I am trying to search a list for terms using a AND semantic.
Here is some code:
var items = new List<string>()
{
"beef",
"beef soup",
"beef ribs",
"chicken soup",
"soup",
"pork ribs",
"ribs"
};
var term = "soup beef";
var terms = term.Split(' ');
var result = items.AsQueryable();
foreach(var t in terms)
{
result = result.Where( i => i.Contains(t) );
}
result.ToList().Dump();
The result are:
beef beef
soup
beef ribs
However, I was looking for it to an AND result, returning just:
beef soup
Now, I CAN GET THE RESULT I WANT by simply adding
.ToList().AsQueryable()
to the end of the Where clause.
Of course, this is not what I want to do if the back end is a database.
What is really odd here is that when I check 'result' in the loop, I get the following:
Before first where clause => full list -> makes sense.
After first where (for 'soup') => ['beef soup', 'chicken soup', 'soup'] -> still looks good
Before second loop (for 'beef') => ['beef', 'beef soup', 'beef ribs'] -> WHOA! Whats up here
After second loop => stays the same
Can someone explain to me what is going on (and how to fix this correctly)?
Review Access To Modified Closure
http://weblogs.asp.net/fbouma/archive/2009/06/25/linq-beware-of-the-access-to-modified-closure-demon.aspx
Essentially the problem is that t is being modified in your foreach loop, but you are using it within your closure (the Where lambda method), which is accessing t by reference.
CLOSURES!!! Arg....
Got it.
var cTerm = t;
result = result.Where( i => i.Contains(cTerm) );
Sorry for the randomness...
Related
I have a list of strings like this
"Users": [
"usrName|Fullname|False|0|False|False",
"usrName|Fullname|False|0|False|False",
"usrName|Fullname|False|0|False|False",
"usrName|Fullname|False|0|False|False",
"usrName|Fullname|False|0|False|False",
]
In my episerver/optimizely code I want to match items. I have written this line of code
searchResult.Filter(x => x.Users.MatchContained(k=> k.Split('|')[3], "0"));
I am trying to get all Users where after splitiing on 'pipe' I get 0 at index 3. I am not getting the result, infact I get an exception.
What am I doing wrong here?
Since you left out A LOT of details these are my assumptions
Users is an IList<string> Users
You are trying to get all Users within that list/array where index 3 equals 0
What we don't know is, among others
Is there more than one page instance that have the Users instance filled?
Anyway, this cannot be solved using any Find API with the current system design. Instead you need to rely on linq to parse the result, but then the Find implementation may not be necessary.
var searchClient = SearchClient.Instance;
var search = searchClient.Search<BlogPage>();
var result = search.GetContentResult();
var usersResult = result
.SelectMany(x => x.Users)
.Where(x => x.Split('|')[3].Equals("0"));
This would create and return an object similar to this, since I'm using SelectMany the array would contain all users throughout the systems where there are matched pages from the search result.
If you for whatever reason would like to keep or see some page properties there are alternative approaches where you construct a new object within a select and remove any object where there where not matched users
var searchClient = SearchClient.Instance;
var search = searchClient.Search<BlogPage>();
var result = search.GetContentResult();
var usersResult = result
.Select(p => new
{
PageName = p.Name,
ContentLink = p.ContentLink.ToString(),
Users = p.Users.Where(x => x.Split('|')[3].Equals("0"))
})
.Where(x => x.Users.Any());
If you like to keep using Find for this kind of implementations you must store the data in a better way than strings with delimiters.
What does it mean and why (if at all) is it important?
It means you can add additional "operators" to a query. It's important because you can do it extremely efficiently.
For example, let's say you have a method that returns a list (enumerable) of employees:
var employees = GetEmployees();
and another method that uses that one to return all managers:
IEnumerable<Employee> GetManagers()
{
return GetEmployees().Where(e => e.IsManager);
}
You can call that function to get managers that are approaching retirement and send them an email like this:
foreach (var manager in GetManagers().Where(m => m.Age >= 65) )
{
SendPreRetirementMessage(manager);
}
Pop quiz: How many times will that iterate over your employees list? The answer is exactly once; the entire operation is still just O(n)!
Also, I don't need to have separate methods for this. I could compose a query with these steps all in one place:
var retiringManagers = GetEmployees();
retiringManagers = retiringManagers.Where(e => e.IsManager);
retiringManagers = retiringManagers.Where(m => m.Age >= 65);
foreach (var manager in retiringMangers)
{
SendPreRetirementMessage();
}
One cool thing about this is that I can change is at run time, such that I can include or not include one part of the composition inside an if block, such that the decision to use a specific filter is made at run time, and everything still comes out nice and pretty.
I think it means that you can daisy chain your queries, like this
var peterJacksonsTotalBoxOffice
= movies.Where(movie => movie.Director == "Peter Jackson")
.Sum(movie => movie.BoxOffice);
I want to do something like DELETE FROM TABLE WHERE ID NOT IN (1,2,3) AND PAGEID = 9
I have a List of IDS but that could be changed if needs be. I can't work out how to get a boolean result for the LINQ parser.
Here is what Subsonic expects I think.
db.Delete(content => content.PageID == ID).Execute();
I can't work out how to do the NOT IN statement. I've tried the List.Contains method but something not quite right.
UPDATE: One alternative is to do:
var items = TABLE.Find(x => x.PageID == ID)'
foreach(var item in items)
{
item.Delete();
}
This hits the database a lot more though
When you say "something not quite right" what exactly do you mean?
I'd expect to write:
List<int> excluded = new List<int> { 1, 2, 3 };
db.Delete(content => !excluded.Contains(content.PageID)).Execute();
Note that you need to call Contains on the array of excluded values, not on your candidate. In other words, instead of saying "item not in collection" you're saying "collection doesn't contain item."
Try .Contains:
db.Delete(content => content.PageID.Contains(<Array containing ID's>).Execute();
(the above is just an example, might need some polishing for your specific situation)
I have found that this works but its not via LINQ
var table = new WebPageContentTable(_db.DataProvider);
var g = new SubSonic.Query.Delete<WebPageContent(_db.DataProvider)
.From(table)
.Where(table.ID)
.NotIn(usedID)
.Execute();
I have found that this does work and via LINQ - however it hits the database multiple times.
var f = WebPageContent.Find(x => !usedID.Any(e => e == x.ID));
if (f.Count > 0)
{
var repo = WebPageContent.GetRepo();
repo.Delete(f);
}
This I imagine would work in one hit to the database but I get an exception thrown in QueryVisitor::VisitUnary
WebPageContent.Delete(x => !usedID.Any(e => e == x.ID));
I felt like the following should be possible I'm just not sure what approach to take.
What I'd like to do is use the include method to shape my results, ie define how far along the object graph to traverse. but... I'd like that traversal to be conditional.
something like...
dealerships
.include( d => d.parts.where(p => p.price < 100.00))
.include( d => d.parts.suppliers.where(s => s.country == "brazil"));
I understand that this is not valid linq, in fact, that it is horribly wrong, but essentially I'm looking for some way to build an expression tree that will return shaped results, equivalent to...
select *
from dealerships as d
outer join parts as p on d.dealerid = p.dealerid
and p.price < 100.00
outer join suppliers as s on p.partid = s.partid
and s.country = 'brazil'
with an emphasis on the join conditions.
I feel like this would be fairly straight forward with esql but my preference would be to build expression trees on the fly.
as always, grateful for any advice or guidance
This should do the trick:
using (TestEntities db = new TestEntities())
{
var query = from d in db.Dealership
select new
{
Dealer = d,
Parts = d.Part.Where
(
p => p.Price < 100.0
&& p.Supplier.Country == "Brazil"
),
Suppliers = d.Part.Select(p => p.Supplier)
};
var dealers = query.ToArray().Select(o => o.Dealer);
foreach (var dealer in dealers)
{
Console.WriteLine(dealer.Name);
foreach (var part in dealer.Part)
{
Console.WriteLine(" " + part.PartId + ", " + part.Price);
Console.WriteLine
(
" "
+ part.Supplier.Name
+ ", "
+ part.Supplier.Country
);
}
}
}
This code will give you a list of Dealerships each containing a filtered list of parts. Each part references a Supplier. The interesting part is that you have to create the anonymous types in the select in the way shown. Otherwise the Part property of the Dealership objects will be empty.
Also, you have to execute the SQL statement before selecting the dealers from the query. Otherwise the Part property of the dealers will again be empty. That is why I put the ToArray() call in the following line:
var dealers = query.ToArray().Select(o => o.Dealer);
But I agree with Darren that this may not be what the users of your library are expecting.
Are you sure this is what you want? The only reason I ask is, once you add the filter on Parts off of Dealerships, your results are no longer Dealerships. You're dealing in special objects that are, for the most part, very close to Dealerships (with the same properties), but the meaning of the "Parts" property is different. Instead of being a relationship between Dealerships and Parts, it's a filtered relationship.
Or to put it another way, if I pull a dealership out of your results and passed to a method I wrote, and then in my method I call:
var count = dealership.Parts.Count();
I'm expecting to get the parts, not the filtered parts from Brazil where the price is less than $100.
If you don't use the dealership object to pass the filtered data, it becomes very easy. It becomes as simple as:
var query = from d in dealerships
select new { DealershipName = d.Name,
CheapBrazilProducts = dealership.Parts.Where(d => d.parts.Any(p => p.price < 100.00) || d.parts.suppliers.Any(s => s.country == "brazil")) };
If I just had to get the filtered sets like you asked, I'd probably use the technique I mentioned above, and then use a tool like Automapper to copy the filtered results from my anonymous class to the real class. It's not incredibly elegant, but it should work.
I hope that helps! It was an interesting problem.
I know this can work with one single Include. Never test with two includes, but worth the try:
dealerships
.Include( d => d.parts)
.Include( d => d.parts.suppliers)
.Where(d => d.parts.All(p => p.price < 100.00) && d.parts.suppliers.All(s => s.country == "brazil"))
Am I missing something, or aren't you just looking for the Any keyword?
var query = dealerships.Where(d => d.parts.Any(p => p.price < 100.00) ||
d.parts.suppliers.Any(s => s.country == "brazil"));
Yes that's what I wanted to do I think the next realease of Data Services will have the possiblity to do just that LINQ to REST queries that would be great in the mean time I just switched to load the inverse and Include the related entity that will be loaded multiple times but in theory it just have to load once in the first Include like in this code
return this.Context.SearchHistories.Include("Handle")
.Where(sh => sh.SearchTerm.Contains(searchTerm) && sh.Timestamp > minDate && sh.Timestamp < maxDate);
before I tried to load for any Handle the searchHistories that matched the logic but don't know how using the Include logic you posted so in the mean time I think a reverse lookup would be a not so dirty solution
Maybe a simple question, I'm trying to get a result from a table where the Name column contains all of an array of search terms. I'm creating a query and looping through my search strings, each time assigning the query = query.Where(...);. It appears that only the last term is being used, I supposed because I am attempting to restrict the same field each time. If I call .ToArray().AsQueryable() with each iteration I can get the cumlative restrinction behavior I'm looking for, but it there an easy way to do this using defered operators only?
Thanks!
If you're doing something like:
foreach (int foo in myFooArray)
{
query = query.where(x => x.foo == foo);
}
...then it will only use the last one since each where criteria will contain a reference to the 'foo' loop variable.
If this is what you're doing, change it to:
foreach (int foo in myFooArray)
{
int localFoo = foo;
query = query.where(x => x.foo == localFoo);
}
...and everything should be fine again.
If this is not what is happening, please provide a code sample of what you're doing...