How can I build a single Linq statement where it check returns all records unless the param is passed? If param is empty then ignore specific 'where'.
I have tried using IsNullOrEmpty within the WHERE but I get an error.
Here are the NON-REQUIRED form field for searching for Invoices.
Invoice Id, Check Number, State Issued
var invoices = ctx.Invoices; <-- get all invoiced
if (inputInvoiceId > 0)
invoices = from i in invoices
where i.id == inputInvoiceId
select i;
if (!string.IsNullOrEmpty(inputCheckNumber))
invoices = from i in invoices
where i.checkNumber == inputCheckNumber
select i;
if (!string.IsNullOrEmpty(inputState))
invoices = from i in invoices
where i.state == inputState
select i;
You could build your query by conditionally appending where clauses like this:
var invoices = ctx.Invoices.AsQueryable();
if (inputInvoiceId > 0)
invoices = invoices.Where(x => x.id == inputInvoiceId);
if (!string.IsNullOrEmpty(inputCheckNumber))
invoices = invoices.Where(x => x.checkNumber == inputCheckNumber);
if (!string.IsNullOrEmpty(inputState))
invoices = invoices.Where(x => x.state == inputState);
return invoices.ToList();
Each additional where clause further filters your results, but the query itself won't be executed (nor any data retrieved) until you call ToList().
What #GrantWinney said will work. Alternatively, you can deal with it in a single query, which may or may not have query compilation/cache benefits if you are concerned about such things:
// Query short-circuit parameters
var invoiceNotSpecified = inputVoiceId == 0;
var checkNumberNotSpecificed = String.IsNullOrEmpty(inputCheckNumber);
var stateNotSpecified = String.IsNullOrEmpty(inputState);
// Query
var invoices = from i in ctx.Invoices
where (invoiceNotSpeficied || i.id == inputInvoiceId) &&
(checkNumberNotSpecified || i.checkNumber == inputCheckNumber) &&
(stateNotSpecified || i.state == inputState)
select i;
Related
I have a main table "SALES" and two secondary tables "PRODUCTS" and "SERVICES", I need to select only the records in "SALES" that contain some product or service entered by the user, I don't need to bring the sales records and products, just filter. First I made the filter in the table "SALES" by date of sale:
var query = (from p in _contexto.sales
where p.datesale.Value.Date >= Convert.ToDateTime(strDtI).Date &&
p.datesale.Value.Date <= Convert.ToDateTime(strDtF).Date
select p);
Now let's say the user wants to filter also the sales that have products or services with the words in a string Array
words = ['apple', 'beef', 'cleaning', 'haircut']
if you receive the array of words, I tried the filter below, but it didn't work, it kept bringing all the records.
var queryi = (from i in _contexto.products
where words.Contains(i.name) || words.Contains(i.description) select i);
//var queryj = (from i in _contexto.services
//where words.Contains(i.name) || words.Contains(i.description) select i);
//query = query.Where(p => queryi.All(c => c.idsale != p.id) || queryj.All(c => c.idsale != p.id));
query = query.Where(p => queryi.All(c => c.idsale != p.id));
where am I failing, and is there a better and more performant way to do this?
Thank you!
Using more descriptive variable names, and assuming you meant to only find products that have the exact same name or description as one of the words, you would have:
var salesInPeriod = from s in _contexto.sales
where Convert.ToDateTime(strDtI).Date <= s.datesale.Value.Date &&
s.datesale.Value.Date <= Convert.ToDateTime(strDtF).Date
select s;
var matchingidsales = from p in _contexto.products
where words.Contains(p.name) || words.Contains(p.description)
select p.idsale;
var ans = from s in salesInPeriod
where matchingidsales.Contains(s.id)
select s;
PS: I inverted the date comparison since I think it makes it easier to see you are doing a between test.
In the first picture I have the result of first query, the highlighted part indicates the rows that would be excluded by applying the filter on the second query, in the second I have the result of query select * from exlusion_table
I have to make a change to the first query to have it exclude the items retrieved from the second query
the first query:
var u = from a in cx.VW_LIST
where (a.PRJ == codPrj) && (a.DATE > date_ || a.DATE == null || date_ == null)
&& (x.Contains(a.CODE) || x.Count() == 0)
select a)
the second query:
var y = from esc in cx.EXCLUSION select esc
The first query should be modified to exclude all the rows that have the value fcode = the fcode of the second query (in the case in which the fscode of the second query = null) or that (fcode = fcode of the second query && fscode = fscode of the second query )
You can use Any(). ie:
var u = from a in cx.VW_LIST
where (a.PRJ == codPrj)
&& (a.DATE > date_ || a.DATE == null || date_ == null)
&& (x.Contains(a.CODE) || x.Count() == 0)
&& (!cx.EXCLUSION.Any( y => x.fscode == y.fscode && x.fcode == y.fcode ))
select a)
There are two approaches to this, one is to use ! and ANY() to filter out records found within the other list, this will compile into WHERE NOT EXISTS(_exclusion_) filter expression
var excluded = cx.EXCLUSION.AsQueryable();
var query = from vw in cx.VW_LIST
where vw.PRJ == codPrj
where vw.DATE == null || date_ == null || vw.DATE > date_
where !x.Any() || x.Contains(vw.CODE)
where !excluded.Any(esc => vw.fcode == esc.fcode
&& (esc.fscode == null || vw.fscode == esc.fscode))
select vw;
var results = query.ToList();
The tricky element is the null for fscode in the excluded table, that needs to act as wildcard match, or negate the fscode comparison.
It is not necessary to split the excluded query out into it's own query, we could have referenced to the cx.EXCLUSION table directly and it would have exactly the same effect, this shows you an encapsulation technique for building the LINQ query in a way that you could easily increase the complexity of the exclusion lookup without creating a mess of your overall query.
You may also find need to conditionally build the query, this is where fluent syntax provides a more modular approach:
bool filterExcludedRecords = true;
...
var excluded = cx.EXCLUSION.AsQueryable();
var query = cx.VW_LIST.Where(vw => vw.PRJ == codPrj)
.Where(vw => vw.DATE == null || date_ == null || vw.DATE > date_)
.Where(vw => !x.Any() || x.Contains(vw.CODE));
if(filterExcludedRecords)
query = query.Where(vw => !excluded.Any(esc => vw.fcode == esc.fcode
&& (esc.fscode == null || vw.fscode == esc.fscode)));
var results = query.ToList();
OUTER JOIN
Another method is to use a LFET OUTER JOIN where the exclusion match is not found:
var excluded = cx.EXCLUSION.AsQueryable();
var query = from vw in cx.VW_LIST
where vw.PRJ == codPrj
where vw.DATE == null || date_ == null || vw.DATE > date_
where !x.Any() || x.Contains(vw.CODE)
from esc in excluded.Where(e => vw.fcode == e.fcode
&& (e.fscode == null || vw.fscode == e.fscode))
.DefaultIfEmpty()
where esc.fscode == null
select vw;
var results = query.ToList();
The WHERE NOT EXISTS is often superior in terms of performance, OUTER JOIN may provide better response in an un-optimised table or when the number of rows in the exclusion list is significantly small and the number of rows in the main table is very large.
I include this query option for completeness, it is not well known that you can create simple outer joins by adding a new from clause to the query.
I am trying to write a search functionality wherein the user can search on the basis of Id, UserName and status. The data corresponding to these search filters are in different tables, so I have to put joins on these tables.
Of course user can search on the basis of Id and UserName or UserName and status and all the combinations that can be considered from these 3 filters.
What I have done is made different functions to address these combinations.
Is there a way that this can be achieved with one method using linq. I am trying to avoid the if-else and switch.
I have already looked into
Add Conditional Join Dynamically with Linq
and
LINQ - Joins in a dynamic query
This is my code where I am searching on the basis of all the parameters
public List<Application> GetApplication(int? applicationId, string userName, string status)
{
_applicationlist.Clear();
if (applicationId != 0 && !string.IsNullOrWhiteSpace(userName) && !string.IsNullOrWhiteSpace(status))
{
var application = (from a in _targetDbContext.DbSet<ApplicationEntity>()
join u in _targetDbContext.DbSet<UserEntity>() on a.InDraftPersonnel equals GetUserID(userName)
join v in _targetDbContext.DbSet<ApplicationVersionEntity>() on a.Id equals v.ApplicationId
join s in _targetDbContext.DbSet<ApplicationStatusEntity>() on v.VersionStatus equals s.Id
where a.Id.Equals(applicationId)
where u.UserName.Equals(userName)
where s.StatusName.Equals(status)
select (new Application
{
Id = a.Id,
AppName = a.AppName,
CreatedBy = a.CreatedBy,
CreatedOn = a.CreatedOn
})).FirstOrDefault();
_applicationlist.Add(application);
}
return _applicationlist;
}
The thing is I am not sure if the user would be searching with all the parameters of with just one.
Any help would be appreciated.
Remove if and try this:
where (!applicationid.HasValue || applicationid == 0 || a.Id.Equals(applicationId)) &&
(string.IsNullOrWhiteSpace(userName) || u.UserName.Equals(userName)) &&
(string.IsNullOrWhiteSpace(status) || s.StatusName.Equals(status))
With this where clause all parameters are optional and if one of those is null or empty those parameter don't affect query result and All combination is supported.
With
...
where a.Id.Equals(applicationId) && u.UserName.Equals(userName) && s.StatusName.Equals(status)
...
... all parameters must match
...
where a.Id.Equals(applicationId) || u.UserName.Equals(userName) || s.StatusName.Equals(status)
...
... at least one parameter must match.
...
where a.Id.Equals(applicationId) && u.UserName.Equals(userName) && s.StatusName.Equals(status)
...
is equivalent to
...
where a.Id.Equals(applicationId)
where u.UserName.Equals(userName)
where s.StatusName.Equals(status)
...
See also: Boolean logical operators (C# reference)
Note that you can construct LINQ queries pice-wise.
var query = source.Join(..) ...;
if (applicationId.HasValue) {
query = query.Where(x => x.Id == applicationId.Value);
}
if (!String.IsNullOrEmpty(userName)) {
query = query.Where(x => x.UserName == userName);
}
if (!String.IsNullOrEmpty(status)) {
query = query.Where(x => x.StatusName == status);
}
query = query.Select(x => ...);
I have an EF query which gets products from the database.
var query = (from pPrice in db.ProductPricing
join prod in db.Products on pPrice.ProductID equals prod.ProductID
join productExt in db.ProductsExt on prod.ProductID equals productExt.ProductID into pExts
from prodExt in pExts.DefaultIfEmpty()
where (includeNonPublic || pPrice.ShowOnline == 1)
&& ((eventID.HasValue && pPrice.EventID == eventID) || (!eventID.HasValue && !pPrice.EventID.HasValue))
orderby prod.DisplayOrder
select new ProductPricingInfo()
{
Product = prod,
ProductPricing = pPrice,
ProductExtension = prodExt
});
I have a table where I can specify add-on products (products which can be bought once the parent item has been bought).
My query to fetch these add-on products is
var addOnProductsQuery = (from pa in db.ProductAddons
where pa.EventID == eventID && pa.StatusID == 1
select new { ProductID = pa.ChildProductId });
Now what I am trying to do is filter on the query variable to only return products which are not in the addOnProductsQuery result.
Currently I have
var addOnProducts = addOnProductsQuery.ToList();
query = query.Where(e => !addOnProducts.Contains(e.Product.ProductID));
But there is a syntax error on the Contains(e.Product.ProductID)) statement
Argument 1: cannot convert from int to anonymous type: int ProductID
Chetan is right in the comments, you need to select the integer from the object before using Contains.
You can do it in 2 ways:
First you could just take the integer initially:
var addOnProductsQuery = (from pa in db.ProductAddons
where pa.EventID == eventID && pa.StatusID == 1
select new pa.ChildProductId);
This should give int as type so you can use Contains later with no problem.
Second, if you want to keep the addOnProductsQuery unchanged:
var addOnProducts = addOnProductsQuery.Select(a => a.ProductID).ToList();
query = query.Where(e => !addOnProducts.Contains(e.Product.ProductID));
I have this scenario in which i query with FindByMany (which takes the lambda and returns post if user country and category matches, (as seen in the "else")
But now i need to customize the return with prefered subcategories from users, so what im doing is query n times foreach subcategory and just addRange. I dont want to query 5 times the db if the user has 5 subcategories as favorite, but i dont know how to apply a dinamic OR.
So my question is, how can this code be improved for performance.
var posts = new List<Content>();
if (request.UserId != 0)
{
var user = _userRepository.FindBy(u => u.Id == request.UserId);
if (user != null && user.SubCategories.Any())
{
foreach (var temp in user.SubCategories.Select(subCategory => _contentRepository.FindManyBy(
c =>
c.Country.Id == country.Id && c.Category.Id == theCategory.Id &&
c.SubCategory.Id == subCategory.Id).ToList()))
{
posts.AddRange(temp);
}
}
}
else
{
posts = _contentRepository.FindManyBy(
c => c.Country.Id == country.Id && c.Category.Id == theCategory.Id
).ToList();
}
Could you not just materalise the sub-categories into a list, and then in your FindBy use a thatlist.Contains()?
You can get the user's sub-categories with one query and then use the list and the Contains method to filter the relevant posts. Contains method is supported by most LINQ query provides and should be translated into a single database query.
var subcategories = user.SubCategories.ToList();
foreach (var temp in _contentRepository.FindManyBy(
c =>
c.Country.Id == country.Id && c.Category.Id == theCategory.Id &&
subcategories.Contains( subCategory.Id ) ).ToList()))
{
posts.AddRange(temp);
}
You can build expression for where clause using Expression.OrElse or use enter link description here
The core of the problem is that you're forcing query execution for each item instead of dynamically building the query. #Milney has the right idea; example code below.
IEnumerable<int> subCategoryIds = user.SubCategories.Select(x => x.Id);
var posts = _contentRepository.FindByMany(c => c.Country.Id == country.Id
&& c.Category.Id == theCategory.Id
&& subCategoryIds.Contains(c.SubCategoryId)).ToList();