Ternary Operator in LINQ Where clause for nullable bool column - c#

Can anyone see anything wrong with the ternary in the where of this linq statement:
var organizations = Context.Set<Domain.Content.Organisation>()
.Where(x => x.ShowCompanyPage == (showCompanyPagesOnly ? true : x.ShowCompanyPage))
if showCompanyPagesOnly is set to true, I get 4 results, this is correct only four companies have ShowCompanyPage = true.
However if I set it to false, I expect 1000+ results (all companies). But I STILL only get 4.
Is my logic not:
if showCompanyPagesOnly is true, then give me results where x.ShowCompanyPage == true
else give me results where x.ShowCompanyPage = whatever is in the column (ie ALL Organisations)
?
x.ShowCompanyPage is a nullable bool column.
Full code:
public Result<IList<Organisation>> GetAllOrganisations(bool showCompanyPagesOnly = false)
{
var result = new Result<IList<Organisation>>();
try
{
var organizations = Context.Set<Domain.Content.Organisation>()
.Where(x => x.ShowCompanyPage == (showCompanyPagesOnly == true ? true : x.ShowCompanyPage)) // show only company pages or show all
.AsNoTracking()
.Select(x => new DataContracts.Content.Organisation
{
Id = x.Id,
Name = x.Name,
OrganisationTypeId = x.OrganisationTypeId,
IsCustomer = x.IsCustomer,
SeoName = x.SeoName,
Description = x.Description,
Website = x.Website
}).OrderBy(x => x.Name).ToList();
result.Data = organizations;
}
catch (Exception ex)
{
result.SetException(ex);
HandleError(ex);
}
return result;
}

Sometimes when logic is getting too complex the best answer is the turn the question upside down, currently you are asking
if showCompanyPagesOnly is true how do i get only the ones with with ShowCompanyPage = true
if you swap that with get everything unless showCompanyPagesOnly is true and your logic becomes a simple OR statement
either showCompanyPagesOnly is not true or ShowCompanyPage is true
which is
x => (!showCompanyPagesOnly) || x.ShowCompanyPage
you may need to make that
x => (!showCompanyPagesOnly) || (x.ShowCompanyPage ?? false)/*default value depends on if you want to treat null as true or false*/)
to take into account the nullability

This is a much better approach, as it will generate two distinct LINQ queries, and this will allow SQL Server to generate two distinct query plans, which can in most cases greatly affect the performance of the queries:
public Result<IList<Organisation>> GetAllOrganisations(bool showCompanyPagesOnly = false)
{
var result = new Result<IList<Organisation>>();
try
{
var organizations = Context.Set<Domain.Content.Organisation>()
.AsNoTracking();
if (showCompanyPagesOnly)
organizations=organization
.Where(x => x.ShowCompanyPage == true);
result.Data = organizations
.Select(x => new DataContracts.Content.Organisation
{
Id = x.Id,
Name = x.Name,
OrganisationTypeId = x.OrganisationTypeId,
IsCustomer = x.IsCustomer,
SeoName = x.SeoName,
Description = x.Description,
Website = x.Website
}).OrderBy(x => x.Name).ToList();
}
catch (Exception ex)
{
result.SetException(ex);
HandleError(ex);
}
return result;
}

Try this:
.Where(x => showCompanyPagesOnly ? x.ShowCompanyPage == true : true)
Here is a fiddle.
The Where() function returns records that satisfy the condition, so the condition must evaluate to a Boolean (i.e. either true or false). If you put the value true in the condition, then you're effectively asking the Where() function to return all records. It is similar to:
if(true){
//do something.
}
As you know this will always execute the "do something".

Related

Flattening Complex LINQ to SQL

