Problem calling string manipulation method within linq-to-sql query - c#

I'm having a frustrating issue trying to use LINQ to call a string manipulation method. I've done lots of searching now and have tried various method to get the line noted as 'FAILS' below to work. It currently throws an exception.
Some things I've tried:
a) Initially the creation of the concatenated key was in the same query, didn't change anything
b) Converting the non-string fields to strings (another whole can of works with .ToString not working in linq. String.Concat and String.Format were tried, work ok in some cases but not when you try to refer to that value later on)
c) Using the concat etc instead of the '+' to join the things together.
As you can see it seems fairly tolerant of appending strings to non-strings, but not when that method is invoked.
There are lots of rows so I'd prefer not to convert the data to a list/array etc, but if that's the only option then any suggestions appreciated.
Many thanks! - Mark
var vouchers = from v in db.Vouchers
select new
{
v.Amount,
v.Due_Date,
v.Invoice_Date,
v.PO_CC,
v.Vendor_No_,
v.Invoice_No_,
invoiceNumeric = MFUtil.StripNonNumeric(v.Invoice_No_)
};
var keyedvouchers = from vv in vouchers
select new
{
thekey = vv.Vendor_No_ + "Test", // works with normal string
thekey2 = vv.Amount + "Test", // works with decimal
thekey3 = vv.Invoice_Date + "Test", // works with date
thekey4 = vv.invoiceNumeric, // works
thekey5 = vv.invoiceNumeric + "Test" // FAILS
};
-- The method to strip chars ---
public static string StripNonNumeric(string str)
{
StringBuilder sb = new StringBuilder();
foreach (char c in str)
{
// only append if its withing the acceptable boundaries
// strip special chars: if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') | || (c >= 'a' && c <= 'z') | c == '.' || c == '_')
// strip any nonnumeric chars
if (c >= '0' && c <= '9')
{
sb.Append(c);
}
}
return sb.ToString();
}
-- The Exception Message--
System.InvalidOperationException was unhandled by user code
Message=Could not translate expression 'Table(Voucher).Select(v => new <>f__AnonymousType07(Amount = v.Amount, Due_Date = v.Due_Date, Invoice_Date = v.Invoice_Date, PO_CC = v.PO_CC, Vendor_No_ = v.Vendor_No_, Invoice_No_ = v.Invoice_No_, invoiceNumeric = StripNonNumeric(v.Invoice_No_))).Select(vv => new <>f__AnonymousType15(thekey = (vv.Vendor_No_ + "Test"), thekey2 = (Convert(vv.Amount) + "Test"), thekey3 = (Convert(vv.Invoice_Date) + "Test"), thekey4 = vv.invoiceNumeric, thekey5 = (vv.invoiceNumeric + "Test")))' into SQL and could not treat it as a local expression.

It's because it tries to build an SQL query of the expression and the MFUtil.StripNonNumeric cannot be translated into SQL.
Try returning it first and then convert the reult into a list and then use a second query to convert it.
var vouchers_temp = from v in db.Vouchers
select new
{
v.Amount,
v.Due_Date,
v.Invoice_Date,
v.PO_CC,
v.Vendor_No_,
v.Invoice_No_
};
var vouchers = vouchers_temp.ToList().Select( new {
Amount,
Due_Date,
Invoice_Date,
PO_CC,
Vendor_No_,
Invoice_No_,
invoiceNumeric = MFUtil.StripNonNumeric(Invoice_No_)
});

It FAILS to work, because it is not suppose to work.
Create a SQL-side function and call that in the query.

Related

Dynamic library (System.Linq.Dynamic) SQL LIKE Operator

