Search function LINQ. Use checkbox values as parameters - c#

I have a search function that tales 7 parameters and 6 of them are checkbox values. They are bools like shown down below. I Have a problem with my datetime search. If the user check that box if would like do run a serach function where that a date is not null in the database. My code look like this at the moment. But i would like to get some feedback to how to improve my code for better performance and code structure.
public List<Invoice> GetAllInvoicesBySearch(int merchant, long buyer, bool send, bool paid, bool firstReminder, bool secondReminder, bool invoiceClaim)
{
var sendValue = new InvoiceStatus();
var paidValue = new InvoiceStatus();
var firstRemind = new DateTime();
var secondRemind = new DateTime();
if (buyer <= 0)
{
return null;
}
if (send)
{
sendValue = InvoiceStatus.Sent;
}
if (paid)
{
paidValue = InvoiceStatus.Paid;
}
if (firstReminder)
{
firstRemind = DateTime.Now;
}
if (secondReminder)
{
secondRemind = DateTime.Now;
}
return
Context.Invoices.Where(
i =>
i.InstallationId == merchant && i.Buyer.Msisdn == buyer || i.Status == sendValue || i.Status == paidValue ||
i.FirstReminderDate == firstRemind || i.SecondReminderDate == secondRemind).ToList();
}
So my problem is with the datetime at the moment to get that correctly. Any suggestions on how to solve my problem and improve my code?

You would be better off using a PredicateBuilder and converting to an extension method like this:
public static class ExtensionMethods {
public static IQueryable<Invoice> Search(this IQueryable<Invoice> src, int merchant,
long buyer, bool send, bool paid, bool firstReminder,
bool secondReminder, bool invoiceClaim)
{
var predicate = PredicateBuilder.False<Invoice>();
var sendValue = new InvoiceStatus();
var paidValue = new InvoiceStatus();
var firstRemind = new DateTime();
var secondRemind = new DateTime();
if (buyer <= 0)
{
return src.Where(predicate);
}
predicate=predicate.Or(i=>i.InstallationId == merchant && i.Buyer.Msisdn == buyer);
if (send)
{
predicate=predicate.Or(i=>i.Status == InvoiceStatus.Sent);
}
if (paid)
{
predicate=predicate.Or(i=>i.Status == InvoiceStatus.Paid);
}
if (firstReminder)
{
predicate=predicate.Or(i=>i.FirstReminderDate == DateTime.Today);
}
if (secondReminder)
{
predicate=predicate.Or(i=>i.SecondReminderDate == DateTime.Today);
}
return src.Where(predicate);
}
}
Also, if I understand your code correctly, InvoiceStatus should either be an enum, or at the very least, InvoiceStatus.Sent and InvoiceStatus.Paid should be marked as being static. Reference it like so:
Context.Invoices.Search(...).OrderBy(...)...
It will make further manipulation much easier if you need to do sorting, paging, filtering beyond the original searching.

Related

How to convert nested for loop with if condition to .Net Linq?

