Trying to work out this lambda query without doing a foreach etc. I currently have a database table with a column which contains a comma separated list of strings. It's basically a filter (it could for example be 'pants,tops,t-shirts,gloves'). In the function that queries the database is basically has a parameter that accepts a similar string.
I don't know if I'm just too tired at the moment and can't work it out but struggling. I know it will be Intersect but can't figure out the syntax.
Currently I have...
public static List<ItemListItem> GetItems(string filter = "")
{
var db = new dbConnection();
var results = (from i in db.Items
select i);
if (!string.IsNullOrEmpty (filter))
results = results.Where(x => x.Filters.Split(',').Intersect(filter.Split(',')) )
}
You need Enumerable.Any at the end of your Intersect like:
x.Filters.Split(',').Intersect(filter.Split(',')).Any()
So your query would be:
results = results.Where(x => x.Filters.Split(',')
.Intersect(filter.Split(','))
.Any());
Enumerable.Where would require an expression returning bool. Intersect would return an IEnumerable<T> and Enumerable.Where would not accept it. Adding Enumerable.Any would means return those rows where intersection resulted in Any row.
Related
I am trying the following code in LinqPad 5 (specifically 5.26.01)
IEnumerable<string> breeds = new List<string>{
"Fantail",
"Lahore",
"Bokhara Trumpeter",
"Rhine Ringbeater",
"Birmingham Roller",
"Pomeranian Pouter",
"Racing Homer",
"Archangel"};
IEnumerable<string> GetAllBreedsContainingLetter_Fluent(IEnumerable<string> breedlist, string letter)
{
return breedlist
.Where(breedname => breedname.Contains(letter.ToUpperInvariant()) || breedname.Contains(letter.ToLowerInvariant()))
.OrderByDescending(breedname => breedname)
.Select(breedname => breedname);
}
IEnumerable<string> GetAllBreedsContainingLetter_Query(IEnumerable<string> breedlist, string letter)
{
return breedlist = from b in breedlist
where (b.Contains(letter.ToUpperInvariant()) || b.Contains(letter.ToLowerInvariant()))
orderby b descending
select b;
}
var breedsFluent = GetAllBreedsContainingLetter_Fluent(breeds, "R");
breedsFluent.Dump();
var breedsQuery = GetAllBreedsContainingLetter_Query(breeds, "R");
breedsQuery.Dump();
I think the two functions should be analogous but I noticed something odd about the output in Linqpad. The first .Dump() is identified as an IEnumerable<String>; the second .Dump() identifies as a IOrderedEnumerable<String>.
Is this something about the queries I'm running or is it an artifact of Linqpad? I haven't found anything from Googling.
In query syntax the transformation is such that when you have a trivial projection (projecting an item to itself) it only generates a Select call when that trivial projection is the only operation in the query. Since your query contains other operations, the Select is elided entirely, for performance reasons.
A proper translation of that query syntax query into method syntax would skip the Select. The way to replicate the behavior using query syntax would require something like a second query, to do the trivial projection.
In my project, I need to return a list of car data that does not match the model Id' provided in the array. I'm unsure on how I would go in getting my query to work. So far i have the following below:
var IdList = new List<int> { 60,61,62, 63, 64, 65 };
var query = Context.ManufacturersTable.Where(m => m.Date == date && m.CountryToship = country && m.ExportOnly == false);
if(query.Count() > 0)
query = query.Where(x => x.CarMoreInfoTable.CarModelTable.Where(f => IdList.Contains(f.ModelId))) //Cannot convert lambda expression to intended delegate type error here
return query
As you can see in the above query I have 3 tables all linked to each other. But please could some direct me on how I would query all data that does not contain the Id's in the given array?
Thank you
You have an error because second 'Where' is the issue here, first Where expects to delegate method return bool instead of IEnumerable. (Comment edit) And I think you have to negate:
IdList.Contains(f.ModelId)
Edit
If you need Car Data (I will assume that it is inside CarModel class) then you need change first Where to SelectMany (we would like to merge many collections into one) and negate the inner expression (with contains).
var result = query
.SelectMany(x => x.CarMoreInfoTable.CarModelTable
.Where(f => !IdList.Contains(f.ModelId))).ToList();
I am trying to implement the following algorithm using LINQ-to-SQL:
Given a list of strings L, return every row R in the DB for which every string in L is a substring of one of the column values in R.
The question is how do I do this iteratively for every string in L? I don't know how I can slickly put it all into one Linq-To-SQL statement. Note that I have no problem writing code along the lines of:
field1.contains(...) || field2.contains(...) || ...
as there are not that many columns.
E.g., if the input is
["Charlie", "Doctor", "Kor"]
we would output all rows that have a field with "Charlie" as a substring, a field with "Doctor" as a substring, and a field with "Kor" as a substring.
One approach I thought of was to make separate SQL queries for each input value and to take the intersection of all of those.
Another approach is to pick just one of the strings from the input, make a SQL query on that, convert it to a list, and filter out the rest of the strings one at a time using just LINQ in C#.
Any thoughts on an optimal way to do this?
I would try All extension method (EF6 supports it, not sure about LINQ to SQL):
List<string> values = new List<string> { "Charlie", "Doctor", "Kor" };
var query = db.Table
.Where(r => values.All(v => r.Field1.Contains(v) || r.Field2.Contains(v) || ...));
Update: Well, the assumption was wrong - as mentioned in the comments, unfortunately LINQ to SQL does not support the above construct (shame on them).
As usual in such cases, I would build dynamically a corresponding predicate expression.
In this particular case we need something like this (for N fields and M values):
r => (r.Field1.Contains(value1) || r.Field2.Contains(value1) ... || r.FieldN.Contains(value1))
&& (r.Field1.Contains(value2) || r.Field2.Contains(value2) ... || r.FieldN.Contains(value2))
...
&& (r.Field1.Contains(valueM) || r.Field2.Contains(valueM) ... || r.FieldN.Contains(valueM));
And here is a custom extension method which does that:
public static class QueryableExtensions
{
public static IQueryable<T> WhereContainsAll<T>(
this IQueryable<T> source,
IEnumerable<string> values,
params Expression<Func<T, string>>[] members)
{
var parameter = Expression.Parameter(typeof(T), "r");
var body = values
.Select(value => members
.Select(member => (Expression)Expression.Call(
Expression.MakeMemberAccess(parameter, ((MemberExpression)member.Body).Member),
"Contains", Type.EmptyTypes, Expression.Constant(value)))
.Aggregate(Expression.OrElse))
.Aggregate(Expression.AndAlso);
var predicate = Expression.Lambda<Func<T, bool>>(body, parameter);
return source.Where(predicate);
}
}
and the sample usage would be
List<string> values = new List<string> { "Charlie", "Doctor", "Kor" };
var query = db.Table.WhereContainsAll(values,
r => r.Field1, r => r.Field2, r => r.Field3, ...);
which should lead to a single SQL query which IMO should be optimal because the heavy work will be done by the database engine. Of course the query most likely will cause full table scan, but the same will happen even with single Contains (SQL LIKE) criteria.
Try this (I made an example using Lists):
var dbValues = new List<string> {"hello", "how", "are", "you"};
var substrings = new List<string> {"ello", "re"};
var result = dbValues.Where(i => substrings.Any(l => i.Contains(l))).ToList();
Result will contain {"hello","are"}
Example with database:
using (var db = new MyDatabase())
{
var substrings = new List<string> { "ello", "re" };
var result = db.MyTable.Where(i => substrings.Any(l => i.Value.Contains(l))).ToList();
}
I have users searching records of type Record. They type a search term in a textbox and then I search records by matching several fields with the search term.
My query looks like:
var results = from record in DataContext.Records
where
record.Field1.ToLower().Contains(term) ||
record.Field2.ToLower().Contains(term) ||
record.Field3.ToLower().Contains(term)
select record;
I have a number of queries that all use the same filter and thus I would like to extract the filtering so it can be reused. Something like:
var filter = new Func<Record, string, bool>(
(record, term) =>
record.Field1.ToLower().Contains(term) ||
record.Field2.ToLower().Contains(term) ||
record.Field3.ToLower().Contains(term)
);
var results = from record in DataContext.Records
where filter(record, term)
select record;
However, it does not work because:
Method 'System.Object DynamicInvoke(System.Object[])' has no supported translation to SQL.
How can I reuse my where condition across queries?
You need to build an expression instead of a function:
Expression<Func<Record, bool>> filter =
record => record.Field1.ToLower().Contains(term); // rest omitted
The lambda expression remains the same, but you need to return it into a variable of type Expression<Func<Record, bool>> -- that will make the C# compiler compile it as an expression instead of a delegate, allowing it to be passed to LINQ to SQL.
However, you won't be able to use an expression variable with a C#-syntax where clause: you'll need to use the Where extension method:
var results = DataContext.Records.Where(filter);
Edited to add: If you want to be able to create filters on different terms, you just need a method to produce an expression from a term:
private static Expression<Func<Record, bool>> Filter(string term)
{
return r => r.Field1.ToLower().Contains(term);
}
var results = DataContext.Records.Where(Filter(term));
If you prefer to keep filter as a lambda as you have at the moment, you can do so, but the generics get a bit nested:
Func<string, Expression<Func<Record, bool>>> filter =
term => (r => r.Field1.ToLower().Contains(term));
var results = DataContext.Records.Where(filter(term));
Regardless, the important thing is that what goes in the Where clause must be an Expression<Func<Record, bool>> -- but as shown above you can make the expression depend on term by building a suitable expression on the fly. Which is exactly what LINQ to SQL would be doing if you spelled out the filter longhand in the Where clause.
Use a CompiledQuery!
var filter = CompiledQuery.Compile(
(DatabaseDataContext dc, Record record, string term) =>
record.Field1.ToLower().Contains(term) ||
record.Field2.ToLower().Contains(term) ||
record.Field3.ToLower().Contains(term)
);
var results = from record in DataContext.Records
where filter(DataContext, record, term)
select record;
For more information, see How to: Store and Reuse Queries.
In addition to the Expression<Func<Record, bool>> issue that others have pointed out, I suggest looking into PredicateBuilder. It's very good for dynamically combining lambda expressions.
I think you need to make it an Expression<Func<Record, bool>>. Otherwise it's trying to translate the actual C# method call to SQL rather than the description of it. This is not a guarantee that this version will work; I'm not sure which string functions are translatable to SQL.
I've got some code which accepts a DataTable as a parameter and calculates the total of several of the columns in the DataTable. I thought it might be nice to be able to pass in a lambda expression which would perform a filter on the column I'm totaling.
Here's a portion of the code:
public TrafficTotals CalculateTotals(DataTable table)
{
TrafficTotals total = new TrafficTotals();
total.TotalTraffic = table.AsEnumerable().Sum(p => p.Field<int>("Converted"));
// More stuff
I can manually add a filter into the expression directly in the code:
var filteredTotal = table.AsEnumerable().Where(p => p.Field<string>("MyColumn") == "Hello").Sum(p => p.Field<int>("Converted"));
But instead I'd like to pass the "Where" portion as lambda expression instead, but I keep getting lost in the syntax to get the parameters correct.
I have several ways of working around this that don't really involve lambdas but it seems like a nice way of handling this.
Any ideas?
I'm slightly confused because you're already specifying the Where clause with a lambda expression, but I suspect you want this:
public TrafficTotals CalculateTotals(DataTable table,
Func<DataRow, bool> filter)
{
TrafficTotals total = new TrafficTotals();
total.TotalTraffic = table.AsEnumerable()
.Where(filter)
.Sum(p => p.Field<int>("Converted"));
// More stuff
}
You'd then call it with:
totals = CalculateTotals(table,
row => row.Field<string>("MyColumn") == "Hello");
Is that what you're after?
If you want to store the lambda expression, you need to declare it like so.
This would return true if an integer is less than 3 for example:
Func<int,bool> F = o=>o < 3
Then you can pass it around.