I have the function shown below that calculates the tax value based on five input parameters.
For each combination of input parameters there is a matching 'condition' which is simply the return statement, e.g. if the input parameters are:
nationality == "German" and
netIncome == 45000 and
(birthDate >= 01.01.1950 && birthDate < 01.01.1960) and
childrenCount == 1
then tax value is 0.15%
the if else statements can get very very long and nested if we consider all input parameter combinations.
public static decimal GetTax(int netIncome, string nationality, DateTime birthDate, int childrenCount, bool handicapped)
{
if (nationality == "DE")
{
if (handicapped)
{
// condition1
return 0;
}
if (birthDate >= new DateTime(1930, 1, 1) && birthDate < new DateTime(1936,1,1))
{
if (childrenCount == 1)
{
// condition2
return 0.05m;
}
else
{
// other conditions
}
}
else if(birthDate >= new DateTime(1950, 1, 1))
{
if (netIncome >= 30000 && netIncome < 40000)
{
if (childrenCount == 1)
{
// condition3
return 0.15m;
}
else if (childrenCount == 2 || childrenCount == 3)
{
// other conditions
return Convert.ToDecimal(netIncome * 0.10);
}
else
{
// other conditions
}
}
if (netIncome >= 40000 && netIncome < 50000)
{
if (childrenCount >= 1 && childrenCount < 3)
{
// other conditions
return Convert.ToDecimal(netIncome * 0.17);
}
else if (childrenCount >= 3 || childrenCount < 5)
{
// other conditions
return Convert.ToDecimal(netIncome * 0.16);
}
else
{
// other conditions
}
}
}
else
{
}
}
else if (nationality == "FR")
{
}
else if (nationality == "IT")
{
}
// just to satisfy the compiler
throw new Exception("The input parameters don't match any condition!");
}
The question is: How to simplify the above if else statements to become a more understandable and easier to maintain code?
I came up with a simple (I hope) solution that I would like to present:
(1) I created a class called Condition shown below
private class Condition
{
private readonly decimal tax;
private readonly Predicate<string> nationalityPredicate;
private readonly Predicate<DateTime> birthDatePredicate;
private readonly Predicate<int> childrenCountPredicate;
private readonly Predicate<int> netIncomePredicate;
private readonly Predicate<bool> handicappedPredicate;
public Condition(decimal taxParameter, Predicate<string> nationality, Predicate<DateTime> birthDate, Predicate<int> netIncome, Predicate<int> childrenCount, Predicate<bool> handicapped)
{
this.tax = taxParameter;
this.nationalityPredicate = new Predicate<string>(nationality);
this.birthDatePredicate = new Predicate<DateTime>(birthDate);
this.netIncomePredicate = new Predicate<int>(netIncome);
this.childrenCountPredicate = new Predicate<int>(childrenCount);
this.handicappedPredicate = new Predicate<bool>(handicapped);
}
public bool IsTrue(string nationality, DateTime birthDate, int netIncome, int childrenCount, bool handicapped)
{
if (this.nationalityPredicate(nationality))
{
if (this.birthDatePredicate(birthDate))
{
if (this.netIncomePredicate(netIncome))
{
if (this.childrenCountPredicate(childrenCount))
{
if (this.handicappedPredicate(handicapped))
{
return true;
}
}
}
}
}
return false;
}
public decimal CalculateTax()
{
Debug.WriteLine(this.tax);
return this.tax;
}
}
this class corresponds to one condition in the nested if else statements in the GetTax() function above. e.g. When the 5 input parameters are Nationality="DE", birthDate="01.01.1955", netIncome=35,000, childrenCount=1 and handicapped=false then the tax for this condition is 0.15
This class is used to break down long complex nested if else statements.
For each return statement in the GetTax() function we use one Condition class instant.
(2) I created the TaxCalculator class that uses the Condition class as an internal helper class as shown below
public class TaxCalculator
{
// List of conditions
private readonly List<Condition> conditions = new List<Condition>();
/// <summary>
/// Constructor. All the conditions are created and initialized here.
/// The conditions initialized in the constructor correspond to the nested if else statements in the GetTax() function.
/// </summary>
public TaxCalculator()
{
// This corresponds to Condition1 in the 'GetTax()' function
Condition condition = new Condition(
0,
nationality => nationality == "DE",
birthDate => true,
netIncome => true,
childrenCount => true,
handicapped => handicapped);
this.conditions.Add(condition);
// This corresponds to Condition2 in the 'GetTax()' function
condition = new Condition(
0.05m,
nationality => nationality == "DE",
birthDate => (birthDate >= new DateTime(1930, 1, 1)) && (birthDate < new DateTime(1936, 1, 1)),
netIncome => true,
childrenCount => childrenCount == 1,
handicapped => !handicapped);
this.conditions.Add(condition);
// This corresponds to Condition3 in the 'GetTax()' function
condition = new Condition(
0.15m,
nationality => nationality == "DE",
birthDate => birthDate >= new DateTime(1950, 1, 1),
netIncome => (netIncome >= 30000 && netIncome < 40000),
childrenCount => childrenCount == 1,
handicapped => !handicapped);
this.conditions.Add(condition);
}
/// <summary>
/// This function corresponds to the GetTax() function, it determines which Condition object corresponds
/// to the input parameters.
/// </summary>
public decimal CalculateTax(string nationality, DateTime birthDate, int netIncome, int childrenCount, bool handicapped)
{
// iterate the conditions
foreach (Condition c in this.conditions)
{
// check if the input parameters apply the current condition
if (c.IsTrue(nationality, birthDate, netIncome, childrenCount, handicapped))
{
// if yes, return the result of this condition
return c.CalculateTax();
}
}
// in case of no matching condition raise an exception.
throw new Exception("TaxCalculator class didn't match any condition for parameters...etc.");
}
}
The TaxCalculator class corresponds to the long nested if else statements in the GetTax() function above.
The class creates Condition objects in the constructor using simple lambda expressions, each Condition object corresponds to one condition in the GetTax() function (marked with //Condition1, //Condition2 and //Condition3)
(3) To calculate the tax for a given set of input parameters we do the following:
TaxCalculator taxCalculator = new TaxCalculator();
// check for conditions
// condition3
decimal tax = taxCalculator.CalculateTax("DE", new DateTime(1985, 1, 1), 33000, 1, false);
// condition2
tax = taxCalculator.CalculateTax("DE", new DateTime(1933, 1, 1), 0, 1, false);
// condition1
tax = taxCalculator.CalculateTax("DE", new DateTime(1977, 1, 1), 0, 0, true);
Questions:
Is there a better and easier solution that provides easy maintenance?
Any optimization or correction suggestions?
I would write the first If like this :
public bool IsTrue(string nationality, DateTime birthDate, int netIncome, int childrenCount, bool handicapped)
{
return (this.nationalityPredicate(nationality) &&
this.birthDatePredicate(birthDate) &&
this.netIncomePredicate(netIncome) &&
this.childrenCountPredicate(childrenCount) &&
this.handicappedPredicate(handicapped))
}
For the rest it's a classical rule checking engine, seems fine. Perhap's just invert the last throw exception. It's a matter of choice, but the exception is not the normal application flow IMHO. So you should throw exception if something is wrong, not if something is not wrong. Like :
public decimal CalculateTax(string nationality, DateTime birthDate, int netIncome, int childrenCount, bool handicapped)
{
var result = this.condition.FirstOrDefault(c => c.IsTrue(nationality, birthDate, netIncome, childrenCount, handicapped));
if(result == null)
throw new Exception("TaxCalculator class didn't match any condition for parameters...etc.");
return result.CalculateTax();
}
Perhap's just be careful with the name. "IsTrue" ? What is true ? What does it means from a business point of view or even technically ? I'm sure it's crystal clear for you, but not for someone who will have to maintain it, he will have to take a look at the method definition (in the best case, if not the entire rule engine) to understand what this "IsTrue" means. At least you have comment, but a good method name is, IMHO, way better than thousands lines of comments.
Related
I have the following code method, which is designed to check if a user is blocked temporarily after invalidating their maximum attempts (say, 10) to login in a two hour period.
public MyError ValidationMethod(MyObject myObject) {
int maxMaximumAttempts = 10;
if (myObject.Attempts >= maximumAttempts && myObject.LastAttempt.Value.AddHours(2)) < DateTime.Now)
return new MyError();
return null;
}
Both of the DateTime fields, "Attempts" and "LastAttempt" are nullable, what is the proper way to format this if statement to ensure I don't get null reference exceptions?
Null for either of the fields, should be the same result as if the if statement returned false, i.e. they haven't tried to login before. I will then return null from the method itself which symbolises, no error was found with the input.
I thought the fields being null would make the whole statement false but instead I get a null reference exception, can anyone explain why please?
Edit: I have added the full method to make the context clearer.
you can this code:
if (myObject != null && myObject.LastAttempt != null)
if (myObject.Attempts >= maximumAttempts && myObject.LastAttempt.Value.AddHours(2) < DateTime.Now)
return new myError();
or:
try
{
if (myObject.Attempts >= maximumAttempts && myObject.LastAttempt.Value.AddHours(2) < DateTime.Now)
return new myError();
}
catch(Exception ex)
{
return new myError(ex.Message);
}
Don't bother with nullables, use DateTime.MaxTime as not yet set value.
and for Attempts use 0 as not yet set value
public class myType {
// initialize myObject.LastAttempt = DateTime.MaxTime
DateTime LastAttempt = DateTime.Max;
int Attempts = 0;
/*
.... rest of the class
*/
}
public MyError ValidationMethod(MyObject myObject) {
int maxMaximumAttempts = 5;
if (myObject.Attempts >= maximumAttempts && myObject.LastAttempt != DateTime.Max && myObject.LastAttempt.AddHours(2)) < DateTime.Now)
return new MyError();
return null;
}
The nullable DateTime object has a property called HasValue, so you could write:
if (myObject.Attempts.HasValue && myObject.Attempts.Value >= maximumAttempts && myObject.LastAttempt.HasValue && myObject.LastAttempt.Value.AddHours(2) < DateTime.Now)
return new myError();
First of all, I would move these checks to the class which holds Attempts and LastAttempt fields (or at least created an extension for this class) - check Tell Don't Ask Principle
public class MyObject
{
public bool IsBlockedOn(DateTime time)
{
if (!Attempts.HasValue || !LastAttempt.HasValue)
return false;
var hasTooManyAttempts = MaximumAttempts <= Attempts.Value;
var timeoutPassed = Timeout < (time - LastAttempt.Value);
return hasTooManyAttempts && !timeoutPassed;
}
private TimeSpan Timeout = TimeSpan.FromHours(2);
private int MaximumAttempts = 10;
// other properties
}
Your if statement:
if (myObject.IsBlockedOn(DateTime.Now))
return new MyError();
The good part - this logic is now unit-testable because you can execute it with different time arguments.
Also, consider making Attempts non-nullable and initialize it with 0.
I am having some trouble with constructors in a C# assignment. The first block of code below contains some of the instructions for the assignment. Essentially, as I understand it, I am to create two constructors, one for setting a default date, and another for checking a date from the user. Also, there is a SetDate method that seems to be doing the same thing. These seem redundant, yet according to the assignment instructions, both are required. I am VERY new to object-oriented programming so I am not sure how to "pass" stuff to a constructor or really how to use it and call it in the main method. The second block of code is what I have written so far. All of the date validation methods seem fine. But, I have no idea what to do with the public Date(int M, int D, int Y) constructor and the SetDate method. What should each of these be doing? Also, why am I being instructed to use integer variables M, D, Y when I am also being told to declare Month, Day, and Year above? Any insight that might aid me in understanding how to use this Constructer and how it relates and differs in function from the SetDate method would be greatly appreciated.
//Create a Date Class
//This class holds:
private int Month;
private int Day;
private int Year;
//Include the following constructors/methods. Include others/more if you
//need them.
// Sets date to 1/1/1900
public Date()
// Sets date to user’s input.
// Checks to see the date is valid
// If it isn’t valid, print message and set date to 1/1/1900
public Date(int M, int D, int Y)
// Sets date to user’s input.
// Checks to see the date is valid
// If it isn’t valid, print message and set date to 1/1/1900
public Boolean SetDate(int M, int D, int Y)ere
//******************************************************************************
class Date
{
private int Month;
private int Day;
private int Year;
// Sets date to 1/1/1900
public Date()
{
Month = 1;
Day = 1;
Year = 1900;
}
public Date(int M, int D, int Y)
{
Month = M;
Day = D;
Year = Y;
}
public Boolean SetDate(int M, int D, int Y)
{
int valDate = 0;
Console.WriteLine("You will be prompted to enter three(3) numbers to represent a month, " +
"day, and year. Only dates between 1/1/1900 and 12/31/2100 are valid.");
Console.WriteLine("");
while (valDate < 1)
{
Console.WriteLine("Enter the number for the month.");
M = int.Parse(Console.ReadLine());
Console.WriteLine("");
Console.WriteLine("Enter the number for the day.");
D = int.Parse(Console.ReadLine());
Console.WriteLine("");
Console.WriteLine("Enter the number for the year.");
Y = int.Parse(Console.ReadLine());
Console.WriteLine("");
ValidateDate();
if (ValidateDate())
{
DisplayDate();
valDate++;
return true;
}
else
{
Console.WriteLine("Please enter a valid date.");
Console.WriteLine("");
Month = 1;
Day = 1;
Year = 1900;
return false;
}
}
return false;
}
// Determines if date is valid.
public Boolean ValidateDate()
{
ValidateMonth();
ValidateDay();
ValidateYear();
if (ValidateMonth() && ValidateDay() && ValidateYear())
{
return true;
}
else
{
return false;
}
}
// Determines if month is valid.
public Boolean ValidateMonth()
{
if (Month >= 1 && Month <= 12)
{
return true;
}
else
{
return false;
}
}
// Determines if year is valid.
public Boolean ValidateYear()
{
if(Year >= 1900 && Year <= 2100)
{
return true;
}
else
{
return false;
}
}
// Determines if day is valid
public Boolean ValidateDay()
{
IsLeapYear();
if(Month == 1 || Month == 3 || Month == 5 || Month == 7 || Month == 8 || Month == 10 || Month == 12)
{
if (Day >= 1 && Day <= 31)
{
return true;
}
else
{
return false;
}
}
else if (Month == 4 || Month == 6 || Month == 9 || Month == 11)
{
if (Day >= 1 && Day <= 30)
{
return true;
}
else
{
return false;
}
}
else if (Month == 2 && IsLeapYear())
{
if (Day >= 1 && Day <= 29)
{
return true;
}
else
{
return false;
}
}
else if (Month == 2 && !IsLeapYear())
{
if (Day >= 1 && Day <= 28)
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
// Determine if year is a leap year
public Boolean IsLeapYear()
{
if ((Year % 4 == 0 && Year % 100 != 0) || (Year % 400 == 0))
{
return true;
}
else
{
return false;
}
}
// Print date to screen in format M/D/Y
public void DisplayDate()
{
Console.WriteLine(ShowDate());
}
public String ShowDate()
{
StringBuilder myStringBuilder = new StringBuilder();
myStringBuilder.AppendFormat("{0} / {1} / {2}", Month, Day, Year);
return (myStringBuilder.ToString());
}
static void Main(string[] args)
{
Date NewDate = new Date();
NewDate.Date();
Console.ReadLine();
}
}
It looks like you've been asked to create a method and a constructor that are doing the same thing. The simple thing to do in this case is to have the constructor call the method.
The only comment I would make about your code is that the problem statement you showed did not require the input to be gathered in the SetDate method. Given the statement it seems like the input from the user would be gathered outside your class.
I don't know what your requirements are for the failure message. That might make sense in it's own method as well.
Here is an example:
public class Date
{
private int Month;
private int Day;
private int Year;
public Date()
{
SetDefaultDate();
}
public Date(int M, int D, int Y)
{
SetDate(M, D, Y);
}
public void SetDate(int M, int D, int Y)
{
if (IsValidDate(M, D, Y))
{
Month = M;
Day = D;
Year = Y;
}
else
{
SetDefaultDate();
}
}
private bool IsValidDate(int M, int D, int Y)
{
// validation logic.. return true if all parameters result in valid date
// false if they do not. If it is an invalid date print the failure message.
return true;
}
private void SetDefaultDate()
{
Month = 1;
Day = 1;
Year = 1900;
}
}
If you want to make some real application using date then i'd suggesting taking look at the DateTime struct in System namespace. That has TryParse function which will return you true or false whether if the input was valid or not.
However it seems like you're doing some programming exercise so in that case my answer would be: It's not very good to have arguments in constructor which could lead to an invalid object - in your case invalid date. That's because once calling the constructor you will have an object in one way or another, except if you throw exception in constructor. Yet if you still want to have it like that then you need to have a property or public variable called "IsValid" in the class/struct which tells if valid date was given with constructor.
Nicer option is follow the DateTime approach - have a public static function which return valid Date object. Like this:
public bool TryParse(int m, int d, int y, out Date date)
{
// validate
// if valid then return Date object like that:
date = new Date()
{
Month = m,
Day = d,
Year = y
};
return true;
// Or like that:
date = new Date(m, d, y);
return true;
// if not valid then return null (because have to return something)
date = null;
return false;
}
I am trying to determine whether times ranges are overlapping on a CRUD page but am stuck.
I can get it working with two time ranges using the code below but I need it working for 3 as well.
public static bool IsOverLapping(ConfigureViewModel viewModel)
{
bool status = false;
var times = viewModel.Periods.OrderBy(x => x.StartTime.TimeOfDay).ToList();
for (var i = 0; i <= times.Count - 2; i++)
{
if (times[i].StartTime.TimeOfDay <= times[i + 1].EndTime.TimeOfDay)
{
if (times[i + 1].StartTime.TimeOfDay >= times[i].EndTime.TimeOfDay)
status = false;
else
return true;
}
else
return true;
}
return status;
}
The data comes in as DateTime values which is why I have only looked at the 'TimeOfDay' value. The image shows the layout of the CRUD page.
This is actually trickier than it seems, as you need to handle time periods that wrap across midnight.
Using some extension methods, you can make it straight forward.
First, determine if a time is between two others:
public static bool Between(this TimeSpan aTime, TimeSpan startTime, TimeSpan endTime) => (startTime <= endTime) ? (startTime < aTime && aTime < endTime)
: (startTime < aTime || aTime < endTime);
Then create a special version using the Period class for the range:
public static bool Between(this TimeSpan aTime, Period aPeriod) => aTime.Between(aPeriod.StartTime.TimeOfDay, aPeriod.EndTime.TimeOfDay);
Finally create a test for if one range overlaps a second range (note this is asymmetric):
public static bool Overlaps(this Period aPeriod, Period bPeriod) => aPeriod.StartTime.TimeOfDay.Between(bPeriod) || aPeriod.EndTime.TimeOfDay.Between(bPeriod);
Now go through all the ranges and check if any range overlaps another range:
public static bool IsOverLapping(this List<Period> periods) {
var periodCount = periods.Count;
for (int j1 = 0; j1 < periodCount; ++j1)
for (int j2 = 0; j2 < periodCount; ++j2)
if (j1 != j2 && periods[j1].Overlaps(periods[j2]))
return true;
return false;
}
Finally you can use the method in your ConfigureViewModel method:
public static bool IsOverLapping(ConfigureViewModel viewModel)
{
bool status = false;
var times = viewModel.Periods.OrderBy(x => x.StartTime.TimeOfDay).ToList();
return times.IsOverLapping();
}
I think it might be simpler than it sounds. If you have period1 and period2, they are NOT overlapping if period1.Start > period2.End or if period1.End < period2.Start. If neither of these are true, then we know that they are overlapping:
I made this a static method on the Period class:
public class Period
{
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public static bool AreOverlapping(Period first, Period second)
{
if (first == null || second == null) return false;
// These two conditions define "overlapping" and must be true
return first.StartTime <= second.EndTime &&
first.EndTime >= second.StartTime;
}
}
Then this should simplify the logic in your method that detects if there are any overlapping periods in a group:
public static bool DoAnyOverlap(List<Period> periods)
{
if (periods == null || periods.Count < 2) return false;
var ordered = periods.OrderBy(p => p.StartTime).ToList();
for (var i = 0; i < ordered.Count - 1; i++)
{
if (Period.AreOverlapping(ordered[i], ordered[i + 1])) return true;
}
return false;
}
If for some reason you cannot modify the Period class, the logic can easily be incorporated into the DoAnyOverlap method:
public static bool DoAnyOverlap(List<Period> periods)
{
if (periods == null || periods.Count < 2) return false;
var ordered = periods.Where(p => p != null).OrderBy(p => p.StartTime).ToList();
for (var i = 0; i < ordered.Count - 1; i++)
{
if (ordered[i].StartTime <= ordered[i + 1].EndTime &&
ordered[i].EndTime >= ordered[i + 1].StartTime)
{
return true;
}
}
return false;
}
Try this:
var periods = new[]
{
new { start = TimeSpan.Parse("10:00"), end = TimeSpan.Parse("14:00") },
new { start = TimeSpan.Parse("16:00"), end = TimeSpan.Parse("17:00") },
new { start = TimeSpan.Parse("13:00"), end = TimeSpan.Parse("15:00") },
};
bool overlapping =
Enumerable
.Range(0, periods.Length)
.SelectMany(i =>
Enumerable
.Range(i + 1, periods.Length - i - 1),
(i, j) => new { A = periods[i], B = periods[j] })
.Any(x => !(x.B.start >= x.A.end || x.B.end <= x.A.start));
It'll work with DateTime too.
Assuming ConfigureViewModel class looks like that:
class ConfigureViewModel
{
...
public List<Period> Periods { get; set; }
}
class Period
{
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
}
You can use Time Period Library for .NET:
PM> Install-Package TimePeriodLibrary.NET
And IsOverLapping mathod may look as simple as that:
public static bool IsOverLapping(ConfigureViewModel vm, int numberOfOverlaps)
{
var ranges = vm.Periods.Select(q => new TimeRange(q.StartTime, q.EndTime));
TimePeriodCollection periodCollection = new TimePeriodCollection(ranges);
TimePeriodIntersector<TimeRange> intersector = new TimePeriodIntersector<TimeRange>();
return intersector.IntersectPeriods(periodCollection).Count > numberOfOverlaps;
}
I have an object that has a calculation field; The calculation formula is too complex, and it have to check some condition, and load some data from another objects (have to join with other tables).
This is my Object:
public class LoadAndExitPermit : Master, IColorSource
{
private int? _ladingBillId;
[DataMember]
public virtual int? LadingBillID
{
get { return _ladingBillId; }
set { SetPropertyValue(ref _ladingBillId, value, "LadingBillID"); }
}
.
. {lots of Properties)
.
[DataMember]
public virtual ICollection<LoadAndExitPermitDocumentDetail> LoadAndExitPermitDocumentDetails { get; set; }
The important part of my object is SumGoodsUnitPrice, and it's:
[NotMapped, DataMember]
Public decimal? SumGoodsUnitPrice
{
get
{
if (LoadAndExitPermitDetails == null || !LoadAndExitPermitDetails.Any())
{
return -1;
}
if (LoadAndExitPermitDetails.First().RequestDetail.OrderDetail == null)
{
decimal? sum = 0m;
foreach (var item in LoadAndExitPermitDetails)
{
decimal? tarrif = item.Good.GoodsTariffDetails.Where(g =>
g.Price > 0 && g.IsActive == true && g.GoodsTariff.GoodsTariffType.IsPublic == true && g.GoodsTariff.FromDate < Date)
.OrderByDescending(w => w.GoodsTariff.FromDate).Select(c => c.Price).FirstOrDefault();
if (tarrif != null)
{
sum += ((item.Quantity ?? 0) * (1.09m * (tarrif))) ?? 0m;
}
else
{
decimal? lastTariff = item.Good.GoodsTariffDetails.Where(x => x.Price > 0 && x.IsActive == true
&& x.GoodsTariff.FromDate < Date).OrderByDescending(w => w.GoodsTariff.FromDate).Select(c => c.Price).FirstOrDefault() ?? 0m;
sum += ((item.Quantity ?? 0) * (1.09m * (lastTariff))) ?? 0;
}
}
return sum;
}
var z = LoadAndExitPermitDetails.Sum(l => (l.Quantity ?? 0) * (1.09m * (l.RequestDetail.OrderDetail.Quantity == 0 ? 0 : (l.RequestDetail.OrderDetail.Price / l.RequestDetail.OrderDetail.Quantity) ?? 0)));
return z;
}
private set
{
}
}
I have some Issues:
I can't use Projection, I have two reasons: first readability of the code and the 2th one is my project structure doesn't allow me to use projected query.
We use database first method and this field is not in database, because its calculated field.
We deal with a large database and I can't Include (join) all tables i need, so i prefer to use SQL service side query to calculate it for me.
We can't use Stored Procedure Or View, Because we have lots of this kind of columns, and we need to use dynamic query for dynamic filters.
The performance of query is very important for us.
I would appropriate if anyone can help me.
If I where you, I add a view that calculate this field on the server and use for queries this view. This is compatible with CQRS architectural pattern.
I have a LINQ query that queries a DataTable. In the DataTable, the field is a string and I need to compare that to an integer, basically:
if ((electrical >= 100 && electrical <= 135) || electrical == 19)
{
// The device passes
}
the problem is, I am trying to do this in LINQ like this:
var eGoodCountQuery =
from row in singulationOne.Table.AsEnumerable()
where (Int32.Parse(row.Field<String>("electrical")) >= 100 &&
Int32.Parse(row.Field<String>("electrical")) <= 135) &&
Int32.Parse(row.Field<String>("electrical")) != 19 &&
row.Field<String>("print") == printName
select row;
I keep getting the exception:
Input string was not in a correct format
The main problem occurs when electrical == ""
Unfortunately, the framework doesn't provide a nice clean way to handle parsing scenarios where it fails. Of what's provided, they only throw exceptions or use out parameters, both of which does not work well with linq queries. If any one value you're parsing fails, the entire query fails and you just can't really use out parameters. You need to provide a method to handle the parsing without that does not throw and does not require using out parameters.
You can handle this in many ways. Implement it where upon failure, you return some default sentinel value.
public static int ParseInt32(string str, int defaultValue = 0)
{
int result;
return Int32.TryParse(str, out result) ? result : defaultValue;
}
Or what I would recommend, return a nullable value (null indicating it failed).
public static int? ParseInt32(string str)
{
int result;
return Int32.TryParse(str, out result) ? result : null;
}
This simplifies your query dramatically while still leaving it readable.
public bool GetElectricalStatus(string printName)
{
var query =
from row in singulationOne.Table.AsEnumerable()
where row.Field<string>("print") == printName
// using the nullable implementation
let electrical = ParseInt32(row.Field<string>("electrical"))
where electrical != null
where electrical == 19 || electrical >= 100 && electrical <= 135
select row;
return !query.Any();
}
p.s., your use of the Convert.ToInt32() method is incorrect. It is the same as calling Int32.Parse() and does not return a nullable, it will throw on failure.
I would check if the data in the column does not contain leading/trailing whitespaces - i.e. "15 " rather than "15" and if it does (or might do) trim it before trying to convert:
Int32.Parse(row.Field<String>("electrical").Trim())
BTW: not related to the error but I'd use let statement to introduce a local variable and do the conversion once:
let x = Int32.Parse(row.Field<String>("electrical").Trim())
where x >= 100...
I could not get anything to work, so I re-did the whole method:
public bool GetElectricalStatus(string printName)
{
List<object> eGoodList = new List<object>();
var eGoodCountQuery =
from row in singulationOne.Table.AsEnumerable()
where row.Field<String>("print") == printName
select row.Field<String>("electrical");
foreach (var eCode in eGoodCountQuery)
{
if (!string.IsNullOrEmpty(eCode.ToString()))
{
int? eCodeInt = Convert.ToInt32(eCode);
if (eCodeInt != null &&
(eCodeInt >= 100 && eCodeInt <= 135) || eCodeInt == 19)
{
eGoodList.Add(eCode);
}
}
}
if (eGoodList.Count() > 0)
{
return false;
}
else
{
return true;
}
}
The main problem occurs when electrical == ""
Why not make a function that does your evaluation, and call it in your Linq query. Put logic in to check the validity of the data contained within (so if you can't parse the data, it should return false)...
The function:
bool IsInRange(string text, int lower, int upper, params int[] diqualifiers)
{
int value = int.MinValue;
if (!int.TryParse(text, out value)) {
return false;
}
if (!(value >= lower && value <= upper)) {
return false;
}
if (disqualifiers != null && disqualifiers.Any(d => d == value)) {
return false;
}
return true;
}
The Linq query...
var eGoodCountQuery =
from row in singulationOne.Table.AsEnumerable()
where
IsInRange(row.Field<String>("electrical"), 100, 135, 19)
&& row.Field<String>("print") == printName
select row;