Multiple Any() in one Where() clause LINQ - c#

Is it possible to use several Any() in one where() clause ?
For example, If I need to get favourite beers, this query will do the job:
var favouriteDrinks = drinks
.Where(f => favouriteBeers
.Any(d => d.drinkID == f.drinkID));
But what if I need to get favourite Beers and favourite Wines ? I am looking for something like this:
var favouriteDrinks = drinks
.Where(f => favouriteBeers.Any(d => d.drinkID == f.drinkID) ||
f => favouriteWines.Any(d => drinkID == f.drinkID));

var favouriteDrinks = drinks
.Where(f => favouriteBeers.Any(d => d.drinkID == f.drinkID) ||
favouriteWines.Any(d => d.drinkID == f.drinkID));

why not do it like this:
var favouriteDrinks = drinks.Where(f =>
favouriteBeers.Any(d => d.drinkID == f.drinkID)) ||
favouriteWines.Any(d => d.drinkID == f.drinkID)));
also you can use Contains:
var favouriteDrinks = drinks.Where(f =>
favouriteBeers.Contains(f.drinkID) ||
favouriteWines.Contains(f.drinkID));

You can use .Union() and .Join()
var favouriteDrinks = favouriteBeers
.Union(favouriteWines)
.Join(drinks,
x => x.drinkID,
y => y.drinkID,
(x,y) => y
);
This will work as long as favouriteBeers, and favouriteWines are of the same type.

This fixes Tim.Tang's second example I believe. There are several approaches in here, but if it comes down to "contains" vs "any", I prefer contains as the intention is much more clear to me.
var favouriteDrinks = drinks.Where(d =>
favouriteBeers.Select(b => b.drinkId).Contains(d.drinkID) ||
favouriteWines.Select(w => w.drinkId).Contains(d.drinkID));
Similarly, with the correct IEquatable interfaces implemented on your "drink" classes, you can slightly simplify too
var favouriteDrinks = drinks.Where(d =>
favouriteBeers.Contains(d) || favouriteWines.Contains(d));

Related

Using two Linq query in a single method

As shown in the below code, the API will hit the database two times to perform two Linq Query. Can't I perform the action which I shown below by hitting the database only once?
var IsMailIdAlreadyExist = _Context.UserProfile.Any(e => e.Email == myModelUserProfile.Email);
var IsUserNameAlreadyExist = _Context.UserProfile.Any(x => x.Username == myModelUserProfile.Username);
In order to make one request to database you could first filter for only relevant values and then check again for specific values in the query result:
var selection = _Context.UserProfile
.Where(e => e.Email == myModelUserProfile.Email || e.Username == myModelUserProfile.Username)
.ToList();
var IsMailIdAlreadyExist = selection.Any(x => x.Email == myModelUserProfile.Email);
var IsUserNameAlreadyExist = selection.Any(x => x.Username == myModelUserProfile.Username);
The .ToList() call here will execute the query on database once and return relevant values
Start with
var matches = _Context
.UserProfile
.Where(e => e.Email == myModelUserProfile.Email)
.Select(e => false)
.Take(1)
.Concat(
_Context
.UserProfile
.Where(x => x.Username == myModelUserProfile.Username)
.Select(e => true)
.Take(1)
).ToList();
This gets enough information to distinguish between the four possibilities (no match, email match, username match, both match) with a single query that doesn't return more than two rows at most, and doesn't retrieve unused information. Hence about as small as such a query can be.
With this done:
bool isMailIdAlreadyExist = matches.Any(m => !m);
bool isUserNameAlreadyExist = matches.LastOrDefault();
It's possible with a little hack, which is grouping by a constant:
var presenceData = _Context.UserProfile.GroupBy(x => 0)
.Select(g => new
{
IsMailIdAlreadyExist = g.Any(x => x.Email == myModelUserProfile.Email),
IsUserNameAlreadyExist = g.Any(x => x.Username == myModelUserProfile.Username),
}).First();
The grouping gives you access to 1 group containing all UserProfiles that you can access as often as you want in one query.
Not that I would recommend it just like that. The code is not self-explanatory and to me it seems a premature optimization.
You can do it all in one line, using ValueTuple and LINQ's .Aggregate() method:
(IsMailIdAlreadyExist, IsUserNameAlreadyExist) = _context.UserProfile.Aggregate((Email:false, Username:false), (n, o) => (n.Email || (o.Email == myModelUserProfile.Email ? true : false), n.Username || (o.Username == myModelUserProfile.Username ? true : false)));