Someone has posted a similar question here How Dynamic library (System.Linq.Dynamic) support LIKE Operator?
But it's not exactly what I want. The Contains operator mentioned in that post only do this in SQL "%SOMETHING%". But the LIKE operator in SQL can do this "SOME%THING". Is there a similar operator for Dynamic LINQ? If not, is there a solution for this? Maybe with Regex? Also is there a single character wildcard? E.g. "SOM$THING" returns "SOMETHING" or "SOM3THING"
Edit: I am developing a WPF application which should read log files in XML format. Each element contains 34 fields. So instead of writing a very long WHERE, I have used System.Reflection.PropertyInfo to iterate each field to write the query and then use System.Linq.Dynamic to execute it.
Edit2: I have edited the code so it's more readable for the viewers.
Here is some code:
Example 1:
prop.Name = "FirstName", paramterString = "Ke%in", returns "Kevin",
"Kelvin"...
Example 2:
prop.Name = "FirstName", paramterString = "Ke$in", returns "Kevin",
"Kelin"...
var query = "";
StringBuilder sb = new StringBuilder();
foreach (var prop in stringProps)
{
sb.Append($"({prop.Name} != null And {prop.Name}.Contains({parameterString})");
}
query = sb.ToString().Substring(0, sb.Length - 4);
filteredData = filteredData.Where(query);
One of the requirement is to implement a SQL LIKE operator, so the users can use something like this to get the result they want: FirstName LIKE 'SOME%THING'
Since there is no LIKE operator in Dynamic Linq library, I have created it my own version. The code is not pretty, but it does work. Hopefully someone can optimize it or suggest a better way to do this.
public string ParseWildcardInParameterString(string parameter, string propertyName)
{
string stringWithWildcard = parameter;
if (parameter.Contains("%") || parameter.Contains("$"))
{
stringWithWildcard = parameter;
string[] substrings = parameter.Split(new Char[] { '%', '$' }, StringSplitOptions.RemoveEmptyEntries);
string[] wildcards = ParseWildcards(parameter);
if (substrings.Any())
{
StringBuilder sb = new StringBuilder();
int substringsCount = substrings.Length;
for (int i = 0; i < substringsCount; i++)
{
if (!substrings[i].EndsWith("\\"))
{
int index = parameter.IndexOf(substrings[i]);
if (i < substringsCount - 1)
{
index = parameter.IndexOf(substrings[i + 1], index + 1);
if (index > -1)
{
string secondPart = wildcards[i].Equals("%") ?
$"{propertyName}.IndexOf(\"{substrings[i + 1]}\", {propertyName}.IndexOf(\"{substrings[i]}\") + \"{substrings[i]}\".Length) > -1" :
$"{propertyName}.IndexOf(\"{substrings[i + 1]}\", {propertyName}.IndexOf(\"{substrings[i]}\") + \"{substrings[i]}\".Length + 1) == {propertyName}.IndexOf(\"{substrings[i]}\") + \"{substrings[i]}\".Length + 1";
sb.Append($"({propertyName}.IndexOf(\"{substrings[i]}\") > -1 And {secondPart}) And ");
}
}
}
}
stringWithWildcard = sb.Remove(sb.Length - 5, 5).Append(") Or ").ToString();
}
}
return stringWithWildcard;
}
private string[] ParseWildcards(string parameter)
{
IList<string> wildcards = new List<string>();
foreach (var chararcter in parameter.ToCharArray())
{
if (chararcter.Equals('%') || chararcter.Equals('$'))
{
wildcards.Add(chararcter.ToString());
}
}
return wildcards.ToArray();
}
Can you try if the Like functionality in System.Linq.Dynamic.Core does work for you?
Code example would be:
var dynamicFunctionsLike1 = context.Cars.Where(config, "DynamicFunctions.Like(Brand, \"%a%\")");
For full example, see ConsoleAppEF2.1.1/Program.cs

How to add characters to a string in C#

