I have linq query where I need to set condition if p.conditionVariable > 0 I would apply the following condition.
from prob in table2.where(p => p.Id == p.ConditionVariable && !p.IsBlocked && p.IsActive)
if p.conditionVariable == 0 the following remains the same.
(from _obj1 in table1.Where(p => p.IsActive == true))
from prob in table2.Where(p => p.Id == _obj1.Id && !p.IsBlocked && p.IsActive && p.ConditionVariable == 0)
select new Class1
{
Title = prob.Title,
Status = prob.IsPending,
Id = obj1.id
}
I think you want to make a || between conditions and table2 will be queried based on p.CondtionVariable.
(from _obj1 in table1.Where(p => p.IsActive == true))
from prob in table2.Where(p => (p.Id == _obj1.Id && !p.IsBlocked && p.IsActive && p.ConditionVariable == 0)
|| (p.ConditionVariable > 0 && p.Id == p.ConditionVariable && !p.IsBlocked && p.IsActive))
select new Class1
{
Title = prob.Title,
Status = prob.IsPending,
Id = _obj1.id
}
If you want to use if/else conditions, you can use something like this
(from _obj1 in table1.Where(p => p.IsActive == true))
from prob in table2.Where(p => {
bool state = false;
if(p.ConditionVariable > 0) {
state = p.Id == p.ConditionVariable && !p.IsBlocked && p.IsActive;
} else if(p.ConditionVariable == 0) {
state = p.Id == _obj1.Id && !p.IsBlocked && p.IsActive;
}
return state;
})
select new Class1
{
Title = prob.Title,
Status = prob.IsPending,
Id = _obj1.id
}
I would put the p.ConditionVariable test at the beginning so taht it is the first thing checked (as && operations stop at the first failing condition. IF the first check fails then the || operation is checked next):
(from _obj1 in table1.Where(p => p.IsActive == true))
from prob in table2.Where(
(p => p.ConditionVariable == 0 && p.Id == _obj1.Id && !p.IsBlocked && p.IsActive)
|| (p.ConditionVariable > 0 && p.Id == p.ConditionVariable && !p.IsBlocked && p.IsActive))
select new Class1
{
Title = prob.Title,
Status = prob.IsPending,
Id = obj1.id
}
A second variant would also be possible with myvariable=0 ? .... as someone commented but in this case it is not really necessary as you have the || && operators there anyway.
Related
I have this query that is being partially evaluated locally and I am wondering how to prevent it.
It seems to be the conditional select that is causing the problem.
var fetchQuery = (
from udg in _entities.UberDirectGroups.AsNoTracking()
join uber in _entities.Ubers.AsNoTracking()
on udg.Id equals uber.Id
let memberCount = (
from t in _entities.UberDirects.AsNoTracking()
join u in _entities.Ubers.AsNoTracking()
on t.UberToId equals u.Id
where t.UberFromId == udg.Id && !u.Deleted
select u.UberTypeId == (byte)UberType.User ? 1 :
u.UberTypeId == (byte)UberType.Department ? u.Department.Users.Where(user => user.Deleted == false).Count() :
u.UberTypeId == (byte)UberType.Office ? u.tblOffice.tblDepartment.SelectMany(d => d.Users).Where(user => user.Deleted == false).Count() :
u.UberTypeId == (byte)UberType.ProjectGroup ? u.Group.GroupMembers.Select(pm => pm.User).Where(user => user.Deleted == false).Count() :
u.UberTypeId == (byte)UberType.Role ? _entities.Roles.Where(r => r.RoleDataId == u.tblRoleData.Id).Select(r => r.tblUser).Where(user => user.Deleted == false).Count() : 0
).Sum()
where
udg != null &&
uber != null &&
uber.InstanceId == instanceId &&
(!isSearch || udg.Name.Contains(searchText))
select new TargetGroupProjection
{
id = udg.Id,
name = udg.Name,
created = uber.Date,
toCount = memberCount
}
);
I have below LINQ query:
SailingMain_Details = SailingMain_Details.Where(f => f.Duration == durationCr_Filter
&& f.DeparturePortID == depPortCr_Filter
&& f.CruiseLine == cruLineCr_Filter
&& f.ShipName == cruShipCr_Filter
&& f.DestinationCr == destinationCr_Filter).ToList();
in above query sometime i get some parameters values like value=="any". in that situation i want to avoid checking only that parameter. Can anyone please guide me how to do that. Thanks.
The below might work if you want to skip the condition if the filter is "any"
SailingMain_Details =
SailingMain_Details.Where(f => (durationCr_Filter != "any" ? f.Duration == durationCr_Filter : true)
&& (depPortCr_Filter != "any" ? f.DeparturePortID == depPortCr_Filter : true)
&& (cruShipCr_Filter != "any" ? f.ShipName == cruShipCr_Filter : true)
&& (cruLineCr_Filter != "any" ? f.CruiseLine == cruLineCr_Filter : true)
&& (destinationCr_Filter != "any" ? f.DestinationCr == destinationCr_Filter : true)).ToList();
To achieve what you want let me show you by example with DeparturePortID. Let's say "any" valu eis -1:
SailingMain_Details = SailingMain_Details
.Where(f =>
f.Duration == durationCr_Filter
&& (depPortCr_Filter == -1 || f.DeparturePortID == depPortCr_Filter)
&& f.CruiseLine == cruLineCr_Filter
&& f.ShipName == cruShipCr_Filter
&& f.DestinationCr == destinationCr_Filter)
.ToList();
Here, if depPortCr_Filter is -1, then (depPortCr_Filter == -1 || f.DeparturePortID == depPortCr_Filter) evaluates to true, independently of f.DeparturePortID == depPortCr_Filter condition.
Apply every filter on separate. example
if(durationCr_Filter.toUpper() != "ANY")
SailingMain_Details = SailingMain_Details.Where(f => f.Duration == durationCr_Filter);
if(depPortCr_Filter.toUpper() != "ANY")
SailingMain_Details = SailingMain_Details.Where(f => f.DeparturePortID == depPortCr_Filter);
if(cruLineCr_Filter.toUpper() != "ANY")
SailingMain_Details = SailingMain_Details.Where(f => f.CruiseLine == cruLineCr_Filter);
if(cruShipCr_Filter.toUpper() != "ANY")
SailingMain_Details = SailingMain_Details.Where(f => f.ShipName == cruShipCr_Filter);
if(destinationCr_Filter.toUpper() != "ANY")
SailingMain_Details = SailingMain_Details.Where(f => f.DestinationCr == destinationCr_Filter);
I assume here some parameter means it can be for any of those 5 params so you can try something like
SailingMain_Details = SailingMain_Details.Where(f => (f.Duration == durationCr_Filter || durationCr_Filter == "any")
&& (f.DeparturePortID == depPortCr_Filter || depPortCr_Filter == "any") &&
(f.CruiseLine == cruLineCr_Filter || cruLineCr_Filter == "any") && (f.ShipName == cruShipCr_Filter || cruShipCr_Filter == "any") &&
(f.DestinationCr == destinationCr_Filter || destinationCr_Filter == "any") ).ToList();
You can do it like
if (SailingMain_Details.Any(x => x.value == "any"))
{
// avoid the check here
SailingMain_Details = SailingMain_Details.Where(f => f.Duration == durationCr_Filter && f.DeparturePortID == depPortCr_Filter && f.CruiseLine == cruLineCr_Filter && f.ShipName == cruShipCr_Filter && f.DestinationCr == destinationCr_Filter).ToList();
}
else
{
SailingMain_Details = SailingMain_Details.Where(f => f.Duration == durationCr_Filter && f.DeparturePortID == depPortCr_Filter && f.CruiseLine == cruLineCr_Filter && f.ShipName == cruShipCr_Filter && f.DestinationCr == destinationCr_Filter).ToList();
}
e.g avoid checking means basically it shall return true by default. for one item see below. other can follow the same
&& (depPortCr_Filter == 'any' || f.DeparturePortID == depPortCr_Filter).
I've been filtetring an Iqueryable object by some rules, using successive Where clauses to do && filtering, how can i do the same for || filtering?
Anyone has a suggestion?
Using another words, i want to apply for all rules || filtering.
I'm going to show a portion of the code to exemplify what im asking.
Where i get the pages object:
public IQueryable<gbd_Pages> getAllPagesByOrderAndDir(int template_id, string name = "", int sortOrder = 3, string sortDirection = "asc")
{
IQueryable<gbd_Pages> Listpages;
if (sortDirection == "asc")
{
Listpages = from p in _db.gbd_Pages
from pc in (from c in p.gbd_Content
where c.IsActive == true && c.IsDeleted == false && c.Template_Id == template_id
&& !string.IsNullOrEmpty(name) ? c.Content.ToLower().Contains(name.ToLower()) : true
&& c.gbd_Template_Fields.SortOrder == sortOrder
select c).Take(1)
orderby pc.Content ascending
where p.IsActive == true && p.IsDeleted == false
select p;
}
else
{
Listpages = from p in _db.gbd_Pages
from pc in (from c in p.gbd_Content
where c.IsActive == true && c.IsDeleted == false && c.Template_Id == template_id
&& !string.IsNullOrEmpty(name) ? c.Content.ToLower().Contains(name.ToLower()) : true
&& c.gbd_Template_Fields.SortOrder == sortOrder
select c).Take(1)
orderby pc.Content descending
where p.IsActive == true && p.IsDeleted == false
select p;
}
return Listpages;
}
The function where i do the filtering. Actually it's only doing && filtering for all rules:
public IEnumerable<gbd_Pages> PagesFiltered(List<Rule> newRules, string search, int sortColumnId, string sortColumnDir, out int count)
{
GBD.FrontOffice.Controllers.SegmentsController segmentCtrl = new GBD.FrontOffice.Controllers.SegmentsController();
var Pages = controller.getAllPagesByOrderAndDir(1, search, sortColumnId, sortColumnDir);
if (newRules.Count > 0)//FILTRA A LISTAGEM DE ACORDO COM AS REGRAS
{
foreach (var rule in newRules)
{
if (rule.Value!=null)
{
gbd_Template_Fields templateField = controller.getTemplateFieldById(1, rule.Template_Field_Id);
gbd_Segment_Operators segOperator = segmentCtrl.getOperatorById(rule.Operator_Id);
if (segOperator.Value == "==")//IGUALDADE
if (rule.Value.Trim() != "")
{
if (templateField != null && templateField.Field_Id == 3) //tipo data
{
try
{
DateTime test_datetime = DateTime.Parse(rule.Value); // testa se a data introduzida é valida
Pages = Pages.Where(p => p.gbd_Content.Any(c => c.Templates_Fields_Id == rule.Template_Field_Id && c.IsActive == true && c.IsDeleted == false && (!string.IsNullOrEmpty(c.Content) && c.Content.Trim() == rule.Value.Trim())));
}
catch (Exception ex)
{
}
}
else if (templateField != null && templateField.Field_Id == 12 && !string.IsNullOrEmpty(rule.Value))//Lista múltipla de valores
{
rule.Value = rule.Value.Replace(',', '|');
Pages = Pages.Where(p => p.gbd_Content.Any(c => c.Templates_Fields_Id == rule.Template_Field_Id && c.IsActive == true && c.IsDeleted == false && (!string.IsNullOrEmpty(c.Content) && c.Content == rule.Value)));
}
else
Pages = Pages.Where(p => p.gbd_Content.Any(c => c.Templates_Fields_Id == rule.Template_Field_Id && c.IsActive == true && c.IsDeleted == false && (c.Content != null && c.Content.ToLower().Trim() == rule.Value.ToLower().Trim())));
}
else
{
Pages = Pages.Where(p => !p.gbd_Content.Any(c => c.Templates_Fields_Id == rule.Template_Field_Id) || p.gbd_Content.Any(c => c.Templates_Fields_Id == rule.Template_Field_Id && c.IsActive == true && c.IsDeleted == false && (c.Content == null || c.Content.ToLower().Trim() == rule.Value.ToLower().Trim())));
}
else if (segOperator.Value == ">")//MAIOR QUE
{
if (templateField != null && templateField.Field_Id == 3) //tipo data
{
try
{
DateTime test_datetime = DateTime.Parse(rule.Value); // testa se a data introduzida é valida
Pages = Pages.Where(p => p.gbd_Content.Any(c => c.Templates_Fields_Id == rule.Template_Field_Id && c.IsActive == true && c.IsDeleted == false && (!string.IsNullOrEmpty(c.Content) && (c.Content.Substring(6, 4).CompareTo(rule.Value.Trim().Substring(6, 4)) > 0 || c.Content.Substring(6, 4).CompareTo(rule.Value.Trim().Substring(6, 4)) == 0 && c.Content.Substring(3, 2).CompareTo(rule.Value.Trim().Substring(3, 2)) > 0 || c.Content.Substring(6, 4).CompareTo(rule.Value.Trim().Substring(6, 4)) == 0 && c.Content.Substring(3, 2).CompareTo(rule.Value.Trim().Substring(3, 2)) == 0 && c.Content.Substring(0, 2).CompareTo(rule.Value.Trim().Substring(0, 2)) > 0))));
}
catch (Exception ex)
{
}
}
else if (templateField != null && templateField.Field_Id == 5)//tipo número
Pages = Pages.Where(p => p.gbd_Content.Any(c => c.Templates_Fields_Id == rule.Template_Field_Id && c.IsActive == true && c.IsDeleted == false && (c.Content != null && c.Content != "" && (c.Content.Trim().CompareTo(rule.Value.Trim()) > 0 || (rule.Value.Trim().StartsWith("-") && c.Content.Trim().CompareTo(rule.Value.Trim()) > 0 || (c.Content.Trim().CompareTo("0") > 0))))));//procurar outra solução https://stackoverflow.com/questions/7740693/big-issue-in-converting-string-to-datetime-using-linq-to-entities
else if (templateField != null && templateField.Field_Id == 6) //tipo telefone //ESTA CONDIÇÃO NÃO FOI ADICIONADA AINDA A BD
{
try
{
Pages = Pages.AsEnumerable().Where(p => p.gbd_Content.Any(c => c.Templates_Fields_Id == rule.Template_Field_Id && c.IsActive == true && c.IsDeleted == false && (c.Content != null && c.Content != "" && (Convert.ToInt32(c.Content.Split(' ')[1]) > Convert.ToInt32(rule.Value))))).AsQueryable();//TODO: procurar outra solução
}
catch (Exception ex) { }
}
}
many more rules...
else if (segOperator.Value == "StartsWith")//COMEÇA COM
{
if (rule.Value.Trim() != "")
Pages = Pages.Where(p => p.gbd_Content.Any(c => c.Templates_Fields_Id == rule.Template_Field_Id && c.IsActive == true && c.IsDeleted == false && (c.Content != null && c.Content.ToLower().Trim().StartsWith(rule.Value.ToLower().Trim()))));
}
else if (segOperator.Value == "EndsWith")//ACABA COM
{
if (rule.Value.Trim() != "")
Pages = Pages.Where(p => p.gbd_Content.Any(c => c.Templates_Fields_Id == rule.Template_Field_Id && c.IsActive == true && c.IsDeleted == false && (c.Content != null && c.Content.ToLower().Trim().EndsWith(rule.Value.ToLower().Trim()))));
}
}
}
}
count = Pages.Select(d => d.Id).Count();
return Pages;
}
The Where statement is implicitly using AND in all cases. To get an OR statement equivalent, you'll need to use the double pipe operator (||) inside of a Where statement with something like:
.Where(p => p.A == 'b' || p.C == 'd')
Unfortunately there is no simple way to add successive optional statements the .NET framework. It would be great to have something like .Where(p => p.A == 'b').Or(p => p.C == 'd') but generating a query from this seems impractical - if there was an another Where statement, then how would Linq know how to group it relative to the other two? (A || B && C) versus ((A || B) && C).
That said, there is an external library (LINQKit) that can help fill the gap. See Dynamic where clause (OR) in Linq to Entities for reference.
You can work around the limitations of linq by just adding all the results into a List<>
List<gbd_Pages> allPages = new List<gdb_Pages>();
foreach (var rule in newRules)
{
IEnumerable<gbd_Pages> rulePages;
// current logic (except you need to not overwrite the Pages variable)
// instead of the pattern "Pages = Pages.Where( ..." use "rulePages = Pages.Where( ..."
allPages.AddRange(rulePages);
}
return allPages;
I have one page name as:CustomerList.aspx on which i am displaying list of customers.
This is my table and class files too:
public partial class Customer
{
public int CustomerID { get; set; }
public string FullName { get; set; }
public string EmailId { get; set; }
public int CustomerLocation { get; set; }
public bool IsActive { get; set; }
public bool Removed { get; set; }
public DateTime SubscribeDate { get; set; }
public Location _Location;
}
public partial class Location
{
public int LocationId { get; set; }
public string Name { get; set; }
}
Inactive=true:Means customer is active in the system.
Inactive=false:Means customer is inactive in the system.
Removed=true:Means customer is removed from the system
Removed=false:Means customer is not removed from the system.
I will provide user with 3 filters to filter customer records.
1)Location Dropdown
<select>
<option Text="All" Value="0" selected="true">
<option Text="London" Value="1">
<option Text="America" Value="2">
</select>
2)Status Dropdown with Value:All,Active,Inactive:
<select>
<option Text="All" Value="0" selected="true">
<option Text="Active" Value="1">
<option Text="Inactive" Value="2">
</select>
3)Stats Data Dropdown:
<select>
<option Text="All" Value="all" selected="true">
<option Text="Active Customers" Value="all">
<option Text="Recent subscribe customers" Value="subscribe">
<option Text="Recent unsubscribe customers" Value="unsubscribe">
</select>
As my page is loaded i want to display customers list in my grid.
This is my code:
public void DisplayCustomersList()
{
DataTable list=GetCustomers(Convert.ToInt16(ddlLocation.SelectedValue),Convert.ToInt16(ddlStatus.SelectedValue),ddlstats.SelectedValue);
Grid1.DataSource = list;
Grid1.DataBind();
}
public DataTable GetCustomers(int LocationId, int ActiveId, string stats)
{
using (var context = new MyContext())
{
var data = from c in context.Customers
where c.Removed == false
select new
{
FullName = c.FullName,
c.CustomerID,
c._Location.Name,
c.IsActive,
c.SubscribeDate,
c.Removed
};
if (LocationId != 0 && ActiveId != 0)
{
if (ActiveId == 1)
{
return
MyContext.CopyToDataTable(
data.Where(x => x.LocationId == LocationId && x.IsActive == true && x.Removed == false));
}
else if(ActiveId==2)
{
return
MyContext.CopyToDataTable(
data.Where(x => x.LocationId == LocationId && x.IsActive == false && x.Removed == false));
}
return
MyContext.CopyToDataTable(
data.Where(x => x.LocationId == LocationId && x.Removed==false));
}
if (LocationId != 0 && stats != "")
{
if (stats == "all")
{
return
MyContext.CopyToDataTable(
data.Where(x => x.LocationId == LocationId && x.IsActive == true && x.Removed == false));
}
else if (stats == "subscribe")
{
return
MyContext.CopyToDataTable(
data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.LocationId==LocationId));
}
}
if (ActiveId != 0 && stats != "")
{
if(ActiveId==1)
{
if(stats=="all")
{
return
MyContext.CopyToDataTable(
data.Where(x => (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == true) || (x.Removed == false) || (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == false)));
}
else if (stats == "subscribe")
{
return
MyContext.CopyToDataTable(
data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive == true));
}
else if (stats == "unsubscribe")
{
return
MyContext.CopyToDataTable(
data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive == false));
}
}
else if(ActiveId==2)
{
if (stats == "all")
{
MyContext.CopyToDataTable(
data.Where(x => (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == false) && (x.Removed == false)));
}
else if (stats == "subscribe")
{
return
MyContext.CopyToDataTable(
data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive == false));
}
else if (stats == "unsubscribe")
{
return
MyContext.CopyToDataTable(
data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive == false));
}
}
}
if (stats != "")
{
if (stats == "all")
{
return
MyContext.CopyToDataTable(
data.Where(x => x.IsActive == true && x.Removed == false));
}
else if (stats == "subscribe")
{
return
MyContext.CopyToDataTable(
data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive==true));
}
else
{
return
MyContext.CopyToDataTable(
data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.IsActive == false && x.Removed == false));
}
}
}
}
On all 3 dropdown selected index change event i am just calling this function like this:DisplayCustomersList()
So i just want to ask you that is this a proper way to perform filters or this code can be optimize in a better way.
can anybody provide me better solution or optimize this code in better way if possible???
This problem can be optimized without PredicateBuilder, but it requires careful and "systematic" analysis.
To begin with... Consider Your Predicate Making Determining Factor
Consider your case, putting all 14 of them together like this, you actually can see that you only have three determining factors, namely: LocationId, ActiveId, and stats:
No LocationId ActiveId stats result
1 not 0 1 don't care data.Where(x => x.LocationId == LocationId && x.IsActive == true && x.Removed == false)
2 not 0 2 don't care data.Where(x => x.LocationId == LocationId && x.IsActive == false && x.Removed == false));
3 not 0 not 0-2 don't care data.Where(x => x.LocationId == LocationId && x.Removed == false));
4 not 0 don't care all data.Where(x => x.LocationId == LocationId && x.IsActive == true && x.Removed == false)
5 not 0 don't care subscribe data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.LocationId == LocationId));
6 don't care 1 all data.Where(x => (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == true) || (x.Removed == false) || (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == false)));
7 don't care 1 subscribe data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive == true));
8 don't care 1 unsubscribe data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive == false));
9 don't care 2 all data.Where(x => (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == false) && (x.Removed == false)));
10 don't care 2 subscribe data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive == false));
11 don't care 2 unsubscribe data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive == false));
12 don't care don't care all data.Where(x => x.IsActive == true && x.Removed == false));
13 don't care don't care subscribe data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive == true));
14 don't care don't care unsubscribe data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive == false));
Next, consider the pattern of your results
I observe that your result is quite deterministic with small exceptions. Apart from result no 6 and no 9, your query predicates can actually be separated into four basic components (6 and 9 omitted). They are:
comp1: x.LocationId == LocationId
comp2: x.IsRemoved == false
comp3: x.IsActive == true
comp4: x.SubscribeDate >= DateTime.Now.AddDays(-7)
And the query logic is simply:
comp1 && comp2 && comp3 && comp4
Putting them together with the 12 cases (excluding the cases 6 and 9), you would get:
Simplification:
DC = don't care
A = applied
NA = not applied
QueryComponents
No LocationId ActiveId stats comp1 comp2 comp3 comp4
1 not 0 1 DC A A Yes NA
2 not 0 2 DC A A No NA
3 not 0 not 0-2 DC A A NA NA
4 not 0 DC all A A Yes NA
5 not 0 DC subscribe A A NA A
7 DC 1 subscribe NA A Yes A
8 DC 1 unsubscribe NA A No A
10 DC 2 subscribe NA A No A
11 DC 2 unsubscribe NA A No A
12 DC DC all NA A Yes A
13 DC DC subscribe NA A Yes A
14 DC DC unsubscribe NA A No A
After all the breaking down, we can see the Mapping
Now, it can be seen that the query components can be mapped back together with the determining factors:
comp1: Applied only when LocationId is not 0
comp2: Always applied //this is very good!
comp3: Yes = 1, 4, 7, 12, 13; NA = 3, 5; No = 2, 8, 10, 11, 14
comp4: Not Applied when LocationId is 0 except on case 5
You can start translating the concept into codes...
Thus, we can make some helping flags (there are 4 of them) to determine if a query component should be included or not, like this:
bool LocationIdNotApplied = LocationId == 0; //for comp1
bool IsActiveNotApplied = LocationId != 0 && (ActiveId < 0 || ActiveId > 2 || stats = "subscribe"); //for comp3 to be applied or not
bool IsActiveFalse = (LocationId != 0 && ActiveId == 2) || stats == "unsubscribe" || (ActiveId == 2 && stats == "subscribe"); //for comp3 to be false
bool DateApplied = LocationId == 0 || (LocationId != 0 && stats == "subscribe");
Then your data.Where for all cases except 6 and 9 can be simplified like this:
data.Where(x => (x.LocationId == LocationId || LocationIdNotApplied) //comp1
&& x.IsRemoved == false //comp2
&& ((x.IsActive == !IsActiveFalse) || IsActiveNotApplied) //comp3
&& (x.SubscribeDate >= DateTime.Now.AddDays(-7) || !DateApplied)) //comp4
This is a significant simplification for 12 cases becoming 1 case, and you only need to add the additional two cases, totalling in 3 cases rather than the original 14 cases!
Putting Them together into code
public DataTable GetCustomers(int LocationId, int ActiveId, string stats)
{
using (var context = new MyContext())
{
var data = from c in context.Customers
where c.Removed == false
select new
{
FullName = c.FullName,
c.CustomerID,
c._Location.Name,
c.IsActive,
c.SubscribeDate,
c.Removed
};
bool LocationIdNotApplied = LocationId == 0; //for comp1
bool IsActiveNotApplied = LocationId != 0 && (ActiveId < 0 || ActiveId > 2 || stats = "subscribe"); //for comp3 to be applied or not
bool IsActiveFalse = (LocationId != 0 && ActiveId == 2) || stats == "unsubscribe" || (ActiveId == 2 && stats == "subscribe"); //for comp3 to be false
bool DateApplied = LocationId == 0 || (LocationId != 0 && stats == "subscribe");
if(LocationId == 0 && ActiveId == 1 && stats == "all"){ //case 6
return MyContext.CopyToDataTable(
data.Where(x => (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == true) || (x.Removed == false) || (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == false)));
} else if (LocationId == 0 && ActiveId == 2 && stats == "all"){ //case 9
return MyContext.CopyToDataTable(
data.Where(x => (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == false) && (x.Removed == false)));
} else { //other cases
return MyContext.CopyToDataTable(
data.Where(x => (x.LocationId == LocationId || LocationIdNotApplied) //comp1
&& x.IsRemoved == false //comp2
&& ((x.IsActive == !IsActiveFalse) || IsActiveNotApplied) //comp3
&& (x.SubscribeDate >= DateTime.Now.AddDays(-7) || !DateApplied))) //comp4
}
}
}
Last Notes
Your case 6 actually seems strange to me:
data.Where(x => (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == true) || (x.Removed == false) || (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == false)));
Note that you have both x.IsActive == true and x.IsActive == false for x.SubscribeDate >= DateTime.Now.AddDays(-7). And you combine it with ||. This is like saying:
(A || true) || (A || false)
And will always return true no matter what. You might want to check again and you may even simplify further/
Last Remarks and Apologize
Thus goes my solution for this case without PredicateBuilder - it requires careful and "systematic" (or, what I actually mean is step by step) analysis on all possible cases.
I have to apologize the OP because I cannot fully test the code which I have proposed for lacking of complete testing resources (unlike the OP).
But if the OP finds that there is a case which I miss to handle or is not put by the OP in the original question, at the very least, the steps presented in my solution above should still be useful for the OP to make his/her own careful analysis for his/her actual cases.
You can optimize search conditions in GetCustomer method. Please take a look Linqkit
Here basic example
http://www.albahari.com/nutshell/predicatebuilder.aspx
https://www.nuget.org/packages/LinqKit/
Using PredicateBuilder
Here's how to solve the preceding example with PredicateBuilder:
IQueryable<Product> SearchProducts (params string[] keywords)
{
var predicate = PredicateBuilder.False<Product>();
foreach (string keyword in keywords)
{
string temp = keyword;
predicate = predicate.Or (p => p.Description.Contains (temp));
}
return dataContext.Products.Where (predicate);
}
First of all, I would not use DataTable anymore but create a ViewModel like this:
public class CustomerVm
{
public string FullName { get; set; }
public int CustomerID { get; set; }
public string LocationName { get; set; }
public bool IsActive { get; set; }
public DateTime SubscribeDate { get; set; }
}
Then create a basic query for grabbing the customers:
var data = from c in context.Customers
where c.Removed == false;
Manipulate the where condition of your query according to your dropdown values:
if(ActiveId == 1)
{
data = data.Where(c => c.IsActive);
}
else if(ActiveId == 1)
{
data = data.Where(c => c.IsActive);
}
if (LocationId != 0)
{
data = data.Where(c => c.LocationId == LocationId);
}
if (stats == "subscribe")
{
data = data.Where(c => c.IsActive
&& c.SubscribeDate >= DateTime.Now.AddDays(-7));
}
else if (stats == "unbsubscribe")
{
data = data.Where(c => !c.IsActive
&& c.SubscribeDate >= DateTime.Now.AddDays(-7));
}
In the end, execute the query and return the results as ViewModels:
return data.Select(c => new CustomerVm {
FullName = c.FullName,
CustomerId = ...
});
I found a different approach more scalable. A real big improvement in performance is to actually perform this filtering in pure SQL and only move the matching data to your application logic.
I was able to make this kind of queries 100x faster (using Oracle) by:
adding a pl/sql procedure which accepts search criteria
building a dynamic SQL statement and bind the search criteria there
ensure that there are no "empty criteria", i.e. if location is not set, we don't build where (location=in_location or in_location is null)
this gives a few fixed SQLs which text and execution plan will be cached
More info on the basics at https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:1669972300346534908
Databases are built for doing data access, filtering and data joining very well on zillions of records. I found leveraging this gives a competitive advantage over any ORM tool which comes with serious limitations (where you give up the whole power of database side processing, you give up all advanced SQLs such as partitioning, and you can't access to technologies like Oracle text because you can't control query relaxation).
I have a where clause that looks up child objects on an entity:
var Lookup = row.Offenses.Where(x => x.Desc == co.Desc && x.Action == co.Action && x.AppealYN == co.AppealYN && x.OffDate == co.OffDate).ToList();
Sometimes the co.OffDate can be null, which will cause an exception. Right now, the only way I can think of to get around that, is to use an if statement:
if (co.OffDate.HasValue)
{
var Lookup = row.Offenses.Where(x => x.Desc == co.Desc && x.Action == co.Action && x.AppealYN == co.AppealYN && x.OffDate == co.OffDate).ToList();
}
else
{
var Lookup = row.Offenses.Where(x => x.Desc == co.Desc && x.Action == co.Action && x.AppealYN == co.AppealYN).ToList();
}
Is there anyway I can re-write the linq query to accomplish what the if statement does? I still want to do a lookup, even if the co.OffDate is null.
You could insert a ternary into your Where filter:
var Lookup = row.Offenses
.Where(x =>
x.Desc == co.Desc
&& x.Action == co.Action
&& x.AppealYN == co.AppealYN
&& (co.OffDate.HasValue ? x.OffDate == co.OffDate : true)
).ToList();
I would rewrite it to be more readable (in my opinion):
var query = row.Offenses.Where(x => x.Desc == co.Desc
&& x.Action == co.Action
&& x.AppealYN == co.AppealYN)
if (co.OffenseDate.HasValue)
{
query = query.Where(x.OffDate == co.OffenseDate);
}
var Lookup = query.ToList();