I have a somewhat complex LINQ to SQL query that I'm trying to optimise (no, not prematurely, things are slow), that goes a little bit like this;
IQueryable<SearchListItem> query = DbContext.EquipmentLives
.Where(...)
.Select(e => new SearchListItem {
EquipmentStatusId = e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null).Id,
StatusStartDate = e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null).DateFrom,
...
});
The where clauses aren't important, they don't filter EquipmentStatuses, happy to include if someone thinks they're required.
This is on quite a large set of tables and returns a fairly details object, there's more references to EquipmentStatuses, but I'm sure you get the idea. The problem is that there's quite obviously two sub-queries and I'm sure that (among some other things) is not ideal, especially since they are exactly the same sub-query each time.
Is it possible to flatten this out a bit? Perhaps it's easier to do a few smaller queries to the database and create the SearchListItem in a foreach loop?
Here's my take given your comments, and with some assumptions I've made
It may look scary, but give it a try, with and without the ToList() before the GroupBy()
If you have LinqPad, check the SQL produced, and the number of queries, or just plug in the SQL Server Profiler
With LinqPad you could even put a Stopwatch to measure things precisely
Enjoy ;)
var query = DbContext.EquipmentLives
.AsNoTracking() // Notice this!!!
.Where(...)
// WARNING: SelectMany is an INNER JOIN
// You won't get EquipmentLive records that don't have EquipmentStatuses
// But your original code would break if such a case existed
.SelectMany(e => e.EquipmentStatuses, (live, status) => new
{
EquipmentLiveId = live.Id, // We'll need this one for grouping
EquipmentStatusId = status.Id,
EquipmentStatusDateTo = status.DateTo,
StatusStartDate = status.DateFrom
//...
})
// WARNING: Again, you won't get EquipmentLive records for which none of their EquipmentStatuses have a DateTo == null
// But your original code would break if such a case existed
.Where(x => x.EquipmentStatusDateTo == null)
// Now You can do a ToList() before the following GroupBy(). It depends on a lot of factors...
// If you only expect one or two EquipmentStatus.DateTo == null per EquipmentLive, doing ToList() before GroupBy may give you a performance boost
// Why? GroupBy sometimes confuses the EF SQL generator and the SQL Optimizer
.GroupBy(x => x.EquipmentLiveId, x => new SearchListItem
{
EquipmentLiveId = x.EquipmentLiveId, // You may or may not need this?
EquipmentStatusId = x.EquipmentStatusId,
StatusStartDate = x.StatusStartDate,
//...
})
// Now you have one group of SearchListItem per EquipmentLive
// Each group has a list of EquipmenStatuses with DateTo == null
// Just select the first one (you could do g.OrderBy... as well)
.Select(g => g.FirstOrDefault())
// Materialize
.ToList();
You don't need to repeat the FirstOrDefault. You can add an intermediate Select to select it once and then reuse it:
IQueryable<SearchListItem> query = DbContext.EquipmentLives
.Where(...)
.Select(e => e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null))
.Select(s => new SearchListItem {
EquipmentStatusId = s.Id,
StatusStartDate = s.DateFrom,
...
});
In query syntax (which I find more readable) it would look like this:
var query =
from e in DbContext.EquipmentLives
where ...
let s = e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null)
select new SearchListItem {
EquipmentStatusId = s.Id,
StatusStartDate = s.DateFrom,
...
});
There is another problem in your query though. If there is no matching EquipmentStatus in your EquipmentLive, FirstOrDefault will return null, which will cause an exception in the last select. So you might need an additional Where:
IQueryable<SearchListItem> query = DbContext.EquipmentLives
.Where(...)
.Select(e => e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null))
.Where(s => s != null)
.Select(s => new SearchListItem {
EquipmentStatusId = s.Id,
StatusStartDate = s.DateFrom,
...
});
or
var query =
from e in DbContext.EquipmentLives
where ...
let s = e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null)
where s != null
select new SearchListItem {
EquipmentStatusId = s.Id,
StatusStartDate = s.DateFrom,
...
});
Given that you don't test for null after calling FirstOrDefault(s => s.DateTo == null) I assume that:
either for each device there is always a status with DateTo == null or
you need to see only devices which have such status
In order to do so you need to join EquipmentLives with EquipmentStatuses to avoid subqueries:
var query = DbContext.EquipmentLives
.Where(l => true)
.Join(DbContext.EquipmentStatuses.Where(s => s.DateTo == null),
eq => eq.Id,
status => status.EquipmentId,
(eq, status) => new SelectListItem
{
EquipmentStatusId = status.Id,
StatusStartDate = status.DateFrom
});
However, if you do want to perform a left join replace DbContext.EquipmentStatuses.Where(s => s.DateTo == null) with DbContext.EquipmentStatuses.Where(s => s.DateTo == null).DefaultIfEmpty().

