I'm trying to simplify a LINQ expression but no matter what i try I'm unable to get it to work
var filterProfileIds = filter.Profiles.Select(s => s.ProfileId);
var newList = new List<FileMedia>();
foreach (var item in filterProfileIds)
{
newList.AddRange(query.Where(w => w.Profiles.Select(s => s.ProfileId).Contains(item)));
}
newList.AddRange(query.Where(w => !w.Profiles.Any()));
query = newList.AsQueryable();
query is of type "FileMedia" and has a relation to Profiles.
So what i want is all the results from the query that has the same profiles that filter.profiles has AND i also want all the results from the query that doesnt have any profiles at all.
Try as the below:
var filterProfileIds = filter.Profiles.Select(s => s.ProfileId);
query = query.Where(w =>
!w.Profiles.Any() ||
w.Profiles.Any(i => filterProfileIds.Contains(i.ProfileId))
).ToList();
If I understand correctly the requirement, you could use a combination of Any and All extension methods like this:
query = query.Where(m => !m.Profiles.Any() ||
filterProfileIds.All(id => m.Profiles.Any(p => p.ProfiledId == id)));
This is if you wish to get the items with exact the same profiles as the filter.
If you indeed want to get the item with any profile contained in the filter, then you could use this instead:
query = query.Where(m => !m.Profiles.Any() ||
m.Profiles.Any(p => filterProfileIds.Contains(p.ProfiledId));
Maybe something like this:
query = (from item in filter.Profiles.Select(s => s.ProfileId)
from fileMedia in query
where fileMedia.Profiles.Select(q => q.ProfileId).Contains(item)
select fileMedia).
Concat(query.Where(w => !w.Profiles.Any())).AsQueryable();
Related
Consider the following LINQ statements:
var model = getModel();
// apptId is passed in, not the order, so get the related order id
var order = (model.getMyData
.Where(x => x.ApptId == apptId)
.Select(y => y.OrderId));
var orderId = 0;
var orderId = order.LastOrDefault();
// see if more than one appt is associated to the order
var apptOrders = (model.getMyData
.Where(x => x.OrderId == orderId)
.Select(y => new { y.OrderId, y.AppointmentsId }));
This code works as expected, but I could not help but think that there is a more efficient way to accomplish the goal ( one call to the db ).
Is there a way to combine the two LINQ statements above into one? For this question please assume I need to use LINQ.
You can use GroupBy method to group all orders by OrderId. After applying LastOrDefault and ToList will give you the same result which you get from above code.
Here is a sample code:
var apptOrders = model.getMyData
.Where(x => x.ApptId == apptId)
.GroupBy(s => s.OrderId)
.LastOrDefault().ToList();
Entity Framework can't translate LastOrDefault, but it can handle Contains with sub-queries, so lookup the OrderId as a query and filter the orders by that:
// apptId is passed in, not the order, so get the related order id
var orderId = model.getMyData
.Where(x => x.ApptId == apptId)
.Select(y => y.OrderId);
// see if more than one appt is associated to the order
var apptOrders = model.getMyData
.Where(a => orderId.Contains(a.OrderId))
.Select(a => a.ApptId);
It seems like this is all you need:
var apptOrders =
model
.getMyData
.Where(x => x.ApptId == apptId)
.Select(y => new { y.OrderId, y.AppointmentsId });
I've set up a search textbox where the search will grab every word individually and search through a field using Contains.
Is there a way to search an array of string through Contains?
//Keep in mind that the array would be generated dynamically through textbox
string[] searchWords = { "hello", "world", "today" };
var articles = _swmDbContext.Articles
.Include(c => c.Category)
.Where(a => a.Title.Contains(searchWords));
searchWords obiviously does not work but trying to show what I want to achieve. searchWords[0] works because it is just one word.
I also tried below as suggested in other links but now the WHERE clause does not show up in query when i run debugger or profiler:
`var articles = _swmDbContext.Articles
.Include(c => c.Category)
.Where(a => searchWords.Any(w => a.Title.Contains(w)));
`
It seems like Entity Framework Core does not translate .Any and .All with .Contains in the above query to SQL statements. Instead it loads all otherwise matching data and does the search in memory.
If you want to find Articles which contain all search words in the Title you could dynamically add .Where conditions (I had a test database with Persons and a Comment field):
var query = (IQueryable<Person>)dbContext.Persons
.Include(p => p.TaxIdentificationNumber);
foreach (var searchWord in searchWords)
{
query = query.Where(p => p.Comment.Contains(searchWord));
}
var persons = query.ToList();
But if you want to find articles which contain any of the search words then you would need an OR in the .Where clause.
Written manually it would look like this:
.Where(p => p.Comment.Contains(searchWords[0]) || p.Comment.Contains(searchWords[1]))
But you can build the expression dynamically:
Expression<Func<Person, bool>> e1 = p => p.Comment.Contains(searchWords[0]);
Expression<Func<Person, bool>> e2 = p => p.Comment.Contains(searchWords[1]);
Expression<Func<Person, bool>> e3 = p => p.Comment.Contains(searchWords[2]);
var orExpression1 = Expression.OrElse(e1.Body, Expression.Invoke(e2, e1.Parameters[0]));
var orExpression2 = Expression.OrElse(orExpression1, Expression.Invoke(e3, e1.Parameters[0]));
var finalExpression = Expression.Lambda<Func<Person, bool>>(orExpression2, e1.Parameters);
and use it like this:
var persons = dbContext.Persons.Where(finalExpression).ToList();
as a function:
Expression<Func<Person, bool>> BuildOrSearchExpression(string[] searchWords)
{
// searchWords must not be null or empty
var expressions = searchWords.Select(s => (Expression<Func<Person, bool>>)(p => p.Comment.Contains(s))).ToList();
if (expressions.Count == 1) return expressions[0];
var orExpression = expressions.Skip(2).Aggregate(
Expression.OrElse(expressions[0].Body, Expression.Invoke(expressions[1], expressions[0].Parameters[0])),
(x, y) => Expression.OrElse(x, Expression.Invoke(y, expressions[0].Parameters[0])));
return Expression.Lambda<Func<Person, bool>>(orExpression, expressions[0].Parameters);
}
and use it
var persons = dbContext.Persons
.Include(p => p.TaxIdentificationNumber)
.Where(BuildOrSearchExpression(searchWords))
.ToList();
If you exchange the .OrElse with .AndAlso all search words must be found like with multiple .where clauses.
When I did some research I also stumbled upon the PredicatedBuilder http://www.albahari.com/nutshell/predicatebuilder.aspx and this SearchExtension https://stackoverflow.com/a/31682364/5550687. But I have not tried them and I don't know if they work with EF Core.
I want to be able to find all orders with items that contain BOTH apples and oranges that I have in a list.
var itemToFind = new List<string>()
{
"apples",
"cookies"
};
How can I rewrite this so that Contains is dynamic?
This returns what I want but how do I make it loop through my list so that it is dynamic?
var query = result
.Where(o => o.OrderItems
.Any(i => i.Item.Name.Contains("apples")))
.Select(x => x)
.Where(y => y.OrderItems
.Any(b => b.Item.Name.Contains("cookies"))).ToList();
// returns 2 orders
Try something like this:
result.Where(o => o.OrderItems.Any(i => itemToFind.All(itf => i.Item.Name.Contains(itf)))).ToList()
This seems to work but not sure if that is the best way.
foreach (var item in listFacets)
{
// append where clause within loop
result = result
.Where(r => r.RecipeFacets
.Any(f => f.Facet.Slug.Contains(item)));
}
I have two options to filter my query:
var users = query.Select(x => x.User).FindUsers(searchString);
query = query.Where(x => users.Contains(x.User));
or
var userIds = query.Select(x => x.User).FindUsers(searchString).Select(x => x.Id);
query = query.Where(x => userIds.Contains(x.UserId));
Description of FindUsers extension:
static IQueryable<User> FindUsers(this IQueryable<User> query, string searchString);
So what I will have in SQL finally? Which of these two requests are better for perfomance?
If someone has another suggestion write it in answer please.
Thanks in advance!
Both query are similar in EF v6 or higher; Contains condition will be translated to Sql EXISTS
Take a loot to the code below :
using (var dbContext = new DbContextTest("DatabaseConnectionString"))
{
var users = dbContext.Users.Select(u => u).Where(x => x.UserId > 0);
var query = dbContext.Users.Where(x => users.Contains(x));
Console.WriteLine(query.ToList());
}
using (var dbContext = new DbContextTest("DatabaseConnectionString"))
{
var ids = dbContext.Users.Select(u => u.UserId).Where(x => x > 0);
var query = dbContext.Users.Where(x => ids.Contains(x.UserId));
Console.WriteLine(query.ToList());
}
The output Sql queries are excatly the same (you can use profiler or EF logger to see that). One thing maybe is important, selecting only the Id's will be more agile for materialisation and cache.
Tip:
If you add ToList() in the Ids query dbContext.Users.Select(u => u.UserId).Where(x => x > 0).ToList(); then this fix will imporve your result performance. The next select query will be translated with SQL "IN" instead of of "EXISTS"! Take a look to this link Difference between EXISTS and IN in SQL? and you can decide what is better for you.
Note:
ToList() will materialize the Id's, that means you will work with another interface then IQueryable!
I need to join multiple criteria inside a linq query, I have a criteria box like below :
Currently Im using a query than can only handle a single tag :
var c = *text after the t:*
var r = rs.Returns.Where(x => x.Lines.Any(y => y.Tags.Any(z => z.Name.Contains(c))));
I need something like (this may be incorrect) :
var r = rs.Returns.Where(x => x.Lines.Any(y => y.Tags.Any(z => z.Name.Contains(*1st Tag*)) && y.Tags.Any(z.Name.Contains(*2nd Tag*)))); .. etc
So that all the tags the Line has are searched and AND is applied. Is there an easy way of achieving such a thing?
Many thanks in advance.
var r = rs.Returns.Where(x => x.Lines.Any(y => searchTags.All(stag => y.Tags.Any(z => z.Name.Contains(stag)))));
searchTags should contain all tags to search for. No need to use a loop.
I think you are looking for something like this:
List<string> tags = new List<string>() { "tag1", "tag2" };
var query = rs.Returns.AsEnumerable();
foreach(string tag in tags)
{
string tmp = tag;
query = query.Where(x => x.Lines.Any(y => y.Tags.Any(z => z.Name.Contains(tmp))));
}