Distinct operator on List<string> - c#

I'm trying to get distinct string values out of an Ax repository, but I'm getting a lot of identical strings out (strings only contains numbers)
var ret = context.XInventTransBackOrder
.Where(i => i.BatchRouteId != "")
.Select(i => i.BatchRouteId)
.Distinct()
.ToList();
Where am I going wrong?

Have you tried
var ret = context.XInventTransBackOrder
.Where(i => i.BatchRouteId != "")
.Select(i => i.BatchRouteId)
.ToList();
ret = ret
.Distinct()
.ToList();

If the BatchRouteId was a XElement, for instance, then probably an object reference comparison would be performed. In that case change the code to
var ret = context.XInventTransBackOrder
.Where(i => i.BatchRouteId != null && !String.IsNullOrEmpty(i.BatchRouteId.Value))
.Select(i => i.BatchRouteId.Value)
.Distinct()
.ToList();
UPDATE #1
Note that some types implement implicit conversions making you think they were another type. You can pass a string to a XName parameter without explicit casting, and the string will automatically be converted to XName.
UPDATE #2
According to a comment of nk2003dec the context is LinqToDynamicsAx. I don't know this interface but probably it does not implement Distinct. What you can to in such a case, is to change the context form a XY-LINQ to Object-LINQ by using the System.Linq.Enumerable.AsEnumerable<TSource> extension method
var ret = context.XInventTransBackOrder
.Select(i => i.BatchRouteId)
.Where(id => id != "")
.AsEnumerable()
.Distinct()
.ToList();
I also inverted Select and Where as this simplifies the access to BatchRouteId

X++ does not have a distinct operator. The deferred execution will try to execute on ToList() and will fail because of this.

Related

EntityFramework: OrderBy() numeric property when mapped column type is textual

return JsonConvert.SerializeObject(new Entities()
.Student_Master
.Where(k => k.Student_Location == Location && k.Student_Course == Program)
.OrderBy(i => i.Student_Batch)
.Select(i => i.Student_Batch)
.Distinct()
.ToList());
Output:
[23,24,28,25,30,26,27,29]
require Output
[23,24,25,26,27,28,29,30]
I tried with OrderBy(i => i.Student_Batch) but in database Student_Batch datatype is string so not sorting correctly
I tried like following
var data=new Entities().Student_Master.Where(k => k.Student_Location == Location && k.Student_Course == Program).OrderBy(i => i.Student_Batch).Select(i => i.Student_Batch).Distinct().ToList();
foreach(var obj in data)
{
//converted string to int then store in array
}
Is there any easy way?
Okay so since the problem is with sorting. You have few options and i will show 2 of them. First is that you can use Array.Sort() which is pretty common:
string[] values = new Entities()
.Student_Master
.Where(k => k.Student_Location == Location && k.Student_Course == Program).Select(i => i.Student_Batch)
.Distinct().ToArray();
Array.Sort(values); // all you need.
Second common way is to create custom comparer and use it inside OrderBy :
public class MeComparer : IComparer<string> {
public int Compare(string stringA, string stringB) {
// your compare logic goes here...
// eg. return int.Parse(stringA) - int.Parse(stringB)
}
}
// and use it like
return JsonConvert.SerializeObject(new Entities()
.Student_Master
.Where(k => k.Student_Location == Location && k.Student_Course == Program)
.Select(i => i.Student_Batch)
.Distinct()
.ToList()
.OrderBy(i => i.Student_Batch, new MeComparer()) // <-- HERE
);
.Distinct() removes any .OrderBy() clause, because by definition, Distinct() (or DISTINCT in SQL) returns an un-ordered set of distinct values. You need to chain your .OrderBy() call after the .Distinct() call.
Having your values as strings does pose a problem when you want to sort them by their numeric value. If you can't change the database schema, you can use this method to project the values to integers, and then do .Distinct() and .OrderBy().
Finally, you should properly dispose your Entities object after you use it, to close the database connection, preferably by enclosing it in a using directive.
As Linq2Entities does not support conversion from string to int (neither using int.Parse not Convert.ToInt32) you have to convert your IQueryAble to IEnumerable using AsEnumerable. Of course this is performance-wise nightmare, however as SQL has no way on making integer-operations on strings by on-the-fly-conversion this is what you need.
Btw.: Using ToArrayor ToList will also enumerate the collection and put it into the memory.

LINQ return type/value is not what im awaiting