Unexpected behavior of the `Where` clause

I have the following Linq statement which is used to retrieve the list of all rows in the table which satisfy a particular condition.
var set = db.TcSet
.Where(x => x.SetName.Equals(original.SetName))
.AsEnumerable()
.Where(x => x.SetName == original.SetName))
This particular statement is used to edit all entities in the table with a particular name. So the initial name of the property is retrieved in the original and checked for all other entries in the database which matches the condition.
Current Output
If I have two entries with the name Play123 and Edit one of the entry the output of the where query only contains one element.
If there is an entry Play123 and Play1234, and I try to edit Play123 to Play1234 , the output of the where query contains two elements : Play123 and Play1234 .
What am I missing that results in this unexpected behavior.
Update
var original = db.TcSet.Find(tcSet.TcSetID);
foreach (var set in db.TcSet
.Where(x => x.SetName.Equals(original.SetName))
.AsEnumerable()
.Where(x => x.SetName == original.SetName)))
{
if (set.SetName == original.SetName) // This was added again due to the unexpected behavior
{
set.ModifiedBy = User.Identity.Name;
set.ModifiedOn = DateTime.Now;
set.PhysicalUnit = tcSet.PhysicalUnit;
db.Entry(set).State = EntityState.Modified;
db.Entry(set).Property(x => x.CreatedBy).IsModified = false;
db.Entry(set).Property(x => x.CreatedOn).IsModified = false;
db.Entry(set).Property(x => x.TechnicalCharacteristicID).IsModified = false;
}
}
Now I have solved the problem by iterating through all the elements in the table which I know isn't the best practice.
Working code
var editlist = db.TcSet.Where(x => x.SetName == original.SetName).AsEnumerable().Where(x=>x.SetName==original.SetName).ToList();
foreach(var set in editlist)
// do save
When I removed the if condition it worked.
You want to do a case-insensitive check in your first .Where() clause, if you are to treat abc and ABC differently.
var set = db.TcSet
.Where(x => x.SetName.Equals(original.SetName, StringComparison.OrdinalIgnoreCase));
I would also suggest to cache the results before iterating in the foreach, as you are changing the source list while iterating through it which is never a good idea.
var original = db.TcSet.Find(tcSet.TcSetID);
var sets = db.TcSet
.Where(x => x.SetName.Equals(original.SetName, StringComparison.OrdinalIgnoreCase))
.ToList();
foreach (var set in sets)
{
set.ModifiedBy = User.Identity.Name;
set.ModifiedOn = DateTime.Now;
set.PhysicalUnit = tcSet.PhysicalUnit;
db.Entry(set).State = EntityState.Modified;
db.Entry(set).Property(x => x.CreatedBy).IsModified = false;
db.Entry(set).Property(x => x.CreatedOn).IsModified = false;
db.Entry(set).Property(x => x.TechnicalCharacteristicID).IsModified = false;
}

Linq remove where if the value is null

