Adding a sum to a list instead of a count - c#

I'm using ASP.Net Core 3.1 to develop a web app. We need to return a list of values to a View. The list includes counts and sums of data. We have created a ViewModel to help. It looks like this:
public class ObjectCountViewModel
{
[DisplayName("Description")]
public string Description { get; set; }
[DisplayName("Count")]
public decimal Count { get; set; }
}
We created a list in the Controller to return the values. It looks like this:
List<ObjectCountViewModel> objectCounts = new List<ObjectCountViewModel>();
Next we added values to the list like this:
int itemsToCount = objects.Where(e => e.ObjectItems.Where(ep => ep.ObjectItemType.Description.Contains("ItemToCount") && ep.ObjectItemSelctionType.Description.Contains("Taken")).Count()>0).Count();
objectCounts.Add(new ObjectCountViewModel() { Description = "Items Counted", Count = itemsToCount });
This code works great! But we also need to generate a sum. this will be used to count items with a decimal I can't get a sum to work. Here is one of the solutions I have tried:
decimal itemToSum = objects.Where(e => e.ObjectItems.Where(ep => ep.ObjectItemType.Description.Contains("ItemToSum") && ep.ObjectItemSelectionType.Description.Contains("Taken") && ep.ObjectValueAmount>0).Sum()>0).Sum();
objectCounts.Add(new ObjectCountViewModel() { Description = "Items Taken Sum", Count = itemToSum });
I have received a wide variety of errors. The current one is: 'IEnumerable' does not contain a definition for 'Sum' and the best extension method overload 'ParallelEnumerable.Sum(ParallelQuery)' requires a receiver type of 'ParallelQuery,decimal>.
What am I doing wrong? What should my query look like for a sum?

If you have a list of lists, where you want to count all lists, then use listsOfList.SelectMany(x=>x).Count().
If you have a list of decimals, where you want a sum of all decimals, then use listsOfDecimals.Sum().
If you have a list of lists of decimals, where you want a sum of all decimals, then use listsOfListOfDecimals.SelectMany(x=>x).Sum().

I found the answer thanks to Heretic Monkey and Intellisense. I had to create a new object with the value I'm trying to sum and then filter to only select from ones that met my criteria. Then, I separated the Select statement from the Where Clause as Heretic Monkey said. Intellisense suggested I put (decimal) in front of the whole thing, and it worked! Here is my final code for this problem.
decimal itemToSum = (decimal)Objects.Where(ep => ep.RelatedObjectType.Description.Contains("Description") && ep.DifferentRelatedObjectType.Description.Contains("Description")).Select(ep => ep.itemToSum).Sum();

Related

C# LINQ How to get MAX of datatable column<string> WHERE value LIKE 'N01%'

I need help, how do I get MAX datatable column value where value LIKE 'N01%'
Basically, if I convert this to SQL:
SELECT MAX(user) FROM tblUser WHERE user LIKE 'N01%'
Thank you.
You can simply do this:
string[] data = {"hello", "N01jaja", "N01ll"};
var userWithN1 = data.Where(we => we.StartsWith("N01")).Max();
StartsWith checks if the element starts with a certain string.
If there's a class then need to implement IComparable.
Sample code:
public class TestClass : IComparable<string>
{
public string Value { get; private set; }
public int CompareTo(string other) { return Value.CompareTo(other); }
}
var result = foo.tblUser.Where(u => u.user.StartsWith("N01")).Max(u => u.user));
Simply use a where statement to start your filter, use a StartsWith to emulate SQL's xx% pattern. Then use Max on a particular column. Though make sure User is something that will actually have a Max value.
In LINQ, I always find it helpful to break the problem down. Here in this case, you have a list of items, you want to narrow down that list with a WHERE clause and return the MAX of the remaining items.
Start
var myItems = db.GetMyList();
WHERE with LIKE
Assuming User is a string variable
myItems = myItems.Where(x=>x.User.StartsWith("N01"));
MAX
var maxItem = myItems.Max(x=>x.User);
All Together
var maxItem = db.GetMyList().Where(x=>x.User.StartsWith("N01")).Max(x=>x.User);
edit - Per comment below, since the search string was 'N01%', is should be starts with and not contains.

How to do this kind of search in ASP.net MVC?