C# Linq multi-word search

I have the following, where searchby is a string
var products = db.Products
.Where(p => p.Category.Name == category
&& p.Active == true
&& (searchby == null || (searchby != null && p.Keywords.Contains(searchby))))
.Include(p => p.Category)
.OrderBy(p => p.Description)
.ThenBy(p => p.Name);
and would like to change it to allow searchby to contain multiple words which would filter the results to records where Keywords contains all of the words in searchby.
Thanks in advance
You can use another collection and either Enumerable.All(not sure if supported by your LINQ provider) or !Enumerable.Any:
List<string> searchby = ... (empty if there is no filter)
var products = db.Products
.Where(p => p.Category.Name == category
&& p.Active == true
&& !searchby.Any(s => !p.Keywords.Contains(s)))
.Include(p => p.Category)
.OrderBy(p => p.Description)
.ThenBy(p => p.Name);
If supported this is more readable:
&& searchby.All(s => p.Keywords.Contains(s)))
This answer assumes, that searchby is either null or an Array.
Since contains only checks for 1 item, you need to find a way to check for all items within searchby. The Enumerable.All-Method comes to mind. Here is the relevant part:
searchby.All(searchItem => p.Keywords.Contains(searchItem))
Source

Simplify multiple nhibernate queryover

I'm doing a query over two different tables.
In the first query, i get some Ids that I then have to check in another table.
Then I do the first query again with the result of the second query.
This can't be the best way to do this.
But I haven't found a good way to solve it. So some help would be appreciated.
IntOrderInvoiceCostOut y = null;
var list = session.QueryOver<IntOrderInvoiceCostOut>(() => y)
.Where(x => x.IntegrationHandleDate == null)
.Select(Projections.Distinct(Projections.Property(() => y.Externalid)))
.List<string>();
var nonPreliminaryOrders = session.QueryOver<RefImplOrderEntity>()
.WhereRestrictionOn(x => x.ExternalId).IsIn(list.ToList())
.Where(x => x.StatusTypeId != 95)
.Select(x => x.ExternalId)
.List<string>();
var finalList = session.QueryOver<IntOrderInvoiceCostOut>()
.WhereRestrictionOn(x => x.Externalid).IsIn(nonPreliminaryOrders.ToList())
.Where(x => x.IntegrationHandleDate == null)
.OrderBy(x => x.IntegrationCreateDate)
.Asc
.List();
The code works...but i't really ugly.
you could use detacheCriteria for this. I have omitted couple of conditions and you might have to twick a bit as per your requirement.
for example
IntOrderInvoiceCostOut y = null;
var list = QueryOver.Of<IntOrderInvoiceCostOut>(() => y)
.Where(x => x.IntegrationHandleDate == null)
.Select(Projections.Distinct(Projections.Property(() => y.Externalid)))
.DetachedCriteria;
var nonPreliminaryOrders = QueryOver.Of<RefImplOrderEntity>()
.Where(Subqueries.PropertyIn(nameof(RefImplOrderEntity.ExternalId), list));
.Select(x => x.ExternalId)
.DetachedCriteria
var finalList = session.QueryOver<IntOrderInvoiceCostOut>()
.Where(Subqueries.PropertyIn(nameof(IntOrderInvoiceCostOut.ExternalId), nonPreliminaryOrders));
.List();

Linq result if null then zero

