Parsing invalid integers as null within orderby - c#

var list = new string[] { TextBox1.Text, TextBox2.Text, TextBox3.Text };
list = list.OrderBy(x => int.Parse(x)).ToArray();
Can anybody advise how to amend the above code so that a null value is returned for every value that fails to parse as an integer?
I think I need to replace Parse with TryParse somehow?
Clarification:
The program accepts 3 integers from 3 different textboxes, sorts them and inserts the sequence into a database. If a non-integer is entered, I wanted to treat it as a null value.
For example, if TextBox1.Text = "", TextBox2.Text = "45" and TextBox3.Text = "8", the sequence inserted would be: 0,8,45.
However, I now think it might be better to exclude non-integers from the sort so for the same example, the resulting sequence would be something like: 8,45,N/A.
Apologies for not being able to explain my requirements clearly.

If you're really using LINQ to Objects, I'd write a separate method:
public static int? ParseOrNull(string text)
{
int result;
return int.TryParse(text, out result) ? (int?) result : null;
}
Then:
list = list.OrderBy(x => ParseOrNull(x)).ToArray();
This will cope with text values which are either genuine string references to non-numbers, or null references. You might want to overload ParseOrNull to accept an IFormatProvider.
This is just ordering by a nullable int, however. If you want values which invalid replaced with null, but other values to remain as strings (ordered by the numeric value) I suspect you want something more like:
var result = list.Select(x => new { Text = x, Numeric = ParseOrNull(x) })
.OrderBy(pair => pair.Numeric)
.Select(pair => pair.Numeric.HasValue ? pair.Text : null)
.ToArray();
If neither of these does what you want, please clarify your requirements.
Note that none of this will work with something like LINQ to SQL or Entity Framework, which wouldn't be able to translate your method into SQL.

Try this:
var list = new string[] { TextBox1.Text, TextBox2.Text, TextBox3.Text };
list = list.OrderBy(x =>
{
int val;
return int.TryParse(x, out val) ? (int?)val : null;
}).ToArray();

As I understand the requirements and reading your code (which assigns the result to the same array), you still want strings as output, but ordered by their numeric value, and the strings that aren't parseable you want in the resulting array as null;
var result =
list
.Select(x => { int tmp; return Int32.TryParse(x, out tmp) ? x : null; })
.OrderBy(x => x);

Try this:
var list = new string[] { TextBox1.Text, TextBox2.Text, TextBox3.Text };
list = list.OrderBy(x => ParseStringToInt(x)).ToArray();
private int ParseStringToInt(string value)
{
int result;
int.TryParse(value, out result);
return result;
}

Related

Returning value from out modifier to a collection in C#