That My Linq query
var result = db.APPLICATIONS
.Where(a => Statuses.Contains(a.STATUS_ID))
.Where(a => a.TrackingNo == TrackingNo)
Statuses is a int list and TrackingNo is a nullable int (int?).
Problem:
If the TrackingNo is null then i dont want to run this clause or just skip this condition.
LINQ queries can be built in multiple steps:
var result = db.APPLICATIONS
.Where(a => Statuses.Contains(a.STATUS_ID));
if (TrackingNo != null)
{
result = result.Where(a => a.TrackingNo == TrackingNo);
}
Note that if you have a Select (a projection), you probably must build the query in multiple steps in multiple variables:
var result2 = result.Select(a => new { a.STATUS_ID });
with the result2 "built" after the if.
You can check a nullable int by using its "HasValue" property.
var result = db.APPLICATIONS
.Where(a => Statuses.Contains(a.STATUS_ID))
.Where(a => a.HasValue && (a.TrackingNo == TrackingNo))
This will cause it to evaluate the "HasValue" prior to checking the value itself. If HasValue return false, then it will never evaluate the rest of the expression (and thus not cause NullReferenceException).
If it is of type "int?", then this will work.
Just add && condition and check null. And you can use 1 where condiiton here why second where.Pls try this:
var result = db.APPLICATIONS
.Where(a => Statuses.Contains(a.STATUS_ID)
&& a.TrackingNo!=null
&& a.TrackingNo == TrackingNo)
You should first check the values of the filtering parameters before trying to add more stuff to the store expression. This would only apply the Statuses and TrackingNo filtering if the nullable TrackingNo has a value. Otherwise it will return all APPLICATIONS as IQueryable.
var result = db.APPLICATIONS.AsQueryable();
if (TrackingNo.HasValue)
{
result = result.Where(a => Statuses.Contains(a.STATUS_ID) && a.TrackingNo == TrackingNo);
}
return result;
Alternatively, this would check if you have any statuses to apply and the tracking separatedly.
var result = db.APPLICATIONS.AsQueryable();
if (Statuses != null && Statuses.Count() > 0)
{
result = result.Where(a => Statuses.Contains(a.STATUS_ID));
}
if (TrackingNo.HasValue)
{
result = result.Where(a => a.TrackingNo == TrackingNo);
}
return result;
Or third option, as it is unclear what you really wanted. This would apply the statuses filtering always and tracking only if it is available
var result = db.APPLICATIONS.Where(a => Statuses.Contains(a.STATUS_ID));
if (TrackingNo.HasValue)
{
result = result.Where(a => a.TrackingNo == TrackingNo);
}
return result;

Finding either foo or bar with Linq extensions

