Combining Where and OrderByDescending Using a Custom Method - c#

I'm trying to use a custom method for ordering but I also want to use that same custom method to only return results that match a certain value. I realize that the code below works but I was hoping there was a way to combine both methods to hopefully speed up the process.
public IEnumerable<List<decimal>> GetBestList(List<List<decimal>> inputList)
{
var bestList = inputList.Where(x => x != null && CalculateAverage(x) > 0).
OrderByDescending(x => CalculateAverage(x)));
return bestList;
}
public decimal CalculateAverage(List<decimal> inputList)
{
return inputList.Average();
}

As far as I understand you want to prevent recalculation of average, so you can use Select to create a temporary tuple containing average and original list, for example like that:
public IEnumerable<List<decimal>> GetBestList(List<List<decimal>> inputList)
{
var bestList = inputList
.Where(x => x != null )
.Select(x => (x, Avg: CalculateAverage(x)))
.Where(x => x.Avg > 0)
.OrderByDescending(x => x.Avg)
.Select(x => x.x);
return bestList;
}

The way to avoid performing the potentially expensive computation multiple times is to project the sequence into a new value that includes the list and the computation. This is simpler and easier with query syntax than method syntax:
public IEnumerable<List<decimal>> GetBestList(List<List<decimal>> inputList)
{
var query = from list in inputList
where list != null
let average = CalculateAverage(list)
where average > 0
orderby average
select list;
}

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: how to get an intersection of two sets of ints?

There must be a way to compare two sets of results while staying in LINQ. Here's my existing code that uses a HashSet to do the comparison after two separate queries:
public static void AssertDealershipsShareTransactionGatewayCredentialIds(long DealershipLocationId1,
long DealershipLocationId2)
{
using (var sqlDatabase = new SqlDatabaseConnection())
{
var DealershipCredentials1 =
sqlDatabase.Tables.DealershipLocationTransactionGateway
.Where(x => x.DealershipLocationId == DealershipLocationId1)
.Select(x => x.TransactionGatewayCredentialId);
var DealershipCredentials2 =
sqlDatabase.Tables.DealershipLocationTransactionGateway
.Where(x => x.DealershipLocationId == DealershipLocationId2)
.Select(x => x.TransactionGatewayCredentialId);
var doSetsOfCredentialsMatch = new HashSet<int>(DealershipCredentials1).SetEquals(DealershipCredentials2);
Assert.IsTrue(doSetsOfCredentialsMatch,
"The sets of TransactionGatewayCredentialIds belonging to each Dealership did not match");
}
}
Ideas? Thanks.
Easy answer (This will make 1, possibly 2 database calls, both of which only return a boolean):
if (list1.Except(list2).Any() || list2.Except(list1).Any())
{
... They did not match ...
}
Better answer (This will make 1 database call returning a boolean):
var DealershipCredentials1 =
sqlDatabase.Tables.DealershipLocationTransactionGateway
.Where(x => x.DealershipLocationId == DealershipLocationId1)
.Select(x => x.TransactionGatewayCredentialId);
var DealershipCredentials2 =
sqlDatabase.Tables.DealershipLocationTransactionGateway
.Where(x => x.DealershipLocationId == DealershipLocationId2)
.Select(x => x.TransactionGatewayCredentialId);
if (DealershipCredentials1.GroupJoin(DealershipCredential2,a=>a,b=>b,(a,b)=>!b.Any())
.Union(
DealershipCredentials2.GroupJoin(DealershipCredential1,a=>a,b=>b,(a,b)=>!b.Any())
).Any(a=>a))
{
... They did not match ...
}
The second method works by unioning a left outer join that returns a boolean indicating if any unmatching records were found with a right outer join that does the same. I haven't tested it, but in theory, it should return a simple boolean from the database.
Another approach, which is essentially the same as the first, but wrapped in a single LINQ, so it will always only make 1 database call:
if (list1.Except(list2).Union(list2.Except(list1)).Any())
{
}
And another approach:
var common=list1.Intersect(list2);
if (list1.Except(common).Union(list2.Except(common)).Any()) {}

How could I use Func<> to modify a single condition of a lambda

I have a simple method containing a query to calculate some values.
private decimal MyQueryBuilderMethod(List<ThingsViewModel> myThings)
{
return myThings.Where(x => x.Id == 1)
.Select(x => (x.ThisValue * x.That))
.Sum();
}
My aim is to modify the method to allow me to specify the x.ThisValue field being queried in the object.
If I were to specify the Where clause of my query, I might pass a predicate but in this case, I only want to alter the value of x.ThisValue.
private decimal MyQueryBuilderMethod(List<ThingsViewModel> myThings, Func<ThingsViewModel,bool> predicates)
{
return myThings.Where(predicates)
.Select(x => (x.ThisValue * x.That))
.Sum();
}
Ideally, I'd like to pass in something like:
MyQueryBuilderMethod(things, x.ThisOtherValue)
Following should work:
private decimal MyQueryBuilderMethod(List<ThingsViewModel> myThings,
Func<ThingsViewModel, decimal> thisValue)
{
return myThings.Where(x => x.Id == 1)
.Sum(x => (thisValue(x) * x.That));
}
I also replaced the Select(...).Sum() by the shorter but equivalent Sum(...).
You then can call it like the following:
var result = MyQueryBuilderMethod(myThings, t => t.ThisValue);

Linq query return 1 item instead of all

I am using the query below to grab all records that have a SubCategoryName == subCatName and i want to return all of there ProductID's as a list of ints. Problem is when my code runs it is only returning 1 int(record) instead of all. How can i make it return all of the records that have that subCatName? Its returning a count = 1 with a capacity of 4. So it is a int[4] but only the first [0] is = to a actual product ID the rest returning zero?
public List<int> GetPRodSubCats(string subCatName)
{
var _db = new ProductContext();
if (subCatName != null)
{
var query = _db.ProductSubCat
.Where(x => x.SubCategory.SubCategoryName == subCatName)
.Select(p => p.ProductID);
return query.ToList<int>();
}
return null;
}
As Daniel already has mentioned, the code should work. But maybe you are expecting that it's case-insensitive or ignores white-spaces. So this is more tolerant:
subCatName = subCatName.Trim();
List<int> productIDs = _db.ProductSubCat
.Where(x => String.Equals(x.SubCategory.SubCategoryName.Trim(), subCatName, StringComparison.OrdinalIgnoreCase))
.Select(p => p.ProductID)
.ToList();
This seems more like an expected behavior here. How do you know you don't only have 1 record that satisfies the Where predicate.
Your code is correct, however you might want to normalize your comparison.
x => x.SubCategory.SubCategoryName == subCatName
to use a specific case for instance:
x => x.SubCategory.SubCategoryName.ToLower() == subCatName.ToLower()
you might also consider a Trim.

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);
}
}

Categories

Resources