Problem: I would like add characters to a phone.
So instead of displaying ###-###-####, I would like to display (###) ###-####.
I tried the following:
string x = "Phone_Number";
string y = x.Remove(0,2);//removes the "1-"
From here, I am not sure how I would add "()" around ###
Any help would be appreciated.
It's worth noting that strings are immutable in C#.. meaning that if you attempt to modify one you'll always be given a new string object.
One route would be to convert to a number (as a sanity check) then format the string
var result = String.Format("{0:(###) ###-####}", double.Parse("8005551234"))
If you'd rather not do the double-conversion then you could do something like this:
var result = String.Format("({0}) {1}-{2}", x.Substring(0 , 3), x.Substring(3, 3), x.Substring(6));
Or, if you already have the hyphen in place and really just want to jam in the parenthesis then you can do something like this:
var result = x.Insert(3, ")").Insert(0, "(");
To insert string in particular position you can use Insert function.
Here is an example:
string phone = "111-222-8765";
phone = phone.Insert(0, "("); // (111-222-8765
phone = phone.Insert(3, ")"); // (111)-222-8765
You can use a regular expression to extract the digit groups (regardless of - or () and then output in your desired format:
var digitGroups = Regex.Matches(x, #"(\d{3})-?(\d{3})-?(\d{4})")[0].Groups.Cast<Group>().Skip(1).Select(g => g.Value).ToArray();
var ans = $"({digitGroups[0]}) {digitGroups[1]}-{digitGroups[2]}";
I would do something like this:
string FormatPhoneNumber(string phoneNumber)
{
if (string.IsNullOrEmpty(phoneNumber))
throw new ArgumentNullException(nameof(phoneNumber));
var phoneParts = phoneNumber.Split('-');
if (phoneParts.Length < 3)
throw new ArgumentException("Something wrong with the input number format", nameof(phoneNumber));
var firstChar = phoneParts[0].First();
var lastChar = phoneParts[0].Last();
if (firstChar == '(' && lastChar == ')')
return phoneNumber;
else if (firstChar == '(')
return $"{phoneParts[0]})-{phoneParts[1]}-{phoneParts[2]}";
else if (lastChar == ')')
return $"({phoneParts[0]}-{phoneParts[1]}-{phoneParts[2]}";
return $"({phoneParts[0]})-{phoneParts[1]}-{phoneParts[2]}";
}
You would use it like this:
string n = "123-123-1234";
var formattedPhoneNumber = FormatPhoneNumber(n);

Linq Expression run func in query

I have simple Query:
var ipLookup = _unit.Repository<IpLookup>().Queryable().FirstOrDefault(i =>
ToInteger(i.From) <= intIp && ToInteger(i.To) >= intIp);
And I want to run this method:
public static long ToInteger(string ip)
{
var arr = ip.Split('.').ToList();
var a = Convert.ToInt32(arr[0]);
var b = Convert.ToInt32(arr[1]);
var c = Convert.ToInt32(arr[2]);
var d = Convert.ToInt32(arr[3]);
return Convert.ToInt64((a * Math.Pow(256, 3)) + (b * Math.Pow(256, 2)) + (c * 256) + d);
}
I am not sure how to write expression for this to work with linq inside of query.
When Linq-to-sql generate query to a DB it converts expressions to sql commands. It can not convert whatever code. In your case I'll recomend wright a Scalar-Valued User-Defined function with same body at .net and sql (for same behaviour). For more details see MSDN

how can I convert this foreach loop to linq?

I'm very new to Linq. I want to convert these lines of code to linq lambda expression, If it makes sense how can I achieve?
foreach (var Type in Essay.Text)
{
string text =
$"{"here is result"}\n{method(Type)}";
if (text.Length <= 20 && !string.IsNullOrEmpty(method(Type)))
{
Essay.Total += text;
}
}
With help of Resharper:
Essay.Total = string.Concat(
Essay.Text.Select(Type => new {Type, text = $"{"here is result"}\n{method(Type)}"})
.Where(#t => #t.text.Length <= 20 && !string.IsNullOrEmpty(method(#t.Type)))
.Select(#t => #t.text)
);
Something like this:
foreach (var type in from type in Essay.Text
let text = $"{"here is result"}\n{method(Type)}"
where text.Length <= 20 && !string.IsNullOrEmpty(method(Type)) select type)
{
Essay.Total += type;
}
A few pointers:
There is no reason to call method twice, just cache it by using let
You can also check the length of the text before constructing the final string
This should do the job for you:
var texts = from type in essay.Text
let methodResult = method(type)
where !string.IsNullOrEmpty(methodResult)
let text = $"here is result\n{methodResult}"
where text.Length <= 20
select text;
essay.Total += string.Concat(texts);

Find a fixed length string with specific string part in C#

I want to find a string of fixed length with specific substring. But I need to do it like we can do in SQL queries.
Example:
I have strings like -
AB012345
AB12345
AB123456
AB1234567
AB98765
AB987654
I want to select strings that have AB at first and 6 characters afterwards. Which can be done in SQL by SELECT * FROM [table_name] WHERE [column_name] LIKE 'AB______' (6 underscores after AB).
So the result will be:
AB012345
AB123456
AB987654
I need to know if there is any way to select strings in such way with C#, by using AB______.
You can use Regular Expressions to filter the result:
List<string> sList = new List<string>(){"AB012345",
"AB12345",
"AB123456",
"AB1234567",
"AB98765",
"AB987654"};
var qry = sList.Where(s=>Regex.Match(s, #"^AB\d{6}$").Success);
Considering you have an string array:
string[] str = new string[3]{"AB012345", "A12345", "AB98765"};
var result = str.Where(x => x.StartsWith("AB") && x.Length == 8).ToList();
The logic is if it starts with AB, and its length is 8. It is your best match.
this should do it
List<string> sList = new List<string>(){
"AB012345",
"AB12345",
"AB123456",
"AB1234567",
"AB98765",
"AB987654"};
List<string> sREsult = sList.Where(x => x.Length == 8 && x.StartsWith("AB")).ToList();
first x.Length == 8 determines the length and x.StartsWith("AB") determines the required characters at the start of the string
This can be achieved by using string.Startwith and string.Length function like this:
public bool CheckStringValid (String input)
{
if (input.StartWith ("AB") && input.Length == 8)
{
return true;
}
else
{
return false;
}
}
This will return true if string matches your criteria.
Hope this helps.
var strlist = new List<string>()
{
"AB012345",
"AB12345",
"AB123456",
"AB1234567",
"AB98765",
"AB987654"
};
var result = strlist.Where(
s => (s.StartsWith("AB") &&(s.Length == 8))
);
foreach(var v in result)
{
Console.WriteLine(v.ToString());
}

Categories