How do I write something like this:
int result = database
.Where(x => x.Name == "Criteria")
.Sum(x => x.Count)) ?? 0;
Where it will return the sum value unless linq does not find anything in which case it will return 0.
EDIT: The field is not null-able.
EDIT 2: I am using Entity Framework.
You were very close with your original query. You only needed to cast your Count variable:
int result = database
.Where(x => x.Name == "Criteria")
.Sum(x => (int?)x.Count) ?? 0;
Doing it this way would be a little more efficient and elegant than wrapping it in a Try/Catch.
I suspect you are using Entity Framework. If you were just using Linq-to-Objects, the solutions everybody else have provided would have worked.
This should work fine (no need for ?? 0):
var result = database
.Where(x => x.Name == "Criteria")
.Sum(x => x.Count))
Unless you want to check if x itself is null or not:
var result = database
.Where(x => x != null)
.Where(x => x.Name == "Criteria")
.Sum(x => x.Count))
You can just write:
int result = database
.Where(x => x.Name == "Criteria")
.Sum(x => x.Count));
The Enumerable.Sum method already returns zero on no results. From the documentation:
returns zero if source contains no elements.
This should work just fine:
var result = database.Where(x => x.Name == "Criteria").Sum(x => x.Count));
If no elements are returned by the Where function then the Sum function will return 0.
All of the Linq functions that return an IEnumerable<T> will return an empty collection instead of null.
Use the Aggregate extension method where 0 is a seed value
int sum = database.Where(x=>x.Name == "Criteria")
.Aggregate(0, (total, next) => total +=next);
I did it in a way that no one is going to like but garrantee to work 100% of the time, behold!
int result = 0;
try{
result = database
.Where(x => x.Name == "Criteria")
.Sum(x => x.Count));
} catch (Exception e){ }

FindAll conditions with different relevance

About the homework:
There are casters(witch(0)/fairy(1)) and they have spellpower(int). I stored them in a list.
I'm to find the best of both types. (There can be multiple casters with the same spellpower)
I've come up with this code, but there is a problem. If the caster with the most spellpower is a 1, then the first FindAll won't return anything, because it tries to find the caster with type 0 AND with the most spellpower. How can I get a list containing type 0 caster(s) with the most spellpower, if the caster with the most overall spellpower is type 1?
private List<Caster> BestCasters()
{
List<Caster> temp = new List<Caster>();
temp = casters.FindAll(x => x.SpellPower == casters.Max(y => y.SpellPower) && (x.TypeOfCaster == 0));
temp.AddRange(casters.FindAll(x => x.SpellPower == casters.Max(y => y.SpellPower) && (x.TypeOfCaster == 1)));
temp.OrderBy(x => x.TypeOfCaster).ThenBy(y => y.CasterName);
return temp;
}
The LINQ GroupBy behavior is perfect for this:
var strongest_casters = casters.GroupBy(c => c.TypeOfCaster)
.Select(grp => grp.OrderByDescending(x => x.SpellPower)
.First()
);
Or to return more than one of each type:
var strongest_casters = casters.GroupBy(c => c.TypeOfCaster)
.SelectMany(grp => grp.Where(y.SpellPower == grp.Max(x => x.SpellPower))
);
private List<Caster> BestCasters()
{
var witches = casters.Where(x => x.TypeOfCaster == 0).ToList();
var fairies = casters.Where(x => x.TypeOfCaster == 1).ToList();
int witchesMax = witches.Max(x => x.SpellPower);
int fairiesMax = fairies.Max(x => x.SpellPower);
var temp = witches.Where(x => x.SpellPower == witchesMax).ToList();
temp.AddRange(fairies.Where(x => x.SpellPower == fairiesMax));
return temp.OrderBy(x => x.TypeOfCaster).ThenBy(y => y.CasterName).ToList();
}
If you have to use FindAll like this you should invoke the Max on a subset only containing the casters of the right kind. Of course it would make more sense to split the initial list first and then fetch the strongest caster of each kind.
Since you did not tell what exactly you have to do I can only hope that you are allowed to split :-)

Categories

Resources