Chaining where clauses returns 0 objects - c#

For a schoolproject i have to make a simple website where i have to search members based on what the user types and then display these members. I have 5 properties to look for (first and lastname, sex, level, club). They have to work together but some might be null.
public List<Lid> ZoekLeden(string deelVoornaam, string deelNaam, string geslacht, string niveau, string deelClubNaam)
{
var filteredleden = context.Leden.Include(lid => lid.ClubNrNavigation)
.OrderBy(l => l.Naam)
.Where(l => l.Voornaam.Contains(deelVoornaam))
.Where(l => l.Naam.Contains(deelNaam))
.Where(l => l.Geslacht.Equals(geslacht))
.Where(l => l.Niveau.Equals(niveau))
.Where(l => l.ClubNrNavigation.Naam.Contains(deelClubNaam))
.ToList();
return filteredleden;
}
Sadly it keep returning 0 and i dont know why.
I tested each individual property and they all return the proper members. But the moment i use multiple they always return 0. Does anyone know why this happends and how i can fix it?

Try this
public List<Lid> ZoekLeden(string deelVoornaam, string deelNaam, string geslacht, string niveau, string deelClubNaam)
{
IQueryable<Lid> filteredleden = context.Leden
.Include(lid => lid.ClubNrNavigation);
if (!string.IsNullOrEmpty(deelVoornaam))
filteredleden = filteredleden.Where(l => l.Voornaam.Contains(deelVoornaam));
if (!string.IsNullOrEmpty(deelNaam))
filteredleden = filteredleden.Where(l => l.Naam.Contains(deelNaam))
if (!string.IsNullOrEmpty(geslacht))
filteredleden = filteredleden.Where(l => l.Geslacht.Equals(geslacht))
if (!string.IsNullOrEmpty(niveau))
filteredleden = filteredleden.Where(l => l.Niveau.Equals(niveau))
if (!string.IsNullOrEmpty(deelClubNaam))
filteredleden = filteredleden.Where(l => l.ClubNrNavigation.Naam.Contains(deelClubNaam));
return filteredleden.OrderBy(l => l.Naam).ToList();
}

You're chaining .Where() which is the equivalent of if(bool && bool && bool). Instead, I think you need if(bool || bool || bool)
So the LINQ statement should be:
var filteredleden = context.Leden.Include(lid => lid.ClubNrNavigation)
.OrderBy(l => l.Naam)
.Where(l => l.Voornaam.Contains(deelVoornaam)
|| l.Naam.Contains(deelNaam)
|| l => l.Geslacht.Equals(geslacht)
|| l => l.Niveau.Equals(niveau)
|| l => l.ClubNrNavigation.Naam.Contains(deelClubNaam)
)
.ToList();
If you really do need the &&'s, then theres probably nothing in your dataset that meets all the criteria in the chained .Where()s

Related

Why EF code is not selecting a single column?

I have used this to pick just a single column from the collection but it doesn't and throws casting error.
ClientsDAL ClientsDAL = new DAL.ClientsDAL();
var clientsCollection= ClientsDAL.GetClientsCollection();
var projectNum = clientsCollection.Where(p => p.ID == edit.Clients_ID).Select(p => p.ProjectNo).ToString();
Method:
public IEnumerable<Clients> GetClientsCollection(string name = "")
{
IEnumerable<Clients> ClientsCollection;
var query = uow.ClientsRepository.GetQueryable().AsQueryable();
if (!string.IsNullOrEmpty(name))
{
query = query.Where(x => x.Name.Contains(name));
}
ClientsCollection = (IEnumerable<Clients>)query;
return ClientsCollection;
}
As DevilSuichiro said in comments you should not cast to IEnumerable<T> just call .AsEnumerable() it will keep laziness.
But in your case it looks like you do not need that at all because First or FirstOrDefault work with IQueryable too.
To get a single field use this code
clientsCollection
.Where(p => p.ID == edit.Clients_ID)
.Select(p => p.ProjectNo)
.First() // if you sure that at least one item exists
Or (more safe)
var projectNum = clientsCollection
.Where(p => p.ID == edit.Clients_ID)
.Select(p => (int?)p.ProjectNo)
.FirstOrDefault();
if (projectNum != null)
{
// you find that number
}
else
{
// there is no item with such edit.Clients_ID
}
Or even simpler with null propagation
var projectNum = clientsCollection
.FirstOrDefault(p => p.ID == edit.Clients_ID)?.ProjectNo;

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)));

Multiple Any() in one Where() clause LINQ

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));

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