I have the following method:
public IQueryable<Profile> FindAllProfiles(string CountryFrom, string CountryLoc)
{
return db.Profiles.Where(p => p.CountryFrom.CountryName.Equals(CountryFrom,
StringComparison.OrdinalIgnoreCase));
}
What is the best way to write the where clause that would filter all the possible combinations of input parameters in one statement:
BOTH CountryFrom and CountryLoc = null
Only CountryFrom null
Only CountryLoc null
BOTH CountryFrom and CountryLoc are not null.
Soon .. I would need to filter out profiles by Age, Gender, Profession .. you name it.
I am trying to find a way to write it efficiently in C#. I know how to do it in a clean manner in TSQL. I wish I knew the way. Thanks for all the responses so far.
A good old binary XNOR operation will do the trick here:
db.Profiles.Where(p => !(p.CountryFrom == null ^ p.CountryTo == null))
It's effectively equating two booleans, though to me it's more direct, less convoluted even, than writing ((p.CountryFrom == null) == (p.CountryTo == null))!
I would use this simple LINQ syntax...
BOTH CountryFrom and CountryLoc = null
var result = from db.Profiles select p
where (p.CountryFrom == null) && (p.CountryLoc == null)
select p
Only CountryFrom null
var result = from db.Profiles select p
where (p.CountryFrom == null) && (p.CountryLoc != null)
select p
Only CountryLoc null
var result = from db.Profiles select p
where (p.CountryFrom != null) && (p.CountryLoc == null)
select p
BOTH CountryFrom and CountryLoc are not null.
var result = from db.Profiles select p
where (p.CountryFrom != null) && (p.CountryLoc != null)
select p
Hope it helps ;-)
I wouldn't call this elegant:
public IQueryable<Profile> FindAllProfiles(string CountryFrom, string CountryLoc)
{
return db.Profiles.Where(p =>
{
p.ContryFrom != null &&
p.CountryFrom.CountryName != null &&
p.CountryFrom.CountryName.Equals(CountryFrom, StringComparison.OrdinalIgnoreCase)
});
}
I may be missing something, but as written, your combination of operators will either let all values through or no values through depending on whether you use || or && to combine them together.
I'm in favor of not trying to cram too much logic into a linq expression. Why not contain your comparison logic in a separate function like this?
EDIT: I provided an example implementation of the MatchesCountry function.
class Example
{
public IQueryable<Profile> FindAllProfiles(string CountryFrom, string CountryLoc)
{
return db.Profiles.Where(p => p.MatchesCountry(CountryFrom, CountryLoc));
}
}
public static class ProfileExtensions
{
public static bool MatchesCountry(this Profile profile, string CountryFrom, string CountryLoc)
{
// NOTE: Your comparison logic goes here. Below is an example implementation
// if the CountryFrom parameter was specified and matches the profile's CountryName property
if(!string.IsNullOrEmpty(CountryFrom) && string.Equals(profile.CountryName, CountryFrom, StringComparison.OrdinalIgnoreCase))
return true; // then a match is found
// if the CountryLoc parameter was specified and matches the profile's CountryCode property
if (!string.IsNullOrEmpty(CountryLoc) && string.Equals(profile.CountryCode, CountryLoc, StringComparison.OrdinalIgnoreCase))
return true; // then a match is found
// otherwise, no match was found
return false;
}
}
Related
In the below code, if i uncomment the line starting with queItem.RequestedMap == null I get
Non-static method requires a target.
If I then instead rewrite it as it is now, with a .ToList() and then doing the same where query after that it works. This tells me that .net is not able to translate the null check of queItem.RequestedMap == null into something sql specific.
queItem is an object paramater passed to the method containing this code.
Is there a way I can write this without retrieving the data back to .net and then doing another where? The existing answers I found just said to remove such expressions from the lambda query, which I dont want to do.
var gameToJoin = db.Games
//.Where(x =>
// (queItem.RequestedMap == null || x.Map.Id == queItem.RequestedMap.Id) // no map preference or same map
//)
.ToList()
.Where(x =>
queItem.RequestedMap == null
|| x.Map.Id == queItem.RequestedMap.Id) // no map preference or same map)
.FirstOrDefault();
Edit: Also, in the real query expression there are multiple other expressions in the first .Where that is commented here, they always need to be checked.
var gameToJoin = db.Games.AsQueryable();
// add the where's that always need to be checked.
if (queItem.RequestMap != null)
{
gameToJoin = gameToJoin.Where(x => x.Map.Id = queItem.RequestMap.Id);
}
var result = gameToJoin.ToList();
Or if you'd rather use FirstOrDefault()
var gameToJoin = db.Games.AsQueryable();
// add the where's that always need to be checked.
if (queItem.RequestMap != null)
{
var result = new List<Game>();
var game = gameToJoin.FirstOrDefault(x => x.Map.Id = queItem.RequestMap.Id);
if (game != null)
{
result.Add(game);
}
return result;
}
return gameToJoin.ToList();
Wouldn't this produce what you want? I don't see a reason that queItem.RequestedMap check should be a part the LINQ, because it is not a part the database.
Game gameToJoin = null;
if(queItem.RequestedMap == null)
{
gameToJoin = db.Games
.Where(x => x.Map.Id == queItem.RequestedMap.Id)
.FirstOrDefault;
}
I don't understand why this query is not working, can you please help me?
public static IEnumerable<SelectListItem> MyList(int? id, string name="")
{
var list =db.Entity
.Where(p=>
(name==null? p.Name !=null : p.Name==name) &&
(id.hasValue || p.Id==id)
.Select(n=>new SelectListItem()
{
Text=n.Name,
Value=n.Id.ToString()
}).ToList();
return list;
}
I want to have the full list when both parameters are null!! but I get an empty list when both parameters are null.
The snippet code is from a big method which contain several query like this one.
If I understand you correctly you do not want to perform filtering when the value is null. Then you should write:
.Where(p=>
(name == null || p.Name == name) &&
(id == null || p.Id == id)
And you should change the signature of the function to set the default value for parameter name to null rather than empty string.
public static IEnumerable<SelectListItem> MyList(int? id, string name = null)
Per the comments, there are two problems:
Firstly, your condition for id isn't quite right. Secondly, your default argument for name is an empty string, not null.
However, there is room for more improvements: by embedding your name == null (and same for id) in the query, your query will be constructed in a way that translates the null check to SQL. That's good if there's a chance of name changing its value after the query has constructed, but there's no chance of that happening here. You can instead dynamically construct your query:
I commented out the p.Name != null check. If your names are possibly null, then according to the question, you don't want them filtered out when name == null.
public static IEnumerable<SelectListItem> MyList(int? id, string name=null)
{
IQueryable<Entity> query = db.Entity;
if (id != null)
query = query.Where(p => p.Id == id);
if (name != null)
query = query.Where(p => p.Name == name);
//else
// query = query.Where(p => p.Name != null);
return query.Select(...).ToList();
}
I have the following code:
public IEnumerable<T> Enumerate<T>(IEnumerable<T> source, string searchField, string searchString,
bool searchEmpty) where T : class
{
if (source == null) throw new ArgumentNullException("source");
if (searchField == null) throw new ArgumentNullException("searchField");
if (searchString == null) throw new ArgumentNullException("searchString");
return from item in source.AsParallel()
let property = item.GetType().GetProperties().Where(_ =>
{
var searchFieldAttribute =
_.GetCustomAttributes(typeof (SearchFieldAttribute), false).SingleOrDefault() as
SearchFieldAttribute;
return searchFieldAttribute != null && Attribute.IsDefined(_, typeof (SearchFieldAttribute)) &&
searchFieldAttribute.Name == searchField;
}).Single()
let value = property.GetValue(item, null)
let asString = value == null ? String.Empty : value.ToString()
where
searchEmpty && String.IsNullOrEmpty(asString) ||
!searchEmpty && asString.ToLower().Contains(searchString.ToLower())
select item;
}
As you can see, I need to pass searchString parameter to this method and use it for searching or filtering source collection by checking whether string representation of property value contains searchString.
What should I improve in this method to use special characters (such as *, %, etc), which commonly used in search queries and LIKE statements in SQL. Is there any best practices, extensions or other implementations of Contains() method? Any advices and suggestions will be greatly appreciated.
After some searching attempts I've found solution from VB.NET language.
There is Operators.LikeString method that allows us to do wildcard matching, so the where clause should be rewritten as follows:
where
searchEmpty && String.IsNullOrEmpty(asString) ||
!searchEmpty && Operators.LikeString(asString.ToLower(), searchString.ToLower(), CompareMethod.Text)
And users will have possibilities to use special characters in their queries. Thanks for your attention.
NB. Microsoft.VisualBasic and Microsoft.VisualBasic.CompilerServices should be referenced.
The situation
I have a method that takes in a POCO. This POCO is like the following
private class SearchCriteria
{
public string name get;set;
public string age get;set;
public string talent get;set;
..
....
}
The method basically has a query to the db , that uses the above criteria.
public void query(SearchCriteria crit)
{
if(crit.name!=null && crit.age!=null && crit.talent !=null)
{
dbContext.Students.Where(c=>c.name ==crit.name && c.age==crit.age...)
}
else if(crit.name !=null && crit.age!=null)
{
}
else if(....
{
}
As you can see there is a definite problem above , where in, in case of large number of criteria, I will have to write a lot of if-elses to drop out specific arguments from the where clause .
The possible solution ?
I am actually new to the lambda expressions world but I believe we must be having a facility which would allow us to do something like below.
dbContext.Students.Where(processCriteria(searchCriteriaPOCO)).
Can you folks lead me to the proper direction ?. Thanks
Get a queryable and then keep adding where clauses to it. That way you only need to test each possible criteria the once and also only generate the number of where clauses that are absolutely needed.
IQueryable<Student> q = dbContext.Students.AsQueryable();
if (crit.name != null)
q = q.Where(c => c.name == crit.name);
if (crit.age != null)
q = q.Where(c => c.age== crit.age);
Let me start by saying that this answer uses the same basic idea as #PhilWright's answer. It just wraps it up in an extension method that applies this pattern for you, and allows you to have a syntax that reads nice.
public static class SearchExtensions
{
public static IQueryable<Student> Query(this SearchCriteria criteria, IQueryable<Student> studentQuery)
{
return studentQuery
.Match(criteria.name, (student) => student.name == criteria.name)
.Match(criteria.age, (student) => student.age == criteria.age)
.Match(criteria.talent, (student) => student.talent == criteria.talent);
// add expressions for other fields if needed.
}
private static IQueryable<Student> Match<T>(
this IQueryable<Student> studentQuery,
T criterionValue,
Expression<Func<Student, bool>> whereClause) where T : class
{
// only use the expression if the criterion value is non-null.
return criterionValue == null ? studentQuery : studentQuery.Where(whereClause);
}
}
You can then use it in your code like this:
var criteria = new SearchCriteria() {
name = "Alex",
talent = "Nosepicking"
};
var results = criteria.Query(dbContext.Students);
Maybe I'm missing something, as the code example is not the clearest I've seen, but for your specific example, I would think the following should be fine:
dbContext.Students.Where(c => (crit.name == null || crit.name == c.name) &&
(crit.age == null || crit.age == c.age) &&
(crit.talent == null || crit.talent == c.talent));
No need to chain a bunch of if statements.
For more complicated scenarios, you might prefer something like PredicateBuilder
You can use a pattern like this:
dbContext.Students.Where(c=>(crit.name == null || c.name ==crit.name) && ...)
A search criterion which is null will give a subexpression which is always true.
I'm sorry if this is a duplicate question, I have found some similar ones, but not one that would solve my problem. I have a collection of objects with various parameters and I want to filter them using data from ComboBoxes and TextBoxes.
var query = from zaj in zajezdy
where zaj.Zeme == (String)zemeCombo.SelectedValue
&& zaj.Oblast == (String)oblastCombo.SelectedValue
&& zaj.Stredisko == (String)strediskoCombo.SelectedValue
&& zaj.Doprava.Contains((String)dopravaCombo.SelectedValue)
&& zaj.Strava.Contains((String)stravaCombo.SelectedValue)
&& zaj.CenaOd > Int32.Parse(cenaOdText.Text)
&& zaj.CenaOd < Int32.Parse(cenaDoText.Text)
select zaj;
This code works if all the combos have been properly selected. However, if the user leaves some unselected/empty, the query comes back with zero objects in it. How can I detect which parameters are null so that the query ignores them?
I think this is a nice example for Specification usage.
Create object, which will represent zajezd specification:
public interface ISpecification<T>
{
bool IsSatisfiedBy(T value);
}
public class ZajezdSpecification : ISpecification<Zajezd>
{
private string _zeme;
private string _oblast;
private string _stredisko;
private string _doprava;
private string _strava;
private int _cenaOd;
private int _cenaDo;
public ZajezdSpecification(string zeme, string oblast, string stredisko,
string doprava, string strava, int cenaOd, int cenaDo)
{
_zeme = zeme;
_oblast = oblast;
_stredisko = stredisko;
_doprava = doprava;
_strava = strava;
_cenaOd = cenaOd;
_cenaDo = cenaDo;
}
public bool IsSatisfiedBy(Zajezd zajezd)
{
if (!String.IsNullOrEmpty(_zeme) && zajezd.Zeme != _zeme)
return false;
if (!String.IsNullOrEmpty(_oblast) && zajezd.Oblast != _oblast)
return false;
// ... verify anything you want
return _cenaOd < zajezd.CenaOd && zajezd.CenaOd < _cenaDo;
}
}
and initialize it with values from UI:
ZajezdSpecification spec = new ZajezdSpecification(
(string)zemeCombo.SelectedValue,
(string)oblastCombo.SelectedValue,
(string)strediskoCombo.SelectedValue,
...
Int32.Parse(cenaDoText.Text)
);
Use this specification to filter your collection:
var query = from zaj in zajezdy
where spec.IsSatisfiedBy(zaj)
select zaj;
PS try to use English names in your code.
First of all, try to introduse separate variables for each combo\text and use it. Use String.IsNullOrEmpty to check strings. Also try to avoid Type.Parse() - use Type.TryParse() instead. Sample :
var query = from c in dc.Customers select c;
//filter the result set based on user inputs
if ( !string.IsNullOrEmpty( countryFilter ) )
query = query.Where ( c=>c.Country == countryFilter );
if ( !string.IsNullOrEmpty( cityFilter ) )
query = query.Where ( c=>c.City == cityFilter );
You could also filter them like this, depending on whether the parameter name is null then all items are returned, otherwise only the elements with the specified name are returned:
myList.Where(x => name == null || x.Name == name)
.Where(x => id == null || x.Id == id);