I have an ASP.NET MVC web application.
The SQL table has one column ProdNum and it contains data such as 4892-34-456-2311.
The user needs a form to search the database that includes this field.
The problem is that the user wants to have 4 separate fields in the UI razor view whereas each field should match with the 4 parts of data above between -.
For example ProdNum1, ProdNum2, ProdNum3 and ProdNum4 field should match with 4892, 34, 456, 2311.
Since the entire search form contains many fields including these 4 fields, the search logic is based on a predicate which is inherited from the PredicateBuilder class.
Something like this:
...other field to be filtered
if (!string.IsNullOrEmpty(ProdNum1) {
predicate = predicate.And(
t => t.ProdNum.toString().Split('-')[0].Contains(ProdNum1).ToList();
...other fields to be filtered
But the above code has run-time error:
The LINQ expression node type 'ArrayIndex' is not supported in LINQ to Entities`
Does anybody know how to resolve this issue?
Thanks a lot for all responses, finally, I found an easy way to resolve it.
instead of rebuilding models and change the database tables, I just add extra space in the search strings to match the search criteria. since the data format always is: 4892-34-456-2311, so I use Startwith(PODNum1) to search first field, and use Contains("-" + PODNum2 + "-") to search second and third strings (replace PODNum1 to PODNum3), and use EndWith("-" + PODNum4) to search 4th string. This way, I don't need to change anything else, it is simple.
Again, thanks a lot for all responses, much appreciated.
If i understand this correct,you have one column which u want to act like 4 different column ? This isn't worth it...For that,you need to Split each rows column data,create a class to handle the splitted data and finally use a `List .Thats a useless workaround.I rather suggest u to use 4 columns instead.
But if you still want to go with your existing applied method,you first need to Split as i mentioned earlier.For that,here's an example :
public void test()
{
SqlDataReader datareader = new SqlDataReader;
while (datareader.read)
{
string part1 = datareader(1).toString.Split("-")(0);///the 1st part of your column data
string part2 = datareader(1).toString.Split("-")(1);///the 2nd part of your column data
}
}
Now,as mentioned in the comments,you can rather a class to handle all the data.For example,let's call it mydata
public class mydata {
public string part1;
public string part2;
public string part3;
public string part4;
}
Now,within the While loop of the SqlDatareader,declare a new instance of this class and pass the values to it.An example :
public void test()
{
SqlDataReader datareader = new SqlDataReader;
while (datareader.read)
{
Mydata alldata = new Mydata;
alldata.Part1 = datareader(1).toString.Split("-")(0);
alldata.Part2 = datareader(1).toString.Split("-")(1);
}
}
Create a list of the class in class-level
public class MyForm
{
List<MyData> storedData = new List<MyData>;
}
Within the while loop of the SqlDatareader,add this at the end :
storedData.Add(allData);
So finally, u have a list of all the splitted data..So write your filtering logic easily :)
As already mentioned in a comment, the error means that accessing data via index (see [0]) is not supported when translating your expression to SQL. Split('-') is also not supported hence you have to resort to the supported functions Substring() and IndexOf(startIndex).
You could do something like the following to first transform the string into 4 number strings ...
.Select(t => new {
t.ProdNum,
FirstNumber = t.ProdNum.Substring(0, t.ProdNum.IndexOf("-")),
Remainder = t.ProdNum.Substring(t.ProdNum.IndexOf("-") + 1)
})
.Select(t => new {
t.ProdNum,
t.FirstNumber,
SecondNumber = t.Remainder.Substring(0, t.Remainder.IndexOf("-")),
Remainder = t.Remainder.Substring(t.Remainder.IndexOf("-") + 1)
})
.Select(t => new {
t.ProdNum,
t.FirstNumber,
t.SecondNumber,
ThirdNumber = t.Remainder.Substring(0, t.Remainder.IndexOf("-")),
FourthNumber = t.Remainder.Substring(t.Remainder.IndexOf("-") + 1)
})
... and then you could simply write something like
if (!string.IsNullOrEmpty(ProdNum3) {
predicate = predicate.And(
t => t.ThirdNumber.Contains(ProdNum3)

using string.split in entity to traverse tree depth

i have the following self-referencing table
public partial class products_category
{
public long id { get; set; }
public string category_name { get; set; }
public string category_description { get; set; }
//self referencing to table id
public Nullable<long> Parent_Id { get; set; }
public string navPath {get; set; }
}
here string navpath contains all the leading parents for a child categroy, say:
"Clothes" = 1 Parent_id=null, navpath=""
"Silk" = 2 Parent_id=1 navpath="1"
"Silk Suit"=3 parent_id=2 navpath="1-2"
"Saree" =4 parent_id=3 navpath="1-2-3"
"Dress Material"=5 parent_id=1 navpath="1" and so on....
now as per this scenario i want to access the flattend tree for frther processing for a certain depth only say to level 2 or until level 4 depth of children associated with navpath.
my idea regarding this issue was to approach using linq to ef this way:
var catTrees = db.products_category.Where(pc => pc.navpath.Split('-').Length < 4).ToList();
i am using the following link to do further traversing and tree generation:
https://bitlush.com/blog/recursive-hierarchical-joins-in-c-sharp-and-linq
and it is doing a great work so far, the only issue is i dont want to pre select whole table for processing. i want to achieve paging and a certain level of depth for first iteration, so i can maintain performance in case of thousand of records. [think of this as a category hierarchy or blog/youtube comments hierarchy].
but using the above ef linq command is giving the following error:
The LINQ expression node type 'ArrayLength' is not supported in LINQ to Entities.
i checked with ef docs and other places in SO to know that string.split doesn't work with EF implicitly. but can we apply it using extension methods or can this tree selection have alternate approach without using string.split and hitting DB only ones?
please advice.
This looks like an issues with building SQL code out of your LINQ mpre specifically SQL which takes a string splits it on dash and counts the elements.
if you dont hate the idea of loading into memory then you can force anything :)
var catTrees = db.products_category.ToList().Where(pc => pc.navpath.Split('-').Length < 4).ToList();
The trick here is to force the execution of the SQL by adding the .ToList() when we want the data from the database. This is called realizing the data.
Even with that realization trick the count is faster
var catTrees = db.products_category.ToList().Where(pc => pc.navpath.Count(a => a == '-') < 3).ToList();
these solutions are essentially the same as
List<Result> filter() {
List<Result> r = new List<Result>();
foreach(var a in db.products_category) {
if(a.navpath.Count(a => a == '-') < 3) {
r.add(a);
}
}
return r;
}
When thinking about it the filter method is somewhat less memory intensive as it reads one and one and never stores everything in memory. (in theory at least, only a few really knows what the .NET compiler does in the shadows)
I would advice you against using the navpath for checking depth.
If you can change your model, you could add an additional numeric Depth field for each category and populate it according its navpath, then you could select them from your code in this way:
var catTrees = db.products_category.Where(pc => pc.Depth < 3).ToList();
There are many ways to populate that new column, but the bottom line is that you will have to do it just once (given that you keep track of it every time you modify the navpath of a category).
One possible way of populating it would be looping through all categories, something like:
var allCategories = db.products_category.ToList();
foreach(var category in allCategories)
{
var depth = category.navpath == "" ? 0 : category.navpath.Split('-').Length + 1;
category.Depth = depth;
}
allCategories.SubmitChanges();

List filtering comes back as 0, but it shouldn't

I've made a list and, in certain circumstances, the list is populated. Simple. However, when I try to filter the list based on a dropdown selection ddlLeadCounty.SelectedItem.Value, the count comes back as 0. This is definitely not true as I have manually checked that both the value in the dropdown and the value pulled into the list are the same.
Could any of you wise people see where it may be going wrong? I have tried both of the following methods to the same result.
c# .net code behind
myList = myList.FindAll(delegate(Partner part)
{
return part.RegionId.Equals(ddlLeadCounty.SelectedItem.Value);
});
OR
myList = myList.Where(c => c.RegionId.Equals(ddlLeadCounty.SelectedItem.Value)).ToList();
Partner List:
public class Partner
{
public int LeadOppCount;
public string Guid;
public int RegionId;
public Partner(int LeadOppCount, string Guid, int RegionId)
{
this.LeadOppCount = LeadOppCount;
this.Guid = Guid;
this.RegionId = RegionId;
}
}
Dropdown list example:
<asp:ListItem value="100000004">Berkshire</asp:ListItem>
In my tests, at least 1 list item definitely has a regionId of 100000004.
Many thanks in advance.
Considering that you're I see RegionId, I suppose it's a integer, so write something like this a pseudocode:
int valueSelected = (int)ddlLeadCounty.SelectedItem.Value;
myList.FindAll(x=>x.RegionId == valueSelected );
In other words do not use Equals, for possibly boxed value in SelectedValue, but use concrete type.
Should work for you.
The problem is that SelectedIndex.Value is a string value not an int so you will have to parse it. The following line should work:
var myListFiltered = myList.Where(c => c.RegionId.Equals(int.Parse(ddlLeadCounty.SelectedItem.Value))).ToList();

How to sort Generic.List<> by comparing with 1 string

I have generic list already filled with values, I need to sort items, that first value should appear same as query string. Is it possible to compare and sort this values, I'm trying with no success.
Bit updating my question.
For example I have Id: 1,2,3,4,5, and I opened page with Id=5, so I need on my searchResult, to pass 4 as first item in the list.
This code for me worked fine and showing 5 as first item, but I need to force for example p2.ObjectId pass as string value just for example "4294", and object with this Id should appear first:
Sort(searchresult);
static void Sort(List<CollectionsItem> list)
{
list.Sort((p1, p2) => string.Compare(p2.ObjectId, p1.ObjectId, true));
}
Doesn't sound like you want a sort at all.
If you want to put X at the top it's just
var item = MyList[MyList.IndexOf(Target)];
MyList.Remove(item);
MyList.Insert(item,0);
If you want Target at the top and the rest sorted by objectid
Find it, remove it, sort the rest and insert it at zero...
It sounds like you want to compare the value of the property ObjectId as a number instead of a string. Here is my suggestion:
static void Sort(List<CollectionsItem> list)
{
list.Sort((p1, p2) =>
{
if (int.Parse(p1.ObjectId) == int.Parse(p2.ObjectId))
return 0;
else if (int.Parse(p2.ObjectId) > int.Parse(p1.ObjectId))
return 1;
else
return -1;
});
}

Categories