When i execuse these lines
drpdf["meno"] = matches.Cast<Match>().Where(c => c.Groups["ID"].Value == i.ToString()).Select(c => c.Groups["meno"].Value);
drpdf["info"] = matches.Cast<Match>().Where(c => c.Groups["ID"].Value == i.ToString()).Select(c => Regex.Replace(c.Groups["zvysok"].Value, #"^,\s?", string.Empty));
it wont save into DataRow value that i want, instead of
System.Linq.Enumerable+WhereSelectEnumerableIterator`2[System.Text.RegularExpressions.Match,System.String]
Can you help me pls how to select/cast return value to the readable type? Thanks anyway. Ondro
Your LINQ queries use Select, so you get an IEnumerable<T> back. If you want the result of your LINQ query, and are expecting exactly one result, add .Single():
drpdf["meno"] = matches.Cast<Match>()
.Where(c => c.Groups["ID"].Value == i.ToString())
.Select(c => c.Groups["meno"].Value)
.Single();
On the other hand, if your query can have multiple results, you should use .First() instead to take the first result. At that point, however, it depends what your scenario is and what you're trying to capture.
Something like:
matches.Cast<Match>()
.Where(c => c.Groups["ID"].Value == i.ToString())
.Select(c => c.Groups["meno"].Value)
.FirstOrDefault(); // this expression will evaluate the linq
// expression, so you get the string you want
Please note: You should only use FirstOrDefault or SingleOrDefault if null is actually a valid value in your context. (like said by #Daniel Hilgarth).
If null is not a valid result and instead you want an empty string, append a ?? String.Empty to the expression:
matches
...
.FirstOrDefault() ?? String.Empty;
The result of your queries are enumerable objects. Calling ToString() on these doesn't give you a meaningful string representation as you have already noticed. You need to generate a string appropriate for display.
If you simply want to display the contents as a comma-separated list, you can use String.Join() to do this:
var menos = matches.Cast<Match>()
.Where(c => c.Groups["ID"].Value == i.ToString())
.Select(c => c.Groups["meno"].Value);
drpdf["meno"] = String.Join(", ", menos);
Otherwise if you intended to select a single result, use Single() to select that single string result.

Sorting a list of strings by placing words starting with a certain letter at the start

Assuming I have the following list:
IList<string> list = new List<string>();
list.Add("Mouse");
list.Add("Dinner");
list.Add("House");
list.Add("Out");
list.Add("Phone");
list.Add("Hat");
list.Add("Ounce");
Using LINQ how would I select the words containing "ou" and sort the selection such that the words beginning with "ou" are listed at the start and then the words containing but not starting with "ou" are subsequently listed. The list I'm trying to create would be:
Ounce
Out
House
Mouse
I came up with the following but it is not working:
list.Where(x => x.Contains("ou"))
.OrderBy(x => x.StartsWith("ou"))
.Select(x => x);
You're getting a case-sensitive comparison, and also you need OrderByDescending(). A quick and dirty way to achieve the case-insensitivity is ToLowerInvariant():
var result = list.Where(x => x.ToLowerInvariant().Contains("ou"))
.OrderByDescending(x => x.ToLowerInvariant().StartsWith("ou"))
.Select(x => x);
Live example: http://rextester.com/GUR97180
This previous answer shows the correct way to do a case insensitive comparison (ie, dont use my example above, its bad)
Your first mistake is not comparing strings in a case-insensitive way; "Out" and "Ounce" have capital Os and would not return "true" when you use Contains("ou"). The solution is to use ToLower() when checking letters.
list.Where(x => x.ToLower().Contains("ou"))
.OrderByDescending(x => x.ToLower.StartsWith("ou")) //true is greater than false.
.Select(x => x);
Three problems:
You need to assign the result to something, otherwise it is simply discarded.
You need to use OrderByDescending because true sorts after false if you use OrderBy.
You need to use a case-insensitive compare.
Try this:
var needle = "ou";
var stringComparison = StringComparison.OrdinalIgnoreCase;
var query =
from word in list
let index = word.IndexOf(needle, stringComparison)
where index != -1
orderby index
select word;
This will append an empty space to the beginning of words that start with "OU".
var result = list.Where(x => x.ToLowerInvariant().Contains("ou"))
.OrderBy(x => x.ToLowerInvariant()
.StartsWith("ou") ? " " + x : x.Trim());
list = list.Where(x => x.ToLower().Contains("ou"))
.OrderBy(x => !x.ToLower().StartsWith("ou")).ToList();
Or by using the methods of List (changing it from IList to List):
list.RemoveAll(x => !x.ToLower().Contains("ou"));
list.Sort((s1, s2) => -1 * 1.ToLower().StartsWith("ou")
.CompareTo(s2.ToLower().StartsWith("ou")));
I think this is what you're looking for:
list = list.Where(x => x.IndexOf("ou", StringComparison.OrdinalIgnoreCase) >= 0)
.OrderByDescending(x => x.StartsWith("ou", StringComparison.OrdinalIgnoreCase))
.ThenBy(x => x)
.ToList();
Note that instead of converting the strings ToLower (or upper), I use a StringComparison enum (currently OrdinalIgnoreCase). This ensures that it works consistently as expected in any culture. Choose the right case-insensitive comparison depending on your circumstance.
If you prefer the LINQ query syntax that's:
list = (from x in list
where x.IndexOf("ou", StringComparison.OrdinalIgnoreCase) >= 0
orderby x.StartsWith("ou", StringComparison.OrdinalIgnoreCase) descending, x
select x).ToList();
var bla = "ou";
var list = new List<string>{
"Mouse",
"Dinner",
"House",
"Out",
"Phone",
"Hat",
"Ounce"};
var groupa = list.GroupBy(x =>x.ToLower().Contains(bla));
groupa.First().ToList().OrderByDescending(x => x.ToLower().StartsWith(bla));
You can simply call the list.Sort method by passing in an instance of a custom comparer as follows:
public class MyCustomStringComparer: IComparer<string>
{
public int Compare(Entity x, Entity y)
{
int result = 0;
if (x.ToLower().StartsWith("ou") && y.ToLower().StartsWith("ou"))
result = x.Compare(y);
else if (x.ToLower().StartsWith("ou") && !y.ToLower().StartsWith("ou"))
result = -1;
else if (!x.ToLower().StartsWith("ou") && y.ToLower().StartsWith("ou"))
result = 1;
else
result = x.Compare(y);
return (result);
}
}

LINQ equivalent of List<T>.Find()?

I'm looking at some code that takes an IEnumerable<T> and converts it to a List<T> so it can use List<T>.Find(predicate):
var myEnumerable = ...;
var myList = new List<T>(myEnumerable);
var match = myList.Find(value => value.Aaa == aaa && value.Bbb == bbb);
Is there a way to rewrite this using the LINQ extension methods that has the same effect, but without building an extra List<T> as an intermediate step?
The FirstOrDefault(source, predicate) extension method looks like a good candidate, but trying to figure out if it's exactly equivalent to Find is making my head hurt.
Just for reference, here is a table of some old .NET 2 style List<> instance methods, and their equivalent extension methods in Linq:
METHOD IN List<> METHOD IN Linq
------------------------------------------------------------------------------------------
list.Contains(item) query.Contains(item)
list.Exists(x => x.IsInteresting()) query.Any(x => x.IsInteresting())
list.TrueForAll(x => x.IsInteresting()) query.All(x => x.IsInteresting())
list.Find(x => x.IsInteresting()) query.FirstOrDefault(x => x.IsInteresting())
list.FindLast(x => x.IsInteresting()) query.LastOrDefault(x => x.IsInteresting())
list.FindAll(x => x.IsInteresting()) query.Where(x => x.IsInteresting())
list.ConvertAll(x => x.ProjectToSomething()) query.Select(x => x.ProjectToSomething())
Of course some of them are not entirely equivalent. In particular Linq's Where and Select use deferred execution, while FindAll and ConvertAll of List<> will execute immediately and return a reference to a new List<> instance.
FindLast will often be faster than LastOrDefault because FindLast actually searches starting from the end of the List<>. On the other hand LastOrDefault(predicate) always runs through the entire sequence (starting from the first item), and only then returns the most "recent" match.
The LINQ equivelent would be to use FirstOrDefault:
var match = myEnumerable.FirstOrDefault(value => value.Aaa == aaa && value.Bbb == bbb);
Or you can do the following way:
var match = myEnumerable.Where(value => value.Aaa == aaa && value.Bbb == bbb)
.FirstOrDefault();

Return nested alias for linq expression

I have the following Linq Expression
var tooDeep = shoppers
.Where(x => x.Cart.CartSuppliers.First().Name == "Supplier1")
.ToList();
I need to turn the name part into the following string.
x.Cart.CartSuppliers.Name
As part of this I turned the Expression into a string and then split on the . and removed the First() argument. However, when I get to CartSuppliers this returns a Suppliers[] array. Is there a way to get the single type from this. eg. I need to get a Supplier back.
Update: Got it to work
var fullName = m.ToString().Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
// this supports the "deep graph" name - "Product.Address.City"
var fixedName = fullName.Skip(1).Take(fullName.Length - 1)
.Where(x => x != "First()")
.Select(x => System.Text.RegularExpressions.Regex.Replace(x, #"\[[0-9]+\]",""))
.ToArray();
with this:
var prop = property.PropertyType.HasElementType ? property.PropertyType.GetElementType() property.PropertyType;
which enabled be to find the individual type from an array.
Thanks
firstSupplierWithNeededName = shoppers
.SelectMany(s => s.Cart.CartSuppliers)
.First(s => s.Name == "Supplier1");
But also look into using FirstOrDefault or Single if it has to return just one.

Categories

Resources