linq multiple field search - c#

I have EF Employee table with fields EmpNo, FirstName, LastName, Email. And to create LINQ to search all columns and return existing record. If one or more fields is null or empty the return result based on existing data. I wrote code:
public static int Search(EmployeeDt emp)
{
using (EF.Model1 context = new EF.Model1)
{
List<string> employee = context.Employees.Where(a => (a.EmpNo == null || a.EmpNo == emp.EmpNo)
&& (b => (b.FirstName == null || b.FirstName == emp.FirstName)
&& (c => (c.LastName == null || c.LastName == emp.LastName)).ToList();
return result;
}
}
But code which I tried to write isn't correct. How to write LINQ for my case?
Thanks.

You don't need reuse a=> in second condition and compare emp.FirstName with null not a variable, change your code to
List<string> employee = context.Employees.Where(a => (emp.FirstName == null || a.FirstName == emp.FirstName)
&& (emp.LastName == null || a.LastName == emp.LastName)
&& (emp.EmpNo == null || a.EmpNo == emp.EmpNo)).ToList()

Related

Combine multiple where conditions having optional parameters in C# linq?

I am searching for a LINQ query where I have 3 parameters and two are optional parameters for this I wrote if-else conditions like below
if (id != null) { where condition}
else if (name!= null) { where condition }
else if (category != null) { where condition }
else if (id != null && name != null) { where condition }
else if (id != null && category != null) { where condition }
else if (name != null && category != null) {where condition}
else if (id != null && name != null && category != null ) { where condition }
I don't want to write more if-else conditions if there is another optional parameter added
Note. Id is not a primary key
The optimal pattern for this in EF is to add the Where conditions only conditionally. EG
IQueryable<SomeType> qry = ...;
if (id != null)
{
qry = qry.Where(x => x.Id == id);
}
if (name != null)
{
qry = qry.Where(x => x.Name == name);
}
if (category != null)
{
qry = qry.Where(x => x.Category == category);
}
var results = qry.ToList();
That way you don't clutter up the expression with lots of predicates that don't do anything, but which can mess up the query execution.
You can write it like this
id == null ? true : {id condition} &&
name == null ? true : {name condition} &&
category == null ? true: {category } and other conditions
Or
id == null || {id condition} &&
id == null || {id condition} &&
returning true will make the statement true if its value equals null.
its clean, Easy to understand and develop.
I hope it helps.
What I always do is below. Easy to read and remember.
myList
.Where(x => id == null || x.Id == id)
.Where(x => name == null || x.Name == name)
.Where(x => category == null || x.Category == category);

C# Query when any of the condition is not null

I want to find the data in database when any one of the conditions meet.
Pasted my code so that it will be more clear
[HttpGet]
[Route("")]
public IEnumerable<User> GetUsers(string FirstName = null, string LastName = null, int Year = 0, int Month = 0)
{
var users = _context.Users.AsQueryable();
if (FirstName != null || LastName != null || Year != 0 || Month != 0)
{
users = _context.Users.Where(u => (u.CreatedAt.Year == Year) && (u.CreatedAt.Month == Month));
}
else
{
users = _context.Users;
}
return users.ToList();
}
This code is doing a simple search in database
where year == createdAt.year &&
month == createdAt.month &&
LastName == abc &&
FirstName == abc
However, if one of the condition is 0/null, then the database will return nothing since there is no month/year == 0 or firstname/lastname == null; What I want is, if year/month/lastname/firstname is 0/null, then just ignore it and check other condition.
Any idea?
// first style
users = _context.Users.Where(u =>
(Year != 0 ? u.CreatedAt.Year == Year : true) &&
(Month != 0 ? u.CreatedAt.Month == Month : true) &&
(FirstName != null ? u.FirstName == FirstName : true) &&
(LastName != null ? u.LastName == LastName : true));
// second style
users = _context.Users.Where(u =>
(Year == 0 || u.CreatedAt.Year == Year) &&
(Month == 0 || u.CreatedAt.Month == Month) &&
(FirstName == null || u.FirstName == FirstName) &&
(LastName == null || u.LastName == LastName));
I think you should check each condition separately like this.
For example when Year != 0 and every other para is not set, your original code will return nothing.
You can add your logic to the LINQ query to check conditions.
users = _context.Users.Where(x => x.Id !=0
&& x.FirstName != null
&& x.FirstName != null
&& x.Year != 0
&& x.Month != 0)
.ToList();
Try this users = _context.Users.Where(x =>
&& (x.FirstName != null || x.FirstName == FirstName)
&& (x.Year == 0 || x.Year == Year)
&& (x.Month == 0 || x.Month == Month)
.ToList();

LINQ2SQL doesn't return row if checking with null

I have following LINQ2SQL Query:
var map =
dbContext.TCPDriverMappings.FirstOrDefault(
c => c.DriverFacilityId == tcpDms.FacilityId &&
c.DriverControlledParameterId == controlledParamId &&
c.DriverValue == value);
All the types are string.
In my DB i have a row, which must be returned by query.
When value="0", controlledParamId =null and FacilityId ="abc" this query returns null, but when i wrote following:
var test = dbContext.TCPDriverMappings.FirstOrDefault(
c => c.DriverFacilityId == "abc" &&
c.DriverControlledParameterId == null &&
c.DriverValue == "0");
test was not null
What am i doing wrong?
P.S. I also tried c.DriverControlledParameterId.Equals(controlledParamId) but it also doesn't work.
The problem is, that LINQ2SQL has a special handling for the expression c.DriverControlledParameterId == null. It is translated to the SQL DriverControlledParameterId IS NULL.
But c.DriverControlledParameterId = controlledParamId is translated to the SQL DriverControlledParameterId = :p1, even when controlledParamId is null. And in SQL DriverControlledParameterId = NULL is undefined and as such never TRUE.
How to fix: Handle the null case specifically:
TCPDriverMapping test;
if(controlledParamId == null)
test = dbContext.TCPDriverMappings.FirstOrDefault(
c => c.DriverFacilityId == "abc" &&
c.DriverControlledParameterId == null &&
c.DriverValue == "0");
else
test = dbContext.TCPDriverMappings.FirstOrDefault(
c => c.DriverFacilityId == "abc" &&
c.DriverControlledParameterId == controlledParamId &&
c.DriverValue == "0");
Or like this:
var test = dbContext.TCPDriverMappings.FirstOrDefault(
c => c.DriverFacilityId == "abc" &&
((controlledParamId == null &&
c.DriverControlledParameterId == null) ||
c.DriverControlledParameterId == controlledParamId) &&
c.DriverValue == "0");
Or like this:
IQueryable<TCPDriverMapping> query =
dbContext.TCPDriverMappings.Where(c => c.DriverFacilityId == "abc" &&
c.DriverValue == "0");
if(controlledParamId == null)
query = query.Where(c => c.DriverControlledParameterId == null);
else
query = query.Where(c => c.DriverControlledParameterId == controlledParamId);
var test = query.FirstOrDefault();
That third option is what I would use. In my opinion, this is the more readable than option 2 and has no repeated code like the first one.

Issue with LINQ IN clause when filter value is empty but works with filter values

I have a filter called serviceEntryFilter with a property System which could have values for instance EP1, EP2 OR EP1 and sometimes this filter would be null. If there are multiple values or a single value then the query (IN) clause runs fine . If the filter value is null then I get the following error:
Unable to create a constant value of type 'System.String[]'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
[HttpPost]
public ActionResult List(string ServiceEntryStatus, string ServiceEntryReconciled, string ServiceEntryReliabilityRecord, string ActiveServiceEntry,
int PageNo, ServiceEntryFilter serviceEntryFilter = null)
{
string[] systems = null;
var list = (from se in db.ServiceEntry
join r in db.RunLogEntry on se.RunLogEntryID equals r.ID into joinRunLogEntry
from r2 in joinRunLogEntry.DefaultIfEmpty()
join u in db.User on se.TechnicianID equals u.ID
join s in db.System1 on se.SystemID equals s.ID
where (
((se.RunLogEntryID == 0 || se.RunLogEntryID != null))
&& ((serviceEntryFilter.ID.HasValue == false) || (se.ID == serviceEntryFilter.ID.Value && serviceEntryFilter.ID.HasValue == true))
&& ((serviceEntryFilter.ServiceDateTime.HasValue == false) || (EntityFunctions.TruncateTime(se.ServiceDateTime) == EntityFunctions.TruncateTime(serviceEntryFilter.ServiceDateTime) && serviceEntryFilter.ServiceDateTime.HasValue == true))
&& ((serviceEntryFilter.RunDate.HasValue == false) || (EntityFunctions.TruncateTime(r2.RunDate) == EntityFunctions.TruncateTime(serviceEntryFilter.RunDate) && serviceEntryFilter.RunDate.HasValue == true))
&& ((serviceEntryFilter.Technician == null) || (u.FullName.Contains(serviceEntryFilter.Technician.Trim()) && serviceEntryFilter.Technician != null))
&& (
((ServiceEntryStatus == "O" && se.ServiceRequestClosed == false) ||
(ServiceEntryStatus == "C" && se.ServiceRequestClosed == true) ||
(ServiceEntryStatus == "A")
)
)
&& (
((ServiceEntryReliabilityRecord == null) ||
(ServiceEntryReliabilityRecord == "N" && se.ReliabilityRecord == false) ||
(ServiceEntryReliabilityRecord == "Y" && se.ReliabilityRecord == true) ||
(ServiceEntryReliabilityRecord == "A")
)
)
&& (
((ServiceEntryReconciled == null) ||
(ServiceEntryReconciled == "N" && se.Reconciled == false) ||
(ServiceEntryReconciled == "Y" && se.Reconciled == true) ||
(ServiceEntryReconciled == "A")
)
)
&& (
((ActiveServiceEntry == null) ||
(ActiveServiceEntry == "N" && se.Active == false) ||
(ActiveServiceEntry == "Y" && se.Active == true) ||
(ActiveServiceEntry == "A")
)
)
&& (
(s.PlatformID == platformID) || (platformID == 0)
)
&& ((serviceEntryFilter.System == null) || ((serviceEntryFilter.System != null) && systems.Contains(s.SystemFullName)))
)
orderby se.ID descending
select new ServiceSearchEntry()
{
ID = se.ID,
ServiceDateTime = se.ServiceDateTime,
Technician = u.FullName,
System = s.SystemFullName,
ReasonForFailure = se.ReasonForFailure,
RunDate = (r2 == null ? (DateTime?)null : r2.RunDate)
});
var listData = list.Skip((page - 1) * PageSize).Take(PageSize);
ServiceEntriesListViewModel viewModel = new ServiceEntriesListViewModel()
{
ServiceSearchEntry = listData,
PagingInfo = new PagingInfo
{
CurrentPage = page,
ItemsPerPage = PageSize,
TotalItems = list.Count()
}
};
}
The Issue:
The following clause is throwing an error when SystemFilter.System is NULL. It is null at times when users do not select values for it. Sample values are as follows:
EP1, EP2
EP1
TP2, TP3, TP4
&& ((serviceEntryFilter.System == null) || ((serviceEntryFilter.System != null) && systems.Contains(s.SystemFullName)))
If it has a value, then I put it in an array and its works like a charm, its just when its null.
The issue is that everything inside a LINQ statement will get translated to SQL. There isn't really a conditional statement that I see here that says "don't try to add this array filter if it's actually null".
I would initialize the systems array to a zero length array, overwrite it if the filter.Systems is not null, and then make my linq statement as follows:
systems.Contains(s.SystemFullName)
Don't include that null checking inside the LINQ statement as it's not doing what you are expecting.
To build conditional LINQ statements, you might want to look at PredicateBuilder: http://www.albahari.com/nutshell/predicatebuilder.aspx
This is a known limitation with Linq to Entities - see section on Referencing Non-Scalar Variables Not Supported.
In otherwords, this line:
systems.Contains(s.SystemFullName)
can't be used as part of your EF query.

Linq compare before inserting to database

HI,
I have Ling Table with MyObject.
It has got fields:
Id
Number
Name
There is a method that adds ne MyObject to Database. In partial class MyOBject Im implementing OnValidate method. I need to check if there is already a row with such number and name.
The problem is where this values are nulls (for this example both can be)
var result = from m in myContext.MyObjects where m.Number == this.Number &&
m.Name == this.Name
select m;
if(result.Count > 0) {
throw ...
}
if for example this.Name = null and r.Name = null and m.Number == this.Number no rows are selected and I dont know why :/
Any hints on how can I check if row with excactly the same values in fileds except Id (which is PK) are already in database ?
Thanks for help
Linq-To-SQL has a problem when you give it an Equals expression with a nullable parameter that happens to be null - the query it creates is incorrect. You can read all about it here: http://riotingbits.com/2009/int-null-or-why-it-misbehaves-in-linq-to-sql/
You have to treat the cases where this.Number or this.Name are null separately.
IQueryable<MyObject> result = null;
if (this.Name == null && this.Number == null)
result = from m in myContext.MyObjects
where m.Name == null && m.Number == null
select m;
else if (this.Name == null)
result = from m in myContext.MyObjects
where m.Name == null && m.Number == this.Number
select m;
else if (this.Number == null)
result = from m in myContext.MyObjects
where m.Name == this.Name && m.Number == null
select m;
else
result = from m in myContext.MyObjects
where m.Name == this.Name && m.Number == this.Number
select m;
Try this:
var result = from m in myContext.MyObjects
where (((m.Number == this.Number) || (m.Number == null)) &&
((m.Name == this.Name) || (m.Name == null)))
select m;
SQL query that LINQ generates would be:
SELECT Whatewer yourObject contains
FROM yourTable as [t0]
WHERE (([t0].[Number] = #p0) OR ([t0].[Number] IS NULL))
AND (([t0].[Name] = #p1) OR ([t0].[Name] IS NULL))

Categories