Let's suppose I receive a collection of strings from user. I need to convert them to GUID sequences for further processing. There is a chance, that user may enter invalid data (not correct GUID sequence), so I need to validate input. Additionally, I can run business-process if only all uploaded data are correct GUID values. Here is my code:
IEnumerable<string> userUploadedValues = /* some logic */;
bool canParseUserInputToGuid = userUploadedValues.All(p => Guid.TryParse(p, out var x));
if(canParseUserInputToGuid)
var parsedUserInput = userUploadedValues.Select(p=> Guid.Parse(p));
This logic works pretty well, but I don't like it as actually I am doing work twice. In second line, Guid.TryParse(p, out var x) already writing parsed GUID sequence to the X variable. Is there an approach to combine validating and mapping logic - if sequence elements satisfy for condition (All) then map this elements to a new collection (Select) in one query? It is important for me also in terms of performance, as it is possible that client will send large amount of data (1, 000, 000+ elements) and doing twice work here is a bit inefficient.
You can do something like this in one Select:
var parsedUserInput = userUploadedValues.Select(p => Guid.TryParse(p, out var x) ? x : default)
.Where(p => p != default);
For this one, you need to be sure if there is no Guid.Empty input from the user.
Otherwise, you can return a nullable Guid if parsing doesn't succeed:
var parsedUserInput = userUploadedValues.Select(p => Guid.TryParse(p, out var x) ? x : default(Guid?))
.Where(p => p != null);
Another solution by creating an extension method, for example:
public static class MyExtensions
{
public static Guid? ToGuid(this string arg)
{
Guid? result = null;
if(Guid.TryParse(arg, out Guid guid))
result = guid;
return result;
}
}
and usage:
var parsedUserInput2 = userUploadedValues.Select(p => p.ToGuid())
.Where(p => p != null);
But keep in mind that in this cases, you will have a collection of nullable Guids.
Your out var x variable will be Guid.Empty in the case where it is not a valid Guid. So you can just do this:
IEnumerable<string> userUploadedValues = new[]
{
"guids.."
};
var maybeGuids = userUploadedValues.Select( x => {
Guid.TryParse( x, out var #guid );
return #guid;
} );
if ( maybeGuids.All( x => x != Guid.Empty ) )
{
//all the maybe guids are guids
}
You can optimize the validation and conversion like below,
IEnumerable<string> userUploadedValues = /* some logic */;
var parsedGuids = userUploadedValues.Where(p => Guid.TryParse(p, out var x));
if(userUploadedValues.Count() != parsedGuids.Count())
{
//Some conversion failed,
}
If the count of both the lists same, then you have all the converted GUIDs in the parsedGuids.
Sometimes the non-LINQ method is just easier to read and no longer.
var parsedUserInput = new List<string>();
foreach(var value in userUploadedValues)
{
if (Guid.TryParse(value, out var x)) parsedUserInput.Add(x);
else...
}

Enumerable.Concat not working

Below is the code:
string[] values = Acode.Split(',');
IEnumerable<Test> tst = null;
foreach (string a in values)
{
if (tst== null)
tst = entities.Test.Where(t=> (t.TCode == Convert.ToInt16(a)));
else
tst.Concat(entities.Test.Where(g => (g.TCode == Convert.ToInt16(a))));
}
return tst.ToList();
I am not able to get all the records in tst, it is giving me records only for the last value in array.
So if my array contains 1,2,3,4 I am getting records only for the 4. Whereas i need all the result for 1,2,3 and 4 get appended in tst.
Any help will be appreciated.
Concat doesn't modify anything - it returns a new sequence, which you're currently ignoring.
However, rather than using Concat, you should just use SelectMany to flatten the sequence:
string[] values = Acode.Split(',');
return values.SelectMany(a => entities.Test.Where(t => t.TCode == Convert.ToInt16(a)))
.ToList();
Or more efficiently, convert values into a List<short> and then you can do one query:
List<short> values = Acode.Split(',').Select(x => short.Parse(x)).ToList();
return entities.Test.Where(t => values.Contains(t.TCode)).ToList();
That is because Concat will return a new instance of your enumerable.
Either use in your else :
tst = tst.Concat(...)
Or Change your Enumerable into list from the beginning :
string[] values = Acode.Split(',');
List<Test> tst= new List<Test>;
foreach (string a in values)
{
tst.AddRange(entities.Test.Where(g => (g.TCode == Convert.ToInt16(a))));
}
return tst;

Return list with numeric keys from C#

My code is as follows:
var aaData =
ctx.PaymentRates
.Where(x => x.ServiceRateCodeId == new Guid("BBCE42CB-56E3-4848-B396-4656CCE3CE96"))
.Select(x => new
{
Id = x.Id
}).ToList();
It generates the following JSON when converted using Json(aaData);:
"aaData":[
{"Id":"ab57fc9d-ffb7-4a12-8c5c-03f36b4ef1fe"},
{"Id":"4c1e9776-5d64-4054-a9c9-0fc8b8b8e8a1"}
etc.
]
However, I would like to return keys before the values, like so:
"aaData":[
[0] => {"Id":"ab57fc9d-ffb7-4a12-8c5c-03f36b4ef1fe"},
[1] => {"Id":"4c1e9776-5d64-4054-a9c9-0fc8b8b8e8a1"}
etc.
]
Edit: I am not sure of the proper syntax - the point is, I just want numeric keys.
How can I do this in C#?
JSON uses implicit indexing - meaning the order of elements in an array is preserved, so you can infer the "index" from the order of elements when you retrieve them.
If you need to include an "index" element, you'll have to make it an attribute of the array element:
var aaData =
ctx.PaymentRates
.Where(x => x.ServiceRateCodeId == new Guid("BBCE42CB-56E3-4848-B396-4656CCE3CE96"))
.Select((x, i) => new
{
Index = i,
Id = x.Id
}).ToList();
Your JSON should look something like:
"aaData":[
{"Index":"0", "Id":"ab57fc9d-ffb7-4a12-8c5c-03f36b4ef1fe"},
{"Index":"1", "Id":"4c1e9776-5d64-4054-a9c9-0fc8b8b8e8a1"}
etc.
]
It is not a matter of whether it is possible in C# or not, it is more a matter of the fact that JSON has a certain format and the format you would like to achieve is not compliant with JSON.
So in case you would like to achieve a result in such a format you would have to build your own format (but then you would also have to create your own serializers, deserializers, etc - in general quite a lot of hassle).
Do you mean the index? Here's one way you can get it
var counter = 0;
var aaData =
ctx.PaymentRates
.Where(x => x.ServiceRateCodeId == new Guid("BBCE42CB-56E3-4848-B396-4656CCE3CE96"))
.Select(x => new
{
Index = counter++,
Id = x.Id
}).ToList();
In order to do what you wish you would have to use a Dictionary instead of a List. Doing:
int idx = 0;
var dict = aaData.ToDictionary(k => "" + idx++);
will return provide you with the following valid JSON
"aaData":{
"0" : {"Id":"ab57fc9d-ffb7-4a12-8c5c-03f36b4ef1fe"},
"1" : {"Id":"4c1e9776-5d64-4054-a9c9-0fc8b8b8e8a1"}
etc.
}
note the fact that the keys are strings that contain numeric values. Numeric values are not supported as keys.

How to omit a value in a select lambda?

I want to make a simple CSV parser. It should go through a list of comma separated values and put them in a IList<int>. The values are expected to be integer numbers. In case a value is not parseable, I just want to omit it.
This is the code I have so far:
csv.Split(',').Select(item =>
{
int parsed;
if (int.TryParse(item, out parsed))
{
return parsed;
}
continue; //is not allowed here
}).ToList();
However, the use of continue is (of course) not allowed here. How to omit a value in my select implementation?
Note: Of course could I use a foreach or a LINQ expression, but I wonder how to do it with a lambda.
How about:
public static IEnumerable<int> ExtractInt32(this IEnumerable<string> values) {
foreach(var s in values) {
int i;
if(int.TryParse(s, out i)) yield return i;
}
}
then:
var vals = csv.Split(',').ExtractInt32().ToList();
The nice things here:
avoids magic "sentinal" numbers (like int.MinValue)
avoids a separate and disconnected "it is valid" / "parse" step (so no duplication)
Select transforms a value. It doesn't filter. Where is doing that:
csv.Split(',')
.Select(item =>
{
int parsed;
return new { IsNumber = int.TryParse(item, out parsed),
Value = parsed };
})
.Where(x => x.IsNumber)
.Select(x => x.Value);
Additionally, please see this answer for a clever, short way of doing it. Please note that the meaning of "clever" isn't entirely positive here.
One way is to return some default value and then skip it.
errorInt = int.MinValue;
csv.Split(',').Select(item =>
{
int parsed;
if (int.TryParse(item, out parsed))
{
return parsed;
}
else
{
return errorInt;
}
}).Where(val => val != errorInt).ToList();
I think you have three options:
Use SelectMany instead which will allow you to return as empty enumerable for elements you wish to omit (and an enumerable of length 1 otherwise).
Use an int value you are sure won't be in the set (e.g. -1) to represent 'omitted' and filter them out afterwards. This approach is fragile as you may pick a value that subsequently appears in the set which will result in a subtle bug. (You could mitigate this by using a larger data type, e.g. long and picking a value outside the range of int but then you will need to convert back to int subsequently.)
Use Nullable<int> (int?) instead and filter out the null values afterwards.
1:
csv.Split(',').SelectMany(item =>
{
int parsed;
if (int.TryParse(item, out parsed))
{
return new[] {parsed};
}
return Enumerable.Empty<int>();
}
3:
csv.Split(',').Select(item =>
{
int parsed;
if (int.TryParse(item, out parsed))
{
return (int?) parsed;
}
return (int?) null;
}
.Where(item => item.HasValue)
.Select(item => item.Value);
try this:
int dummy;
sv.Split(',').Where(c => int.TryParse(c,out dummy)).Select(c => int.Parse(c));
The int.TryParse(..) just checks if it is a valid string to be translated into an int. The out parameter is just ignored - we cont need it.
We know that only those string values that "makes-it" to the Select() are values that can be safetly parsed as int's.
Why not to use Where on array and only then select proper ints
csv.Split(',')
.Where(item =>
{
int parsed;
return int.TryParse(item, out parsed);
})
.Select(item => Convert.ToInt32(item));
I would probably just use:
csv.Split(',').Where(item => isValid(item)).Select(item => TransformationExpression(item));
or,
csv.Split(',').Select(item => ReturnsDummyValueIfInvalid(item)).Where(item => item != DummyValue);
int TempInt;
List<int> StuffIWant = csv.Split(',').Where(item => int.TryParse(item, TempInt)).ToList();

how to filter with linq and `contains`

I try to filter my items according to unknown number of filters.
//item.statusId is nullable int
//statusIds is a string
{...
var statusIds = Convert.ToString(items["StatusId"]);//.Split(';');
results = mMaMDBEntities.MamConfigurations.Where(item =>
FilterByStatusId(statusIds, item.StatusId)).ToList();
}
return results;
}
private bool FilterByStatusId(string statusIds, int? statusId)
{
return statusIds.Contains(statusId.ToString());
}
But I get this error:
LINQ to Entities does not recognize the method 'Boolean FilterByStatusId(System.String, System.Nullable1[System.Int32])' method, and this method cannot be translated into a store expression.`
Any idea how to re-write it?
If statusIds is an array then you can do:
results = mMaMDBEntities.MamConfigurations
.Where(item => statusIds.Contain(item.StatusID)).ToList();
Some what similar to SQL Select * from table where ID in (1,2,3)
EDIT:
From your code it appears you have a string with semicolon separated values. You can try the following to get an array of int and later use that in your LINQ expression.
var str = Convert.ToString(items["StatusId"]);//.Split(';');
// string str = "1;2;3;4;5"; //similar to this.
int temp;
int[] statusIds = str.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries)
.Select(r => int.TryParse(r, out temp) ? temp : 0)
.ToArray();
then later you can use the int array in your expression like:
results = mMaMDBEntities.MamConfigurations
.Where(item => statusIds.Contain(item.StatusID)).ToList();
Why not insert the predicate statement directly into the where clause?
like this:
results = mMaMDBEntities.MamConfigurations.Where(item => statusIds.Contains(item.StatusId).ToList();
you might need to convert the string array resulting from the Split to a List or IEnumerable to make this work.
The exception is pretty self-explanatory, the method cannot be converted to a SQL statement in the form you wrote it, but if you write it like above, you should obtain the same result and it will work.

Categories

Resources