Let's say I have Customers table and I want to filter it by the following:
Country: All, US, UK, Canada
Income: All, low, high, medium
Age:All, teenager, adult, senior
if I had to build an SQL string for this filter, it would be something like this:
if (Country != "All") sql += "country = " + Country
if (Income != "All") sql += "and income = " + Income
if (Age != "All") sql += "and age = " + Age;
So, basically, the user can filter by some, but not necessary all fields.
How do you do this using Entity Framework ?
Thanks !
LINQ to Entity queries return IQueryable's, so you can build your query this way:
IQueryable<Person> query = context.People;
if (Country != "All")
{
query = query.Where(p => p.Country == Country);
}
if (Income != "All")
{
query = query.Where(p => p.Income == Income);
}
if (Age != "All")
{
query = query.Where(p => p.Age == Age);
}
List<Person> fetchedPeople = query.ToList();
This case is almost too simple, but this is very helpful in more complex situations when you need to add filtering dynamically.
You can include conditional parameter this way:
return Customers.Where(
customer =>
customer.Name == Name &&
(Age == "All" || customer.Age == Age) &&
(Income == "All" || customer.Income == Income) &&
(Country == "All" || customer.Country == Country)
).ToList();
If some condition is true (e.g. country is equal to All), then all parameter condition becomes true, and this parameter does not filter result.
You can use extension method to help readable and maintainable code.
LinqExtensions.cs
public static class LinqExtensions
{
public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> source, bool condition, Expression<Func<TSource, bool>> predicate)
{
return condition ? source.Where(predicate) : source;
}
}
Refactoring code
List<Person> peoples = context.People
.WhereIf(Country != "All", p => p.Country == Country)
.WhereIf(Income != "All", p => p.Income == Income)
.WhereIf(Age != "All", p => p.Age == Age)
.ToList();
Related
Trying to figure out what would be the most efficient approach to use a subquery in an inline SQL statement. Inline SQL is not something I have much experience with, but I have no choice at my organization alas.
SELECT *
FROM dbo.VW_RMISPayment
WHERE ProcDate BETWEEN '7/2/2018' AND '3/8/2019'
AND Status = 'P'
AND Fund = '359'
AND Amount > 0
AND (BatchNotate = 'B' OR BatchNotate IS NULL)
ORDER BY ProcDate, Amount
How could I parameterized the (BatchNotate = 'B' OR BatchNotate IS NULL) part?
My variable passed in as a List<string>, but I could change it to be anything. I'm just not sure how I can create this subquery from my variable
if (BatchNotate.Count() > 0)
{
query += " AND BatchNotate= #BatchNotate";
}
cmd.Parameters.AddWithValue("#BatchNotate", batchNotate);
Use this:
BatchNotate = COALESCE(#inVariable,'B')
If the variable (#inVariable) is null then it will "default to B.
If it is something else it will compare against that.
Could you do something like this?
SELECT *
FROM dbo.VW_RMISPayment
WHERE ProcDate BETWEEN '7/2/2018' AND '3/8/2019'
AND Status = 'P'
AND Fund = '359'
AND Amount > 0
AND BatchNotate = COALESCE(#BatchNotate, BatchNotate)
ORDER BY ProcDate, Amount
Neither of those worked for what I'm after. Here is what I ended up doing. It's kinda hacky, but it works.
public static string AddParametersOR<T>(SqlParameterCollection parameters,
string fieldName,
string pattern,
SqlDbType parameterType,
int length,
IEnumerable<T> values)
{
if (parameters == null)
throw new ArgumentNullException("parameters");
if (pattern == null)
throw new ArgumentNullException("pattern");
if (values == null)
throw new ArgumentNullException("values");
if (!pattern.StartsWith("#", StringComparison.CurrentCultureIgnoreCase))
throw new ArgumentException("Pattern must start with '#'");
var parameterNames = new List<string>();
foreach (var item in values)
{
var parameterName = parameterNames.Count.ToString(pattern, CultureInfo.InvariantCulture);
string parameterWithFieldName = string.Empty;
if (item.ToString().ToUpper() == "NULL")
{
parameterWithFieldName = string.Format("{0} IS NULL", fieldName);
}
else if (item.ToString().ToUpper() == "NOTNULL")
{
parameterWithFieldName = string.Format("{0} IS NOT", fieldName);
}
else
{
parameterWithFieldName = string.Format("{0}= {1}", fieldName, parameterName);
}
parameterNames.Add(parameterWithFieldName);
parameters.Add(parameterName, parameterType, length).Value = item;
}
return string.Join(" OR ", parameterNames.ToArray());
}
Usage:
if (batchNotate.Count() > 0)
{
query += " AND ({#BatchNotate})";
}
string batchNotateParamNames = SqlHelper.AddParametersOR(cmd.Parameters, "BatchNotate", "#B0", SqlDbType.VarChar, 1, batchNotate);
cmd.CommandText = query.Replace("{#BatchNotate}", batchNotateParamNames);
Depending on how many items are in your list the output will look like this:
(BatchNotate= 'B' OR BatchNotate= 'N' OR BatchNotate IS NULL)
If it finds "NULL" or "NOTNULL" it will replace these with IS NULL or IS NOT NULL
I have one table which contains employees records like first , middle, last name. I have 3 textboxes for entering all these.
Now on .net side I want to write a single LINQ query to filter data based on first name middle name and last name. Any of these 3 fields can be blank.
Is these any way to write a single generic LINQ query?
public IList<Employee> GetEmployees(string first, string middle, string last)
{
var query = context.Employees.AsQueryable();
if (!string.IsNullOrWhiteSpace(first))
{
query = query.Where(x => x.FirstName == first);
}
if (!string.IsNullOrWhiteSpace(middle))
{
query = query.Where(x => x.MiddleName == middle);
}
if (!string.IsNullOrWhiteSpace(last))
{
query = query.Where(x => x.LastName == last);
}
return query.Select(x =>
new Employee
{
FullName = string.Join(" ", new string[] { x.FirstName, x.MiddleName, x.LastName}.Where(y => !string.IsNullOrWhiteSpace(y)))
})
.ToList();
}
You can write one linq query with or condition like bellow
_context.tablename.where(p=>p.firstName.contains(txtFirstName) || p.middleName.contains(txtMiddleName) || p.lastName.contains(txtLastName)).ToList();
Change tablename with you database table in above linq query
when iterating just verify if the criteria in use has value
var criteria = new
{
FirstName = default(string),
MiddleName = default(string),
LastName = "Doe",
};
var query = from record in Context()
where !string.IsNullOrEmpty(criteria.FirstName)
&& record.FirstName == criteria.FirstName
|| !string.IsNullOrEmpty(criteria.MiddleName)
&& record.MiddleName == criteria.MiddleName
|| !string.IsNullOrEmpty(criteria.LastName)
&& record.LastName == criteria.LastName
select record;
Try something like that;
public List<Employee> RetrieveEmployees(string firstName, string lastName, string middleName)
{
var query = from employees in context.Employees
where (string.IsNullOrEmpty(firstName) || employees.FirstName == firstName) &&
(string.IsNullOrEmpty(lastName) || employees.LastName == lastName) &&
(string.IsNullOrEmpty(middleName) || employees.MiddleName == middleName)
select employees;
return query.ToList();
}
I want to build a query with where condition for filter properties when they have a value (not null). The multiple filters must be a AND combination if they are used.
How can combine/add each predicate when the according property value is not null to get a final query ready for filtering?
IQueryable<Customer> filter = null;
Expression<Func<Customer, bool>> predicatetest1 = res => res.test1 == request.test1;
Expression<Func<Customer, bool>> predicatetest2 = res => res.test2 == request.test2;
Expression<Func<Customer, bool>> predicatetest3 = res => res.test3 == request.test3;
if (request.test1 != null)
{
// add the above predicate to the filter
}
if (request.test2 != null)
{
// add the above predicate to the filter
}
if (request.test3 != null)
{
// add the above predicate to the filter
}
IQueryable<Customer> filter = Context.Customers;
if (request.test1 != null)
{
filter = filter.Where(predicatetest1);
}
if (request.test2 != null)
{
filter = filter.Where(predicatetest2);
}
if (request.test3 != null)
{
filter = filter.Where(predicatetest3);
}
var customers = filter.ToList();
The following would be equivalent, when all 3 properties where not null
Context.Customers.Where(predicatetest1).Where(predicatetest2).Where(predicatetest3).ToList();
I often find myself writing querys like this:
var voyages = db.VoyageRequests.Include("Carrier")
.Where(u => (fromDate.HasValue ? u.date >= fromDate.Value : true) &&
(toDate.HasValue ? u.date <= toDate.Value : true) &&
u.Carrier != null &&
u.status == (int)VoyageStatus.State.InProgress)
.OrderBy(u => u.date);
return voyages;
With conditionals inside the where statement:
fromDate.HasValue ? u.date >= fromDate.Value : true
I know the other way to do it'll be like:
var voyages = db.VoyageRequests.Include("Carrier").Where(u => u.Carrier != null &&
u.status == (int)VoyageStatus.State.InProgress);
if (fromDate.HasValue)
{
voyages = voyages.Where(u => u.date >= fromDate.Value);
}
if (toDate.HasValue)
{
voyages = voyages.Where(u => u.date <= toDate.Value);
}
return voyages.OrderBy(u => u.date);
Is there any real difference that may affect performance when this 2 approaches get transform to SQL expression?
The second query will create the simpler SQL because the evaluation of fromDate.HasValue and toDate.HasValue happens on the client. In the first query the ternary operators get evaluated on the database server as part of the SQL query. Both fromDate and toDate will be transmitted as constants to the server while in the second query only then if .HasValue is true.
We are talking about a few bytes more in length of the SQL statement and I don't believe that the server-side evaluation of the ternaries has any significant effect on query performance.
I would choose what you find more readable. Personally I would decide for the second query.
If you want the simple SQL and the more readable C# you can create an Extension as suggested by Viktor Mitev http://mentormate.com/blog/improving-linq-to-entities-queries/
public static class WhereExtensions
{
// Where extension for filters of any nullable type
public static IQueryable<TSource> Where<Tsource, TFilter>
(
this IQueryable <TSource> source,
Nullable <TFilter> filter,
Expression<Func<TSource, bool>> predicate
) where TFilter : struct
{
if (filter.HasValue)
{
source = source.Where(predicate);
}
return source;
}
// Where extension for string filters
public static IQueryable<TSource> Where<TSource>
(
this IQueryable<TSource> source,
string filter,
Expression<Func<TSource, bool>> predicate
)
{
if (!string.IsNullOrWhiteSpace(filter))
{
source = source.Where(predicate);
}
return source;
}
// Where extension for collection filters
public static IQueryable<TSource> Where<TSource, TFilter>
(
this IQueryable<TSource> source,
IEnumerable<TFilter> filter,
Expression<Func<TSource, bool>> predicate
)
{
if (filter != null && filter.Any())
{
source = source.Where(predicate);
}
return source;
}
Then your "secound query" will look like this:
var voyages = db.VoyageRequests.Include("Carrier")
.Where(u => u.Carrier != null && u.status == (int)VoyageStatus.State.InProgress)
.Where(u => u.date >= fromDate)
.Where(u => u.date <= toDate)
.OrderBy(u => u.date);
I don't know if it is more readable or if it will be confusing for some developers because it is more difficult to read directly form the code what part of the filtering is in use.
Maybe it will be more readable if you name the extensions function something like WhereIfFilterNotNull (or something meaningful :-)
var voyages = db.VoyageRequests.Include("Carrier")
.Where(u => u.Carrier != null && u.status == (int)VoyageStatus.State.InProgress)
.WhereIfFilterNotNull(u => u.date >= fromDate)
.WhereIfFilterNotNull(u => u.date <= toDate)
.OrderBy(u => u.date);
I want to return the total sum from a linq query, I pass in 2 parameters that may/may not be included in the query.
OrgId - int
reportType - int
So two questions:
How can I update the query below so that if OrgId = 0 then ignore the organisation field(Return All)?
LocumClaimTF can be True/False/Both, if both then ignore the where query for this field.
Heres what I have done so far, this is working but I'd like something for efficient.
// Using reportType set preferences for LocumClaimTF
bool locumClaimTF1, locumClaimTF2 = false;
if (reportType == 0)
{
locumClaimTF1 = false;
locumClaimTF2 = false;
}
else if (reportType == 1)
{
locumClaimTF1 = true;
locumClaimTF2 = true;
}
else // 2
{
locumClaimTF1 = true;
locumClaimTF2 = false;
}
if (OrgID != 0) // Get by OrgID
{
return _UoW.ShiftDates.Get(x => x.shiftStartDate >= StartDate && x.shiftEndDate <= EndDate)
.Where(x => x.Shift.LocumClaimTF == locumClaimTF1 || x.Shift.LocumClaimTF == locumClaimTF2)
.Where(x => x.Shift.organisationID == OrgID)
.GroupBy(s => s.assignedLocumID)
.Select(g => new dataRowDTO { dataLabel = string.Concat(g.FirstOrDefault().User.FullName), dataCount = g.Count(), dataCurrencyAmount = g.Sum(sd => sd.shiftDateTotal.Value) }
).Sum(g=>g.dataCurrencyAmount);
}
else // Ignore OrgID - Get ALL Orgs
{
return _UoW.ShiftDates.Get(x => x.shiftStartDate >= StartDate && x.shiftEndDate <= EndDate)
.Where(x => x.Shift.LocumClaimTF == locumClaimTF1 || x.Shift.LocumClaimTF == locumClaimTF2)
.GroupBy(s => s.assignedLocumID)
.Select(g => new dataRowDTO { dataLabel = string.Concat(g.FirstOrDefault().User.FullName), dataCount = g.Count(), dataCurrencyAmount = g.Sum(sd => sd.shiftDateTotal.Value) }
).Sum(g => g.dataCurrencyAmount);
}
I'm using EF with unit of work pattern to get data frm
A few things come to mind, from top to bottom:
For handling the Booleans
bool locumClaimTF1 = (reportType == 1 || reportType == 2);
bool locumClaimTF2 = (reportType == 1);
From what I read in the query though, if the report type is 1 or 2, you want the Shift's LocumClaimTF flag to have to be True. If that is the case, then you can forget the Boolean flags and just use the reportType in your condition.
Next, for composing the query, you can conditionally compose where clauses. This is a nice thing about the fluent Linq syntax. However, let's start temporarily with a regular DbContext rather than the UoW because that will introduce some complexities and questions that you'll need to look over. (I will cover that below)
using (var context = new ApplicationDbContext()) // <- insert your DbContext here...
{
var query = context.ShiftDates
.Where(x => x.shiftStartDate >= StartDate
&& x.shiftEndDate <= EndDate);
if (reportType == 1 || reportType == 2)
query = query.Where(x.Shift.LocumClaimTF);
if (OrgId > 0)
query = query.Where(x => x.Shift.organisationID == OrgID);
var total = query.GroupBy(s => s.assignedLocumID)
.Select(g => new dataRowDTO
{
dataLabel = tring.Concat(g.FirstOrDefault().User.FullName),
dataCount = g.Count(),
dataCurrencyAmount = g.Sum(sd => sd.shiftDateTotal.Value)
})
.Sum(g=>g.dataCurrencyAmount);
}
Now this here didn't make any sense. Why are you going through the trouble of grouping, counting, and summing data, just to sum the resulting sums? I suspect you've copied an existing query that was selecting a DTO for the grouped results. If you don't need the grouped results, you just want the total. So in that case, do away with the grouping and just take the sum of all applicable records:
var total = query.Sum(x => x.shiftDateTotal.Value);
So the whole thing would look something like:
using (var context = new ApplicationDbContext()) // <- insert your DbContext here...
{
var query = context.ShiftDates
.Where(x => x.shiftStartDate >= StartDate
&& x.shiftEndDate <= EndDate);
if (reportType == 1 || reportType == 2)
query = query.Where(x.Shift.LocumClaimTF);
if (OrgId > 0)
query = query.Where(x => x.Shift.organisationID == OrgID);
var total = query.Sum(x => x.shiftDateTotal.Value);
return total;
}
Back to the Unit of Work: The main consideration when using this pattern is ensuring that this Get call absolutely must return back an IQueryable<TEntity>. If it returns anything else, such as IEnumerable<TEntity> then you are going to be facing significant performance problems as it will be returning materialized lists of entities loaded to memory rather than something that you can extend to build efficient queries to the database. If the Get method does not return IQueryable, or contains methods such as ToList anywhere within it followed by AsQueryable(), then have a talk with the rest of the dev team because you're literally standing on the code/EF equivalent of a land mine. If it does return IQueryable<TEntity> (IQueryable<ShiftDate> in this case) then you can substitute it back into the above query:
var query = _UoW.ShiftDates.Get(x => x.shiftStartDate >= StartDate && x.shiftEndDate <= EndDate);
if (reportType == 1 || reportType == 2)
query = query.Where(x.Shift.LocumClaimTF);
if (OrgId > 0)
query = query.Where(x => x.Shift.organisationID == OrgID);
var total = query.Sum(x => x.shiftDateTotal.Value);
return total;