I have the following code with linq query:
class Program
{
static void Main(string[] args)
{
var allProfessionals = new Collection<Professional>
{
new Professional { Name = "Bruno Paulovich Silva" },
new Professional { Name = "Ivan Silva Paulovich Bruno"},
new Professional { Name = "Camila Campos"}
};
var namesSearch = new[] {"bruno", "silva"};
var query = namesSearch.Aggregate(allProfessionals.AsQueryable(), (current, nome) => current.Where(oh => oh.Name.ToLower().Contains(nome.ToLower())));
foreach (var res in query.ToList())
{
Console.WriteLine(res.Name.ToLower());
}
}
}
The result is:
Bruno Paulovich Silva
Silva Paulovich Bruno
I wonder know how I could turn linq query Aggregate for a generic method where I can re-use at another time.
in the example below I show what I understand of a generic query that is uses in the project:
public IQueryable<T> QueryBy(Expression<Func<T, bool>> criteria)
{
return DbSet.Where(criteria);
}
ps: sorry for my bad english
Sticking to bare essentials and to the same output as your script, it would be something like:
class Program
{
static void Main(string[] args)
{
var allProfessionals =
new Collection<Professional>
{
new Professional {Name = "Bruno Paulovich Silva"},
new Professional {Name = "Ivan Silva Paulovich Bruno"},
new Professional {Name = "Camila Campos"}
};
var namesSearch = new[] {"bruno", "silva"};
var items = allProfessionals
.Select(x => x.Name)
.ContainsAll(namesSearch);
foreach (var res in items)
{
Console.WriteLine(res);
}
}
}
static class Extensions
{
public static IEnumerable<string> ContainsAll(this IEnumerable<string> haystacks, IEnumerable<string> needles)
{
var lowerNeedles = needles.Select(x => x.ToLower()).ToList();
var lowerHay = haystacks.Select(x => x.ToLower()).ToList();
// note that Regex may be faster than .Contains with larger haystacks
return lowerNeedles
.Where(hay => lowerHay.All(hay.Contains)); // or .Any(), depending on your requirements
}
}
Note that if you use LinqToSQL or similar technologies, table indexes may not be used. This may render the query very slow.
In order to make it fit with the mentioned QueryBy<T> it may look like:
var items = allProfessionals.QueryBy(
professional => namesSearch
.Select(needle => needle.ToLower()) // convert all to lower case
.All(hay => professional.Name.ToLower().Contains(hay))); // then try to search for a professional that matches all nameSearch.
Here is how I implement a method for LikeBy where an array of string is equivalent to a contains all.
public IQueryable<T> LikeBy(string[] strings, Func<IQueryable<T>, string, IQueryable<T>> criteria)
{
return strings.Aggregate(GetAll(), criteria);
}
Here the example of how implement:
public IQueryable<ViaturaFuncao> FindByNome(string names)
{
return Uow.Professional.LikeBy(names.Trim().Split(' '),
(professional, nameProfessional) =>
professional.Where(
f => f.Name.ToLower().Contains(nameProfessional.ToLower())));
}
PS: In the project we're using UnitOfWork and that is why has the UOW before the entity
Related
I have a table like this:
variety category quantity
----------------------------------------------
rg pm 10
gs pm 5
rg com 8
I want to make a GroupBy based on these bool parameters:
IncludeVariety
IncludeCategory
eg:
IncludeVariety = true;
IncludeCategory = true;
would return this:
variety category quantity
----------------------------------------------
rg pm 10
gs pm 5
rg com 8
and this:
IncludeVariety = true;
IncludeCategory = false;
would return this:
variety category quantity
----------------------------------------------
rg - 18
gs - 5
and this:
IncludeVariety = false;
IncludeCategory = true;
would return this:
variety category quantity
----------------------------------------------
- pm 15
- com 8
You get the idea...
Question: How can I achieve this with LINQ?
Important: I've reduced the problem to two bool variables (IncludeVariety and IncludeCategory) but in reality I will be having more columns (say five)
I can't figure out how to generate the query dynamically (the .GroupBy and the .Select):
rows.GroupBy(r => new { r.Variety, r.Category })
.Select(g => new
{
Variety = g.Key.Variety,
Category = g.Key.Category,
Quantity = g.Sum(a => a.Quantity),
});
rows.GroupBy(r => new { r.Category })
.Select(g => new
{
Variety = new {},
Category = g.Key.Category,
Quantity = g.Sum(a => a.Quantity),
});
rows.GroupBy(r => new { r.Variety })
.Select(g => new
{
Variety = g.Key.Variety,
Category = new {},
Quantity = g.Sum(a => a.Quantity),
});
A similar thing I've done in the past is concatenate Where's, like:
var query = ...
if (foo) {
query = query.Where(...)
}
if (bar) {
query = query.Where(...)
}
var result = query.Select(...)
Can I do something like that here?
var results=items
.Select(i=>
new {
variety=includevariety?t.variety:null,
category=includecategory?t.category:null,
...
})
.GroupBy(g=>
new { variety, category, ... }, g=>g.quantity)
.Select(i=>new {
variety=i.Key.variety,
category=i.Key.category,
...
quantity=i.Sum()
});
shortened:
var results=items
.GroupBy(g=>
new {
variety=includevariety?t.variety:null,
category=includecategory?t.category:null,
...
}, g=>g.quantity)
.Select(i=>new {
variety=i.Key.variety,
category=i.Key.category,
...
quantity=i.Sum()
});
If you need this to be truly dynamic, use Scott Gu's Dynamic LINQ library.
You just need to figure out what columns to include in your result and group by them.
public static IQueryable GroupByColumns(this IQueryable source,
bool includeVariety = false,
bool includeCategory = false)
{
var columns = new List<string>();
if (includeVariety) columns.Add("Variety");
if (includeCategory) columns.Add("Category");
return source.GroupBy($"new({String.Join(",", columns)})", "it");
}
Then you could just group them.
var query = rows.GroupByColumns(includeVariety: true, includeCategory: true);
In any case someone still needs grouping by dynamic columns without Dynamic LINQ and with type safety. You can feed this IQueryable extension method an anonymous object containing all properties that you might ever want to group by (with matching type!) and a list of property names that you want to use for this group call. In the first overload I use the anonymous object to get it's constructor and properties. I use those to build the group by expression dynamically. In the second overload I use the anonymous object just for type inference of TKey, which unfortunately there is no way around in C# as it has limited type alias abilities.
Does only work for nullable properties like this, can probably easily be extended for non nullables but can't be bothered right now
public static IQueryable<IGrouping<TKey, TElement>> GroupByProps<TElement, TKey>(this IQueryable<TElement> self, TKey model, params string[] propNames)
{
var modelType = model.GetType();
var props = modelType.GetProperties();
var modelCtor = modelType.GetConstructor(props.Select(t => t.PropertyType).ToArray());
return self.GroupByProps(model, modelCtor, props, propNames);
}
public static IQueryable<IGrouping<TKey, TElement>> GroupByProps<TElement, TKey>(this IQueryable<TElement> self, TKey model, ConstructorInfo modelCtor, PropertyInfo[] props, params string[] propNames)
{
var parameter = Expression.Parameter(typeof(TElement), "r");
var propExpressions = props
.Select(p =>
{
Expression value;
if (propNames.Contains(p.Name))
value = Expression.PropertyOrField(parameter, p.Name);
else
value = Expression.Convert(Expression.Constant(null, typeof(object)), p.PropertyType);
return value;
})
.ToArray();
var n = Expression.New(
modelCtor,
propExpressions,
props
);
var expr = Expression.Lambda<Func<TElement, TKey>>(n, parameter);
return self.GroupBy(expr);
}
I implemented two overloads in case you want to cache the constructor and properties to avoid reflection each time it's called. Use like this:
//Class with properties that you want to group by
class Record
{
public string Test { get; set; }
public int? Hallo { get; set; }
public DateTime? Prop { get; set; }
public string PropertyWhichYouNeverWantToGroupBy { get; set; }
}
//usage
IQueryable<Record> queryable = ...; //the queryable
var grouped = queryable.GroupByProps(new
{
Test = (string)null, //put all properties that you might want to group by here
Hallo = (int?)null,
Prop = (DateTime?)null
}, nameof(Record.Test), nameof(Record.Prop)); //This will group by Test and Prop but not by Hallo
//Or to cache constructor and props
var anonymous = new
{
Test = (string)null, //put all properties that you might want to group by here
Hallo = (int?)null,
Prop = (DateTime?)null
};
var type = anonymous.GetType();
var constructor = type.GetConstructor(new[]
{
typeof(string), //Put all property types of your anonymous object here
typeof(int?),
typeof(DateTime?)
});
var props = type.GetProperties();
//You need to keep constructor and props and maybe anonymous
//Then call without reflection overhead
queryable.GroupByProps(anonymous, constructor, props, nameof(Record.Test), nameof(Record.Prop));
The anonymous object that you will receive as TKey will have only the Keys that you used for grouping (namely in this example "Test" and "Prop") populated, the other ones will be null.
In the link https://msdn.microsoft.com/en-us/library/bb534631(v=vs.110).aspx, the third signature is
// M<S> -> (S -> M<C>) -> (S -> M<C> -> R) -> E<R>
public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(
this IEnumerable<TSource> source,
Func<TSource, IEnumerable<TCollection>> collectionSelector,
Func<TSource, TCollection, TResult> resultSelector
)
Shouldn't the first one which has the signature of the typical Monad bind function M<A> -> (A -> M<B>) -> M<B> be enough? Isn't it easy to merge code in the resultSelector into collectionSelector?
The MSDN document gave an example to show the usage of the method.
class Program
{
static void Main(string[] args)
{
SelectManyEx3();
}
public static void SelectManyEx3()
{
PetOwner[] petOwners =
{ new PetOwner { Name="Higa",
Pets = new List<string>{ "Scruffy", "Sam" } },
new PetOwner { Name="Ashkenazi",
Pets = new List<string>{ "Walker", "Sugar" } },
new PetOwner { Name="Price",
Pets = new List<string>{ "Scratches", "Diesel" } },
new PetOwner { Name="Hines",
Pets = new List<string>{ "Dusty" } } };
// Project the pet owner's name and the pet's name.
var query =
petOwners
.SelectMany(petOwner => petOwner.Pets, (petOwner, petName) => new { petOwner, petName })
.Where(ownerAndPet => ownerAndPet.petName.StartsWith("S"))
.Select(ownerAndPet =>
new
{
Owner = ownerAndPet.petOwner.Name,
Pet = ownerAndPet.petName
}
);
// Print the results.
foreach (var obj in query1)
{
Console.WriteLine(obj);
}
}
}
class PetOwner
{
public string Name { get; set; }
public List<string> Pets { get; set; }
}
However, the var query = ... can be rewritten using the SelectMany of the first signature as following?
var query = petOwners.SelectMany(o => o.Pets.Select(p => new { petOwner = o, petName =p}))
When will the SelectMany with the third signature be really useful?
It would have been more helpful if they used the example like below. See how I added a Where clause in the SelectMany. This overload gives you the flexibility to run a query and then do a projection on the result of that query:
// Project the pet owner's name and the pet's name.
var query =
petOwners
.SelectMany(petOwner => petOwner.Pets.Where(x => x.StartsWith("S")), (petOwner, petName) => new { petOwner, petName })
//.Where(ownerAndPet => ownerAndPet.petName.StartsWith("S"))
.Select(ownerAndPet =>
new
{
Owner = ownerAndPet.petOwner.Name,
Pet = ownerAndPet.petName
}
);
The 3rd and 4th overloads of the SelectMany( ) method with the additional “result selector” parameter take a bit more explanation.
The extra selector parameter is trying to help you know and understand the relationship between the parent and child collections. The code is more concise concerned the relationship.
A result selector is an intermediate object available within the scope of the query to give you the information you need, and it’s up to you to decide what data you need in the result selector to help you.
Say you need a result collection that will not only have the full list of Teams that match some criteria in all leagues but need to know what League the teams are in.
For example:
var teamsAndTheirLeagues = from helper in leagues
.SelectMany( l => l.Teams, ( league, team )
=> new { league, team } )
where helper.team.Players.Count > 2
&& helper.league.Teams.Count < 10
select new { LeagueID = helper.league.ID, Team = helper.team };
This syntax gives you extra ability to query both the team and the league in the where clause and you are not "losing" backward connection of the parent-child relationship.
More detailed explanation about selectMany projections can be found here and here
As commonly known in EF-Core there is no Lazy loading. So that kind of means I'm forced to do my queries with some afterthought. So since I have to think, then i might as well try to do it properly.
I have a Fairly standard update query, but I thought hey, I don't always have to include the HeaderImage and PromoImage FK-objects. There should be a way to make that happen. But I can just not find a way to perform a Include at a later point. In-fact I would like to maybe include right before I actually do work on the object. That way i might be able to automate some of the eagerness.
ArticleContent ac = _ctx.ArticleContents
.Include(a=> a.Metadata)
.Include(a=> a.HeaderImage)
.Include(a=> a.PromoImage)
.Single(a => a.Id == model.BaseID);
ac.Title = model.Title;
ac.Ingress = model.Ingress;
ac.Body = model.Body;
ac.Footer = model.Footer;
if (model.HeaderImage != null)
{
ac.HeaderImage.FileURL = await StoreImage(model.HeaderImage, $"Header_{model.Title.Replace(" ", "_")}_{rand.Next()}");
}
if (model.PromoImage != null)
{
ac.PromoImage.FileURL = await StoreImage(model.PromoImage, $"Promo_{model.Title.Replace(" ", "_")}_{rand.Next()}");
}
ac.Metadata.EditedById = uId;
ac.Metadata.LastChangedTimestamp = DateTime.Now;
await _ctx.SaveChangesAsync();
EXTRA
To be clear, this is EF7 (Core), and im after a solution that allows me to add includes on demand, hopefully after the initial _ctx.ArticleContents.Include(a=> a.Metadata).Single(a => a.Id == model.BaseID).
I'm using something similar to Alexander Derck's solution. (Regarding the exception mentioned in the comments: ctx.ArticleContents.AsQueryable() should also work.)
For a couple of CRUD MVC sites I'm using a BaseAdminController. In the derived concrete Controllers I can add Includes dynamically. From the BaseAdminController:
// TModel: e.g. ArticleContent
private List<Expression<Func<TModel, object>>> includeIndexExpressionList = new List<Expression<Func<TModel, object>>>();
protected void AddIncludes(Expression<Func<TModel, object>> includeExpression)
{
includeIndexExpressionList.Add(includeExpression);
}
Later I saw that I need more flexibility, so I added a queryable. E.g. for ThenInclude().
private Func<IQueryable<TModel>, IQueryable<TModel>> IndexAdditionalQuery { get; set; }
protected void SetAdditionalQuery(Func<IQueryable<TModel>, IQueryable<TModel>> queryable)
{
IndexAdditionalQuery = queryable;
}
Here the Index action:
public virtual async Task<IActionResult> Index()
{
// dynamic include:
// dbset is for instance ctx.ArticleContents
var queryable = includeIndexExpressionList
.Aggregate(dbSet.AsQueryable(), (current, include) => current.Include(include));
if(IndexAdditionalQuery != null) queryable = IndexAdditionalQuery(queryable);
var list = await queryable.Take(100).AsNoTracking().ToListAsync();
var viewModelList = list.Map<IList<TModel>, IList<TViewModel>>();
return View(viewModelList);
}
In the concrete Controller I use:
AddIncludes(e => e.EventCategory);
SetAdditionalQuery(q => q
.Include(e => e.Event2Locations)
.ThenInclude(j => j.Location));
I would create a method to fetch the data which takes the properties to include as expressions:
static ArticleContent GetArticleContent(int ID,
params Expression<Func<ArticleContent, object>>[] includes)
{
using(var ctx = new MyContext())
{
var acQuery = _ctx.ArticleContents.Include(a=> a.Metadata);
foreach(var include in includes)
acQuery = acQuery.Include(include);
return acQuery.Single(a => a.Id == model.BaseID);
}
}
And then in main method:
var ac = GetArticleContent(3, a => a.HeaderImage, a => a.PromoImage);
I'm constructing a simple search function for a site. The aim is to allow the user to find 'swimmers' and add them to a list via a search box, using a query like "bob staff".
The first part of this I decided to tackle was allowing the user to search via a group name (In the database all swimmers are part of a group). The following is the code I have at the moment.
[HttpGet]
public JsonResult searchForSwimmers(string q)
{
//designed to return [{name='foo',id='bar'}]
String[] QueryTerms = q.Split(' '); //all the search terms are sep. by " "
var groupResults = _db.SwimGroups.Where(g => g.Name.ContainsAny(QueryTerms))
.OrderByDescending(g => g.Name.StartsWithAny(QueryTerms) ? 1 : 0)
.ThenBy( g => g)
.Select(g => new { name = g.Name, id = g.ID });
return Json(groupResults,JsonRequestBehavior.AllowGet);
}
On line 8, there is a method invoked called StartsWithAny. This is an extension method I defined in the following file:
public static class StringUtils
{
public static Boolean StartsWithAny(this String str, params String[] Fragments)
{
foreach (String fragment in Fragments)
{
if (str.StartsWith(fragment))
{
return true;
}
}
return false;
}
}
The idea is that if a Name starts with one of the terms then it should be ranked higher in relevancy. I recognize that this logic is naïve and has flaws however I thought it would be a good example to illustrate the problem I've having. The code compiles however when searchForSimmers is invoked in my cshtml page with the following: (using the tokenInput library)
<script type="text/javascript">
$(document).ready(function () {
$("#demo-input-local").tokenInput("/Admin/searchForSwimmers");
});
</script>
I get a 500 internal server error. The error message is as follows:
LINQ to Entities does not recognize the method 'Boolean ContainsAny(System.String, System.String[])' method, and this method cannot be translated into a store expression.
The ContainsAny method
public static Boolean ContainsAny(this String str, List<String> Fragments)
{
foreach (String fragment in Fragments)
{
if(str.Contains(fragment))
{
return true;
}
}
return false;
}
I've had a look around but couldn't find a solution to the problem. Any help would be greatly appreciated, cheers.
Check out my blog where I create a search extension method for IQueryable.
UPDATED: I have created a new blog post showing the extension method required to acheive your goal
http://www.ninjanye.co.uk/2013/04/generic-iqueryable-or-search-for.html
http://jnye.co/Posts/8/generic-iqueryable-or-search-for-multiple-search-terms-using-expression-trees
The problem is linq does not know how to translate your code into SQL.
By adding the following extension method:
public static class QueryableExtensions
{
public static IQueryable<T> Search<T>(this IQueryable<T> source, Expression<Func<T, string>> stringProperty, params string[] searchTerms)
{
if (!searchTerms.Any())
{
return source;
}
Expression orExpression = null;
foreach (var searchTerm in searchTerms)
{
//Create expression to represent x.[property].Contains(searchTerm)
var searchTermExpression = Expression.Constant(searchTerm);
var containsExpression = BuildContainsExpression(stringProperty, searchTermExpression);
orExpression = BuildOrExpression(orExpression, containsExpression);
}
var completeExpression = Expression.Lambda<Func<T, bool>>(orExpression, stringProperty.Parameters);
return source.Where(completeExpression);
}
private static Expression BuildOrExpression(Expression existingExpression, Expression expressionToAdd)
{
if (existingExpression == null)
{
return expressionToAdd;
}
//Build 'OR' expression for each property
return Expression.OrElse(existingExpression, expressionToAdd);
}
private static MethodCallExpression BuildContainsExpression<T>(Expression<Func<T, string>> stringProperty, ConstantExpression searchTermExpression)
{
return Expression.Call(stringProperty.Body, typeof(string).GetMethod("Contains"), searchTermExpression);
}
}
This will allow you to write the following lambda:
[HttpGet]
public JsonResult searchForSwimmers(string q)
{
//designed to return [{name='foo',id='bar'}]
String[] QueryTerms = q.Split(' '); //all the search terms are sep. by " "
var groupResults = _db.SwimGroups.Search(g => g.Name, queryTerms)
.OrderByDescending(g => g.Name.StartsWithAny(QueryTerms) ? 1 : 0)
.ThenBy( g => g)
.Select(g => new { name = g.Name, id = g.ID });
return Json(groupResults,JsonRequestBehavior.AllowGet);
}
That's because neither your ContainsAny or StartsWithAny extension methods can be translated into SQL.
As your database table is small (as noted in comments), just resolve your query, by calling .ToList() before you do the Where and OrderBy.
Try this:
var groupResults = _db.SwimGroups
.ToList() //evaluate the query, bring it into memory
.Where(g => g.Name.ContainsAny(QueryTerms))
.OrderByDescending(g => g.Name.StartsWithAny(QueryTerms) ? 1 : 0)
.ThenBy( g => g)
.Select(g => new { name = g.Name, id = g.ID });
The following search method works fine for up to two terms.
How can I make it dynamic so that it is able to handle any number of search terms?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TestContains82343
{
class Program
{
static void Main(string[] args)
{
List<string> tasks = new List<string>();
tasks.Add("Add contract to Customer.");
tasks.Add("New contract for customer.");
tasks.Add("Create new contract.");
tasks.Add("Go through the old contracts.");
tasks.Add("Attach files to customers.");
var filteredTasks = SearchListWithSearchPhrase(tasks, "contract customer");
filteredTasks.ForEach(t => Console.WriteLine(t));
Console.ReadLine();
}
public static List<string> SearchListWithSearchPhrase(List<string> tasks, string searchPhrase)
{
string[] parts = searchPhrase.Split(new char[] { ' ' });
List<string> searchTerms = new List<string>();
foreach (string part in parts)
{
searchTerms.Add(part.Trim());
}
switch (searchTerms.Count())
{
case 1:
return (from t in tasks
where t.ToUpper().Contains(searchTerms[0].ToUpper())
select t).ToList();
case 2:
return (from t in tasks
where t.ToUpper().Contains(searchTerms[0].ToUpper()) && t.ToUpper().Contains(searchTerms[1].ToUpper())
select t).ToList();
default:
return null;
}
}
}
}
How about replacing
switch (searchTerms.Count())
{
case 1:
return (from t in tasks
where t.ToUpper().Contains(searchTerms[0].ToUpper())
select t).ToList();
case 2:
return (from t in tasks
where t.ToUpper().Contains(searchTerms[0].ToUpper()) && t.ToUpper().Contains(searchTerms[1].ToUpper())
select t).ToList();
default:
return null;
}
By
(from t in tasks
where searchTerms.All(term => t.ToUpper().Contains(term.ToUpper()))
select t).ToList();
Just call Where repeatedly... I've changed the handling of searchTerms as well to make this slightly more LINQ-y :)
public static List<string> SearchListWithSearchPhrase
(List<string> tasks, string searchPhrase)
{
IEnumerable<string> searchTerms = searchPhrase.Split(' ')
.Select(x => x.Trim());
IEnumerable<string> query = tasks;
foreach (string term in searchTerms)
{
// See edit below
String captured = term;
query = query.Where(t => t.ToUpper().Contains(captured));
}
return query.ToList();
}
You should note that by default, ToUpper() will be culture-sensitive - there are various caveats about case-insensitive matching :( Have a look at this guidance on MSDN for more details. I'm not sure how much support there is for case-insensitive Contains though :(
EDIT: I like konamiman's answer, although it looks like it's splitting somewhat differently to your original code. All is definitely a useful LINQ operator to know about...
Here's how I would write it though:
return tasks.Where(t => searchTerms.All(term => t.ToUpper().Contains(term)))
.ToList();
(I don't generally use a query expression when it's a single operator applied to the outer query.)
EDIT: Aargh, I can't believe I fell into the captured variable issue :( You need to create a copy of the foreach loop variable as otherwise the closure will always refer to the "current" value of the variable... which will always be the last value by the time ToList is executed :(
EDIT: Note that everything so far is inefficient in terms of uppercasing each task several times. That's probably fine in reality, but you could avoid it by using something like this:
IEnumerable<string> query = tasks.Select
(t => new { Original = t, Upper = t.ToUpper });
return query.Where(task => searchTerms.All(term => task.Upper.Contains(term)))
.Select(task => task.Original)
.ToList();
Can't test code right now, but you could do something similar to this:
from t in tasks
let taskWords=t.ToUpper().Split(new char[] { ' ' });
where searchTerms.All(term => taskWords.Contains(term.ToUpper()))
select t
Replace the switch statement with a for loop :)
[TestMethod]
public void TestSearch()
{
List<string> tasks = new List<string>
{
"Add contract to Customer.",
"New contract for customer.",
"Create new contract.",
"Go through the old contracts.",
"Attach files to customers."
};
var filteredTasks = SearchListWithSearchPhrase(tasks, "contract customer new");
filteredTasks.ForEach(Console.WriteLine);
}
public static List<string> SearchListWithSearchPhrase(List<string> tasks, string searchPhrase)
{
var query = tasks.AsEnumerable();
foreach (var term in searchPhrase.Split(new[] { ' ' }))
{
string s = term.Trim();
query = query.Where(x => x.IndexOf(s, StringComparison.InvariantCultureIgnoreCase) != -1);
}
return query.ToList();
}
why not use a foreach and AddRange() after splitting the terms and saving it into a list.
List<ItemsImLookingFor> items = new List<ItemsImLookingFor>();
// search for terms
foreach(string term in searchTerms)
{
// add users to list
items.AddRange(dbOrList(
item => item.Name.ToLower().Contains(str)).ToList()
);
}
that should work for any amount of terms.