I am working on a function which will modify the input payload properties. The payload contains nodes and each node contains list of features. I need to remove some specific features match condition and also modify each node window property start and end time. I have written the function using traditional nested for loop, but struggling to convert it to Linq function. Anyone has idea how to convert this nested for loop function to a Linq function?
private void ApplyTransformation(InputPayload payload, int startTime = 8, int endTime = 15)
{
var nodes = payload.Nodes;
for (var i = 0; i < nodes.Count(); ++i)
{
var node = nodes[i];
var features = node.Features;
for (var j = 0; j < features.Count(); ++j)
{
var feature = features[j];
if (feature.NodeFeatureTypeID
== FeatureTypeEnum.FEATURE_A
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_B
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_C
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_D
)
{
features.RemoveAt(j);
}
}
var windows = node.Windows;
for (var k = 0; k < windows.Count(); ++k)
{
var window = windows[k];
if (window.NodeFunctionTypeID == FeatureTypeEnum.MM_HOURS) continue;
window.StartHour = new TimeSpan(startTime, 0, 0);
window.EndHour = new TimeSpan(endTime, 0, 0);
}
}
}
Let's do it in parts. This first code of yours removes features that are in a list,
for (var j = 0; j < features.Count(); ++j)
{
var feature = features[j];
if (feature.NodeFeatureTypeID
== FeatureTypeEnum.FEATURE_A
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_B
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_C
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_D
)
{
features.RemoveAt(j);
}
}
We need to convert that to "keep features not in a list"
var discard = new [] {FeatureTypeEnum.FEATURE_A, FeatureTypeEnum.FEATURE_B, FeatureTypeEnum.FEATURE_C, FeatureTypeEnum.FEATURE_D };
node.Features = node.Features.Where(f => !discard.Contains(f)).ToArray();
This part of your code skips if function type is a certain kind, and zeroes timespans if the function type is not a certain kind:
if (window.NodeFunctionTypeID == FeatureTypeEnum.MM_HOURS) continue;
window.StartHour = new TimeSpan(startTime, 0, 0);
window.EndHour = new TimeSpan(endTime, 0, 0);
Which might be better as a loop that operates on just those things we are interested in (we want to modify only those Windows where the nodefuctionTypeId is not Hours):
foreach(var window in node.Windows.Where(w => w.NodeFunctionTypeID != FeatureTypeEnum.MM_HOURS){
window.StartHour = new TimeSpan.FromHous(startTime);
window.EndHour = new TimeSpan.FromHours(endTime);
}
Meaning the whole is:
private void ApplyTransformation(InputPayload payload, int startTime = 8, int endTime = 15)
{
var discard = new [] {FeatureTypeEnum.FEATURE_A, FeatureTypeEnum.FEATURE_B, FeatureTypeEnum.FEATURE_C, FeatureTypeEnum.FEATURE_D };
foreach (var node in payload.Nodes)
{
node.Features = node.Features.Where(f => !discard.Contains(f)).ToArray();
foreach(var window in node.Windows.Where(w => w.NodeFunctionTypeID != FeatureTypeEnum.MM_HOURS){
window.StartHour = TimeSpan.FromHous(startTime);
window.EndHour = TimeSpan.FromHours(endTime);
}
}
}
I don't think I'd convert it all to a Linq form, as it would make a mess; linq queries should not have side effects (modify the objects the query iterates over) so particularly the second part where the time spans are being zeroed would have to become an operation where everything about the window was being copied over to a new Window if you wanted to LINQ it.
If a window is literally just a time span pair then it's not so bad, or if you want to provide a constructor that takes an existing Window and a startTime and endTime:
public Window(Window copyFrom, int startTime, int endTime){
this.X = copyFrom.X;
this.Y = copyFrom.Y;
...
this.StartHour = TimeSpan.FromHours(strtTime);
this.EndHour = TimeSpan.FromHours(endTime);
}
Then maybe your method could become some linq:
foreach (var node in payload.Nodes)
{
node.Features = node.Features.Where(f => !discard.Contains(f)).ToArray();
node.Windows = node.Windows.Select(w => w.NodeFunctionTypeID == FeatureTypeEnum.MM_HOURS ? w : new Window(w, startTime, endTime).ToArray();
}
..but I don't know if I would try for a wholesale replacement of the entire nodes list, for reasons even based on the name of the method: ApplyTransformations sounds like it means "take this list of nodes and change bits of them" not "take this list of nodes and give me a new list with some or all of the nodes replaced or modified" - that sort of behavior in code could wreck something else, if the calling code is expecting a tweak and the object it sends (or objects within it) are swapped out for new ones
Using a linq query for the second part would make things messier, better to just have a for loop
Something like this should work:
var nodes = payload.Nodes;
nodes.Features = nodes.Features.Where(f => !(
f.NodeFeatureTypeID == FeatureTypeEnum.FEATURE_A
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_B
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_C
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_D
)
);
foreach(var window in nodes.Windows)
{
if (window.NodeFunctionTypeID != FeatureTypeEnum.MM_HOURS)
{
window.StartHour = new TimeSpan(startTime, 0, 0);
window.EndHour = new TimeSpan(endTime, 0, 0);
}
}
Pls have look at below code snippet to avoid nested loop using linq.
public class Employee
{
public string Name { get; set; }
public List<EmployeeProject> EmployeeProject { get; set; }
}
public class EmployeeProject
{
public string ProjectName { get; set; }
public string ClientName { get; set; }
}
/// <summary>
/// Object mapping
/// </summary>
/// <returns></returns>
public static List<Employee> CreateObject()
{
var employeeList = new List<Employee>();
var employee = new Employee();
var employeeProjectList = new List<EmployeeProject>();
employee.Name = "John";
var employeeProject = new EmployeeProject();
employeeProject.ProjectName = "Chrome";
employeeProject.ClientName = "Google";
employeeProjectList.Add(employeeProject);
employeeProject = new EmployeeProject();
employeeProject.ProjectName = "WhatsApp";
employeeProject.ClientName = "Meta";
employeeProjectList.Add(employeeProject);
employee.EmployeeProject = employeeProjectList;
employeeList.Add(employee);
employee.Name = "Alex";
employeeProjectList = new List<EmployeeProject>();
employeeProject = new EmployeeProject();
employeeProject.ProjectName = "Chrome2";
employeeProject.ClientName = "Google2";
employeeProjectList.Add(employeeProject);
employeeProject = new EmployeeProject();
employeeProject.ProjectName = "WhatsApp2";
employeeProject.ClientName = "Meta2";
employeeProjectList.Add(employeeProject);
employee.EmployeeProject = employeeProjectList;
employeeList.Add(employee);
return employeeList;
}
/// <summary>
/// Linq function
/// </summary>
public static void LinqFunctionForNestedQuery()
{
var employeeObject = CreateObject();
var result1 = employeeObject.Select(x =>
{
x.EmployeeProject = x.EmployeeProject.Select(y =>
{
y.ProjectName.Contains("Chrome");
return y;
}).ToList();
return x;
});
}
To maintain readability of the code and to make it simpler , I have modified the code.
Step1 : Replace for with foreach if you can
Step2 : Replace foreach with linq
Important tip. Resharper helps you with code suggestions.
Solution
private void ApplyTransformation(InputPayload payload, int startTime = 8, int endTime = 15)
{
foreach (var node in payload.Nodes)
{
var features = node.Features;
foreach (var feature in features.Where(feature => feature.NodeFeatureTypeID == FeatureTypeEnum.FEATURE_A ||
feature.FeatureTypeID == FeatureTypeEnum.FEATURE_B ||
feature.FeatureTypeID == FeatureTypeEnum.FEATURE_C ||
feature.FeatureTypeID == FeatureTypeEnum.FEATURE_D))
{
features.Remove(feature);
}
var windows = node.Windows;
foreach (var window in windows
.Where(window => window.NodeFunctionTypeID != FeatureTypeEnum.MM_HOURS))
{
window.StartHour = new TimeSpan(startTime, 0, 0);
window.EndHour = new TimeSpan(endTime, 0, 0);
}
}
}

C# Filter Items In A List According To Multiple Criteria

First, what my situation here is...
My SomeObject has a property string Status which I am interested in for this scenario.
Status property can contain "Open", "Closed", "Finished" values exactly.
I have a method called FilterObjects which returns a List<SomeObject>
Method accepts an argument same as its return type, List<SomeObject>
Method is supposed to filter according to following cases explained below and return the list of objects.
The List<SomeObject> I am sending as argument to my method is guaranteed to be in order (through their ID and type).
The cases are (all related to the string Status property I mentioned):
If any item in the list contains Status = "Finished"; then eliminate all other elements that was in the original list and return only the object that has the "Finished" status.
If any item does NOT contain Status = Finished but contains "CLOSED", I need to check if there is any other item that has the value of "Open" after that "CLOSED" one. You can think of this as a "a task can be closed, but can be reopened. But once it is finished, it cannot be reopened".
If it contains a "CLOSED" and does not have any "OPEN" after that item, I will ignore all the items before CLOSED and only return CLOSED object. If it contains "OPEN" after any closed, I need to return anything AFTER that CLOSED, by excluding itself.
I also tried explain the same thing with my awesome MS Paint skills.
The object itself is not really a problem, but my method is something like this:
private List<SomeObject> FilterObjects(List<SomeObject> objectList)
{
var objects = objectList;
var returnList = new List<SomeObject>();
foreach (var obj in objects)
{
if (obj.Status == "Finished")
{
returnList.Add(obj);
return returnList;
}
}
return new List<SomeObject>();
}
Long story short, what would be the best and most efficient way to apply all this logic in this single method? Honestly, I couldn't go further than the first case I already implemented, which is the FINISHED. Could this whole thing be done with some LINQ magic?
It is guaranteed that I receive an ordered list AND I will never get items more than a couple of hundred so the collection will never be massive.
Many thanks in advance for the help.
You can try something like that:
private List<SomeObject> FilterObjects(List<SomeObject> objectList)
{
SomeObject finished = objectList.FirstOrDefault(o => o.Status.Equals("Finished"));
if (finished != null) { return new List<SomeObject> { finished }; }
List<SomeObject> closed = objectList.SkipWhile(o => !o.Status.Equals("Closed")).ToList();
if (closed.Count == 1) { return closed; }
if (closed.Count > 1) { return closed.Skip(1).ToList(); }
// if you need a new list object than return new List<SomeObject>(objectList);
return objectList;
}
I really wouldn't bother using Linq for this, as you will either create an overly complicated instruction to manage or you will require several loop iterations. I would go for something like this instead:
private List<SomeObject> FilterObjects(List<SomeObject> objectList)
{
int lastClosed = -1;
for (int i = 0; i < objectList.Count; i++)
{
if (objectList[i].Status == "Closed")
lastClosed = i;
else if (objectList[i].Status == "Finished")
return new List<SomeObject>() { objectList[i] };
}
if (lastClosed > -1)
if (lastClosed == objectList.Count - 1)
return new List<SomeObject>() { objectList[lastClosed] };
else
return objectList.Skip(lastClosed + 1).ToList();
else
return objectList;
}
EDIT: slightly changed the last bit of code so that it won't trigger an exception if the objectList is empty
LINQ is not well suited and inefficient for scenarios where you need to apply logic based on previous / next elements of a sequence.
The optimal way to apply your logic is to use a single loop and track the Closed status and the position where the status change occurred. At the end you'll return a single element at that position if the last status is Closed, or a range starting at that position otherwise.
static List<SomeObject> FilterObjects(List<SomeObject> objectList)
{
int pos = 0;
bool closed = false;
for (int i = 0; i < objectList.Count; i++)
{
var item = objectList[i];
if (item.Status == "Finished")
return new List<SomeObject> { item };
if (item.Status == (closed ? "Opened" : "Closed"))
{
pos = i;
closed = !closed;
}
}
return objectList.GetRange(pos, closed ? 1 : objectList.Count - pos);
}
I did it this way:
public static IEnumerable<SomeObject> convert(this IEnumerable<SomeObject> input)
{
var finished = input.FirstOrDefault(x => x.Status == "Finished");
if (finished != null)
{
return new List<SomeObject> {finished};
}
return input.Aggregate(new List<SomeObject>(), (a, b) =>
{
if (!a.Any())
{
a.Add(b);
}
else if (b.Status == "Open")
{
if (a.Last().Status == "Closed")
{
a.Remove(a.Last());
}
a.Add(b);
}
else if (b.Status == "Closed")
{
a = new List<SomeObject> {b};
}
return a;
});
}
You can write a method like this. This is bare minimum you will have to add null check and exception handling.
public List<SomeCls> GetResult(List<SomeCls> lstData)
{
List<SomeCls> lstResult;
if(lstData.Any(x=>x.Status=="Finished"))
{
lstResult = lstData.Where(x=>x.Status=="Finished").ToList();
}
else if(lstData.Any(x=>x.Status=="Closed"))
{
// Here assuming that there is only one Closed in whole list
int index = lstData.FindIndex(0,lstData.Count(),x=>x.Status=="Closed");
lstResult = lstData.GetRange(index,lstData.Count()-index);
if(lstResult.Count()!=1) // check if it contains Open.
{
lstResult = lstResult.Where(x=>x.Status=="Open").ToList();
}
}
else // Only Open
{
lstResult = lstData;
}
return lstResult;
}
something like this :
private List<SomeObject> FilterObjects(List<SomeObject> objectList)
{
if (objectList.Where(x => x.Status == "Finished").Any())
{
return objectList.Where(x => x.Status == "Finished").ToList();
}
else if (objectList.Where(x => x.Status == "Closed").Any())
{
if (objectList.FindIndex(x => x.Status == "Closed") == objectList.Count() - 1)
{
return objectList.Where(x => x.Status == "Closed").ToList();
}
else
{
return objectList.GetRange(objectList.FindIndex(x => x.Status == "Closed") + 1, objectList.Count() - (objectList.FindIndex(x => x.Status == "Closed") + 1));
}
}
return objectList;
}

Calling IF Statement method in C#

I have noticed that I am repeating a lot of my code. What I want to do is store the below if-statement in a method so that it can be called at any time. My problem is, I understand methods, slightly, but I'm having difficulty creating a method for what i want. Below is an example of my code. The commented out code is the code i want to put in a method.Any help is much appreciated.
if (result.Equals("") && type == "")
{
JObject jobj = JObject.Parse(Helper.callerClass(#""));
var titleTokens = jobj.SelectTokens("...title");
var values = titleTokens.Select(x => (x as JProperty).Value);
/*if (titleTokens.Contains(s))
{
MessageBox.Show("");
}
else
{
MessageBox.Show("");
}*/
}
private bool Check(string result, string type) {
if (result.Equals("") && type == "")
{
JObject jobj = JObject.Parse(Helper.callerClass(#""));
var titleTokens = jobj.SelectTokens("...title");
var values = titleTokens.Select(x => (x as JProperty).Value);
return titleTokens.Contains(s);
}
return false;
}
You mean like this ?
//To call the function use the following line and replace yourClassName with you class name
yourClassName myInstObj= new yourClassName();
myInstObj.Check(result,type);
//Copied from the above answer b/c it should work fine.
private bool Check(string result, string type) {
if (result.Equals("") && type == "")
{
JObject jobj = JObject.Parse(Helper.callerClass(#""));
var titleTokens = jobj.SelectTokens("...title");
var values = titleTokens.Select(x => (x as JProperty).Value);
if (titleTokens.Contains(s))
{
return true;
}
else
{
return false;
}
}
}

Troubles with large amount of Code duplication

I have a lot of methods that follow in large parts the same algorithm, and I ideally want to be able to make calls to a generic method that eliminates a lot of code duplication.
I have tons of methods like the ones below, I would optimally want to be able to just call
Save<SQLiteLocation>(itemToSave); but i have a great deal of trouble since that the methods
SQLiteConnection.Find<T> won't accept an abstract data type like T in generics.
Is there any way to get around this, if i could get it fixed i would save as many as 150 lines of code
Here's my code:
public bool SaveLocation(ILocation location, ref int primaryKey)
{
var dbConn = new SQLiteConnection (dbPath);
SQLiteLocation itemToSave = new SQLiteLocation ();
itemToSave.LocationName = location.LocationName;
itemToSave.Latitude = location.Latitude;
itemToSave.Longitude = location.Longitude;
itemToSave.PrimaryKey = location.PrimaryKey;
----------------------------------------------------------------------------------------
SQLiteLocation storedLocation = dbConn.Find<SQLiteLocation>
(x => x.PrimaryKey == location.PrimaryKey);
if (storedLocation != null)
{
dbConn.Update(itemToSave);
return true;
}
else if (storedLocation == null)
{
dbConn.Insert(itemToSave);
primaryKey = itemToSave.PrimaryKey;
return true;
}
return false;
}
And here another method see how the code in both methods below my dashed line is basically the same thing
public bool SaveInvitation(IInvitation invitation, ref int primaryKey)
{
var dbConn = new SQLiteConnection(dbPath);
SQLiteInvitation itemToSave = new SQLiteInvitation ();
itemToSave.GroupName = invitation.GroupName;
itemToSave.InviterName = invitation.InviterName;
itemToSave.ParseID = invitation.ParseID;
itemToSave.GroupParseID = invitation.GroupParseID;
itemToSave.PrimaryKey = invitation.PrimaryKey;
---------------------------------------------------------------------------------------
SQLiteInvitation storedInvitation = dbConn.Find<SQLiteInvitation>
(x => x.PrimaryKey == invitation.PrimaryKey);
if (storedInvitation != null)
{
dbConn.Update(itemToSave);
return true;
}
else if (storedInvitation == null)
{
dbConn.Insert(itemToSave);
primaryKey = itemToSave.PrimaryKey;
return true;
}
return false;
}
Shouldn't you be able to do something like this:
Note: ICommonInterface is anything that is common between any allowable classes you would want to use as T. Preferably, looking at your code, an interface or class that exposes the PrimaryKey property.
public bool SaveItem<T>(T item, ref int primaryKey) where T : ICommonInterface, new()
{
var dbConn = new SQLiteConnection(dbPath);
T storedItem = dbConn.Find<T>(x => x.PrimaryKey == item.PrimaryKey);
if (storedItem != null)
{
dbConn.Update(item);
return true;
}
else if (storedItem == null)
{
dbConn.Insert(item);
primaryKey = item.PrimaryKey;
return true;
}
return false;
}
EDIT: Added the new() constraint to the method.
For some reason it threw an not supported exception
when i used :
T storedItem = dbConn.Find<T>(x => x.PrimaryKey == item.PrimaryKey);
The code below fixed my woes though, thanks for the help everyone, I love this site!
Also i added another constraint on T seeing as T has to be a non abstract type and an interface is just that I suppose
private void SaveItem<T>(T item, ref int primaryKey)
where T : ISQLiteClass, new()
{
var dbConn = new SQLiteConnection(dbPath);
T storedItem = dbConn.Find<T>(primaryKey);
if (storedItem != null)
{
dbConn.Update(item);
}
else if (storedItem == null)
{
dbConn.Insert(item);
primaryKey = item.PrimaryKey;
}
}

What is an effective way to validate a bunch of variables before moving on?

I've been reading about the ideal size of methods and the single responsibility principle then I go look at some of my code. I feel I can break up a lot (>90%) of my stuff to be small manageable methods but then I get to validating a data or a form. It always seems really large and bloated. I tend to validate my data with nested if statements and try to catch errors or issues at each level. But when I start to get 6, 8, 10+ levels of validation it is very cumbersome. But I'm not sure how to break it up to be more effective.
An example of something I think is cumbersome but not sure how to improve upon it is below.
Each of the levels has a unique action associated with it and only once all the conditions return true can the whole thing return true but this is tough to read, especially after coming back to the program after a month or so.
if (InitialUsageSettings.zeroed || sender.Equals(btnZero))
{
if (InitialUsageSettings.StandardFilterRun || sender.Equals(btnStandard))
{
if (InitialUsageSettings.ReferenceFilterRun || sender.Equals(btnReference) || sender.Equals(btnStandard))
{
if (InitialUsageSettings.PrecisionTestRun || sender.Equals(btnPrecision) || sender.Equals(btnReference) || sender.Equals(btnStandard))
{
if (txtOperatorID.Text.Length > 0 && cboProject.Text.Length > 0 && cboFilterType.Text.Length > 0 && cboInstType.Text.Length > 0)
{
if (txtFilterID.Text.Length > 0 && txtLot.Text.Length > 0)
{
return true;
}
else
{
if (txtFilterID.Text.Length == 0)
{
//E
}
if (txtLot.Text.Length == 0)
{
//D
}
}
}
else
{
if (txtOperatorID.Text.Length == 0)
{
//A
}
if (cboProject.Text.Length == 0)
{
//B
}
if (cboFilterType.Text.Length == 0)
{
//C
}
if (cboInstType.Text.Length == 0)
{
//D
}
//return false;
}
}
else
{
outputMessages.AppendLine("Please correct the folloring issues before taking a reading: X");
}
}
else
{
outputMessages.AppendLine("Please correct the folloring issues before taking a reading: Y");
}
}
else
{
outputMessages.AppendLine("Please correct the folloring issues before taking a reading: Z");
}
}
else
{
outputMessages.AppendLine("Please correct the folloring issues before taking a reading: A");
}
If your main purpose is to break the methods up into manageable chunks, you could encapsulate each if block in its own method. e.g.:
if (InitialUsageSettings.zeroed || sender.Equals(btnZero))
{
ValidateStandardFilter();
}
else
{
outputMessages.AppendLine("Please correct the folloring issues before taking a reading: A");
}
But it seems to me that this method has too many responsibilities: You're trying to make it validate and also output a message. Instead, the method should be solely responsible for validating.
public ValidationResult Validate(Sender sender)
{
if (!(InitialUsageSettings.zeroed || sender.Equals(btnZero)))
{
return ValidationResult.Error("A");
}
if (!(InitialUsageSettings.StandardFilterRun || sender.Equals(btnStandard)))
{
return ValidationResult.Error("Z");
}
// Etc...
if (txtOperatorID.Text.Length == 0)
{
errors.Add("A");
}
if (cboProject.Text.Length == 0)
{
errors.Add("B");
}
if (cboFilterType.Text.Length == 0)
{
errors.Add("C");
}
if (cboInstType.Text.Length == 0)
{
errors.Add("D");
}
if(errors.Count > 0)
{
return ValidationResult.Errors(errors);
}
if (txtFilterID.Text.Length == 0)
{
errors.Add("E");
}
if (txtLot.Text.Length == 0)
{
errors.Add("D");
}
return errors.Count > 0
? ValidationResult.Errors(errors)
: ValidationResult.Success();
}
And then the calling code can worry about the output:
var result = Validate(sender);
if (result.IsError)
{
outputMessages.AppendLine("Please correct...: " + result.Issue);
}
To get an idea of what the ValidationResult class might look like, see my answer here.
Update
The code above could be further refactored to reduce repetition even more:
public ValidationResult Validate(Sender sender)
{
if (!(InitialUsageSettings.zeroed || sender.Equals(btnZero)))
{
return ValidationResult.Error("A");
}
if (!(InitialUsageSettings.StandardFilterRun || sender.Equals(btnStandard)))
{
return ValidationResult.Error("Z");
}
// Etc...
var firstErrorBatch = GetEmptyStringErrors(
new[]{
new InputCheckPair(txtOperatorID, "A"),
new InputCheckPair(cboProject, "B"),
new InputCheckPair(cboFilterType, "C"),
new InputCheckPair(cboInstType, "D"),
})
.ToList();
if(firstErrorBatch.Count > 0)
{
return ValidationResult.Errors(firstErrorBatch);
}
var secondErrorBatch = GetEmptyStringErrors(
new[]{
new InputCheckPair(txtFilterID, "E"),
new InputCheckPair(txtLot, "D"),
})
.ToList();
return secondErrorBatch.Count > 0
? ValidationResult.Errors(secondErrorBatch)
: ValidationResult.Success();
}
private class InputCheckPair
{
public InputCheckPair(TextBox input, string errorIfEmpty)
{
Input = input;
ErrorIfEmpty = errorIfEmpty;
}
public TextBox Input {get; private set;}
public string ErrorIfEmpty{get; private set;}
}
public IEnumerable<string> GetEmptyStringErrors(IEnumerable<InputCheckPair> pairs)
{
return from p in pairs where p.Input.Text.Length == 0 select p.ErrorIfEmpty;
}
Something akin to
if(errorCondition1)
errors.add(message1);
if(errorCondition2)
errors.add(message2);
return errors.Count == 0;
So each condition is not nested
You can invert your if statements and use Guard Clauses instead. See this example.
Reverse the flow. Instead of
If(cond) {
if(someothercond) {
//great sucess!
return true;
} else {
// handle
return false;
}
} else {
// handle
return false;
}
do:
if(!cond1) {
// handle
return false;
}
if(!someothercond) {
// handle
return false;
}
// great sucess!
return true;
One way is to have a validation method that is called prior to executing your other code.
For example:
private String ValidateThis() {
StringBuilder result = new StringBuilder();
if (!cond1) {
result.AppendLine("error on cond1");
}
if (!cond2) {
result.AppendLine("error on cond2");
}
return result.ToString();
}
public void ButtonClick(object sender) {
String isValid = ValidateThis();
if (!String.IsNullOrEmpty(isValid)) {
// set your error message
outputMessages.AppendLine(isValid);
return;
}
// ... perform your other operations.
}
I would try to have each validation defined as a predicate, something like this...
delegate bool Validator(object sender, out string message);
Then you could string as many of those together as you need.
There are a number of ways to tackle this. You really want to limit the amount of repeated code, such as the code that adds an output message, which is nearly identical in four or more places.
If you think of these nested if…else blocks as a sequence, where as soon as one fails you take action and stop further processing, you can create a list and leverage LINQ's FirstOrDefault functionality to process the list of conditions sequentially until one fails, or you get null if they all pass.
Creating an object to encapsulate the conditions will help consolidate and reduce duplication.
Here is an example:
public class Validator
{
public Validator(string code, bool settingsCheck, Button button, object sender)
{
Code = code;
IsValid = sender != null && button != null && sender.Equals(button);
}
public bool IsValid { get; private set; }
public string Code { get; private set; }
}
Now, your method looks more like this:
var validationChecks = new List<Validator>
{
new Validator("A", InitialUsageSettings.zeroed, btnZero, sender),
new Validator("Z", InitialUsageSettings.StandardFilterRun, btnStandard, sender),
new Validator("Y", InitialUsageSettings.ReferenceFilterRun, btnReference, sender),
new Validator("X", InitialUsageSettings.PrecisionTestRun, btnPrecision, sender)
}
var failure = validationChecks.FirstOrDefault(check => !check.IsValid);
if (failure != null)
{
outputMessages.AppendLineFormat(
"Please correct the following issues before taking a reading: {0}", failure.Code);
return;
}
else
{
// further checks; I'm not sure what you're doing there with A-E
}

Categories