I have an object "IdentityProvider" and "IdentityProvider" has child Domains.
class IdentityProvider
{
...
public virtual ICollection<Domain> Domains { get; set; }
...
}
class Domain
{
...
public string Name { get; set; }
...
}
There is a catch all domain called "*"
Using Linq Extensions, I need to find all the IdentityProviders that have either the specified domain, or IdentityProviders that have the catch all, but nor both.
How would I form my query?
Something like this should make it:
from i in identityProviders
let hasDomain = i.Domains.Any(d => d.Name == domainName)
let hasCatchAll = i.Domains.Any(d => d.Name == "*")
where (hasDomain && !hasCatchAll) || (!hasDomain && hasCatchAll)
select i;
You could try using XOR (^) instead in where clause:
from i in identityProviders
let hasDomain = i.Domains.Any(d => d.Name == domainName)
let hasCatchAll = i.Domains.Any(d => d.Name == "*")
where hasDomain ^ !hasCatchAll
select i;
but I'm not sure if it get's translated into SQL by your provider (you didn't specify what kind of LINQ source you're dealing with...).
Your condition can't be checked with standard LINQ functions, without iterating the Domains collection twice which is needlessly inefficient. I would use a custom filter function like this, iterating once and failing early if both are found:
identityProviders.Where(identityProvider => {
bool hasDomain = false, hasCatchAll = false;
foreach (var domain in identityProvider.Domains) {
hasDomain = hasDomain || domain.Name == domainName;
hasCatchAll = hasCatchAll || domain.Name == "*";
if (hasDomain && hasCatchAll) return false;
}
return hasDomain || hasCatchAll;
})
If you group your data by domains which have the catch all such as:
var grouped = ipProviders.Domains
.GroupBy (dm => dm.Name == "*");
Then you can either return all the catch alls at once or extract the target domains with the exact name such as
var targetDomain = "Jabberwocky";
var targets = grouped.Where (gr => gr.Key == (targetDomain == "*"))
.Select (gr => gr.Where (dm => dm.Name == targetDomain));
The grouping looks like this with data of Jabberwocky, OmegaMan and two domains with *
Thanks to those that answered, you helped me in other areas, but for this question I ended up doing the following, probably not the best way, but it works:
See if the domain exists:
var domExists = db.Domains.Any(d => d.Name == domain);
Find all the identity providers where domain exists AND domExists OR find wildcard and not domExists.
IdentityProviders.Where(d =>
d.Domains.Any(n => n.Name == domain && domExists) ||
d.Domains.Any(n => n.Name == "*" && !domExists)
).Any()
The answer you gave doesn't give you what you asked for in the question. You said you wanted providers that had one or the other but not both.
First, if a provider has both, it will be selected by this code because the first condition is true:
d.Domains.Any(n => n.Name == domain && domExists)
Second, if a provider has the catch-all but NOT the specified domain, it will not be selected if the domain does exist in a different provider. This is because domExists will be true, so the second check will fail:
d.Domains.Any(n => n.Name == "*" && !domExists)
I don't see how capturing the domExists flag can really help you. However, I think starting by searching the entire collection of domains is the right idea. You could try this:
First, collect all the IDs of providers for domains that match either "*" or the name (I assume Domain must have a foreign key to IdentityProvider):
var providerIds =
db.Domains.Where(d => d.Name == domain || d.Name == "*")
.Select(d => d.IdentityProviderID)
.ToList();
This narrows it down quite a lot, and we have a way of filtering it again: Any providers that have both will have been added to the list twice, so we just need to select all the IDs that only appear once:
var uniqueProviderIds =
providerIds.GroupBy(id => id)
.Where(g => g.Count() == 1)
.Select(g => g.Key)
.ToList();
Now uniqueProviderIds.Any() will give you your answer. You can also use this list to build another SQL query to get the actual IdentityProvider objects if you need them:
db.IdentityProviders.Where(ip => uniqueProviderIds.Contains(ip.ID)).ToList()
please try
identityProviders.Where(ip=>ip.Domains.Any(d=>d.Name=="SearchDomain" || d.Name=="*"))

Linq query return 1 item instead of all

I am using the query below to grab all records that have a SubCategoryName == subCatName and i want to return all of there ProductID's as a list of ints. Problem is when my code runs it is only returning 1 int(record) instead of all. How can i make it return all of the records that have that subCatName? Its returning a count = 1 with a capacity of 4. So it is a int[4] but only the first [0] is = to a actual product ID the rest returning zero?
public List<int> GetPRodSubCats(string subCatName)
{
var _db = new ProductContext();
if (subCatName != null)
{
var query = _db.ProductSubCat
.Where(x => x.SubCategory.SubCategoryName == subCatName)
.Select(p => p.ProductID);
return query.ToList<int>();
}
return null;
}
As Daniel already has mentioned, the code should work. But maybe you are expecting that it's case-insensitive or ignores white-spaces. So this is more tolerant:
subCatName = subCatName.Trim();
List<int> productIDs = _db.ProductSubCat
.Where(x => String.Equals(x.SubCategory.SubCategoryName.Trim(), subCatName, StringComparison.OrdinalIgnoreCase))
.Select(p => p.ProductID)
.ToList();
This seems more like an expected behavior here. How do you know you don't only have 1 record that satisfies the Where predicate.
Your code is correct, however you might want to normalize your comparison.
x => x.SubCategory.SubCategoryName == subCatName
to use a specific case for instance:
x => x.SubCategory.SubCategoryName.ToLower() == subCatName.ToLower()
you might also consider a Trim.

Categories

Resources