LINQ GroupBy while converting from string to decimal and then back to string - linq

Is it possible to convert a string value to a decimal value within a LINQ expression that performs an aggregate function like SUM or AVERAGE?
Assume the example below where I have a collection of Bank Accounts where my goal is to obtain an average of each customers bank account if they have a balance. The data comes from an XML API where all the data is read in a strings.
public class BankAccount
{
string Id{ get; set; }
string CustomerId { get; set; }
string Balance { get; set; }
}
Sample data ...
{ Id = "1", CustomerId = "Bob", Balance = "1" }
{ Id = "2", CustomerId = "Bob", Balance = "2" }
{ Id = "3", CustomerId = "Sam", Balance = "4" }
{ Id = "4", CustomerId = "Sam", Balance = "" }
{ Id = "5", CustomerId = "Alice", Balance = "" }
LINQ grouping expression. Is there a way to convert the value of Balance to a decimal so an average can be taken within the LINQ statement? I tried x => Decimal.Parse(x.Balance) but got an Input string was not in a correct format error. I only need to convert the Balance property to decimal for the Average calculation as the results would be rendered as a string in the XML.
At the same time, if an account does not have a balance listed (i.e. it's blank like Sams's first account and Alice's only account above) then I don't want the Average to take that entry included in the average, though I still want the account grouped in for display.
var groupedResults = allAccounts
.GroupBy(x => new {x.CustomerId, x.Balance})
.Select(g => new BankAccount {
CustomerId = g.Customer.Key.CustomerId,
Balance = g.Average(x => x.Balance)
}).ToList();
These are the results I am looking for:
{ CustomerId = "Bob", Balance = "1.5" }
{ CustomerId = "Sam", Balance = "4" }
{ CustomerId = "Alice", Balance = "" }

I think to achieve the result you are looking for you should try this:
var groupedResults = allAccounts
.GroupBy(x =>x.CustomerId)
.Select(g => new BankAccount {
CustomerId = g.Key,
Balance = g.Where(x =>!string.IsNullOrEmpty(x.Balance))
.Select(x =>(decimal?)decimal.Parse(x.Balance))
.DefaultIfEmpty(null)
.Average().ToString()
}).ToList();
First just group by CustomerId, is not necessary to include the Balance there. Then, to get the average and avoid the error parsing include the condition to make sure the Balance is not empty.
Another way to do it using query syntax:
from e in allAccounts
group e by e.CustomerId into g
let temp=g.Where(x =>!string.IsNullOrEmpty(x.Balance))
select new BankAccount(){CustomerId = g.Key,
Balance =temp.Any()?
temp.Average(x =>Decimal.Parse(x.Balance)).ToString():""
};

decimal d;
var groupedResults = allAccounts.GroupBy(a => a.CustomerId)
.Select(g => new BankAccount { CustomerId = g.Key, Balance = g.Average(b =>
decimal.TryParse(b.Balance, out d) ? (decimal?)d : null).ToString() }).ToList();
The .TryParse part results in (decimal?)null for strings that can't be parsed, which are then ignored by .Average. Also, the last average for Alice results in (decimal?)null and then in "".

Related

LINQ DistinctBy chosing what object to keep

If I have a list of objects and I don't want to allow duplicates of a certain attribute of the objects. My understanding is that I can use DistinctBy() to remove one of the objects. My question is, how do I choose which of the objects with the same value of an attribute value do I keep?
Example:
How would I go about removing any objects with a duplicate value of "year" in the list tm and keep the object with the highest value of someValue?
class TestModel{
public int year{ get; set; }
public int someValue { get; set; }
}
List<TestModel> tm = new List<TestModel>();
//populate list
//I was thinking something like this
tm.DistinctBy(x => x.year).Select(x => max(X=>someValue))
You can use GroupBy and Aggregate (there is no MaxBy built-in method in LINQ):
tm
.GroupBy(tm => tm.year)
.Select(g => g.Aggregate((acc, next) => acc.someValue > next.someValue ? acc : next))
User the GroupBy followed by the SelectMany/Take(1) pattern with an OrderBy:
IEnumerable<TestModel> result =
tm
.GroupBy(x => x.year)
.SelectMany(xs =>
xs
.OrderByDescending(x => x.someValue)
.Take(1));
Here's an example:
List<TestModel> tm = new List<TestModel>()
{
new TestModel() { year = 2020, someValue = 5 },
new TestModel() { year = 2020, someValue = 15 },
new TestModel() { year = 2019, someValue = 6 },
};
That gives me:

Select record where value equals value of id

This is a simple but hard to describe question
await dbContext.MyTable.AnyAsync(x => x.valueOne == valueOne && x.valueTwo == /*here's my problem*/ );
valueOne is getting passed in as a parameter. The end of the statement in pseudocode would be:
x.valueTwo == the valueTwo of valueOne's record
I know I could pull in valueOne's record and compare from there, but that's clumsier and I feel like there's definitely a better way if only I could find it.
How can I do this in one line?
Edit: clarification
To clarify, the clumsy way would be
var valueOneRecord = await dbContext.MyTable.FirstAsync(x => x.valueOne == valueOne);
exists = await dbContext.MyTable.AnyAsync(x => x.valueOne == valueOne && x.valueTwo == valueOneRecord.valueTwo);
I'm trying to merge this into one line instead of having two database requests
Please try the below
dbContext.MyTable.Join(dbContext.MyTable,
x => new {
x.valueOne,
valueTwo = x.valueOne
},
y => new {
y.valueOne,
y.valueTwo
},
(x, y) => new
{
record1_valueOne = x.valueOne,
record1_valueTwo = x.valueTwo,
record2_valueOne = y.valueOne,
record2_valueTwo = y.valueTwo,
}).Where(x => x.record1_valueOne == valueOneParam && x.record2_valueOne == valueOneParam);
Quite often it is easier for us if you give us your requirement instead of giving us some code and say that the code is not working.
Let's assume that your clumsy method is what you want.
So MyTable is a sequence of objects, let's call them Values. Every Value has at least two properties: ValueOne and ValueTwo. You also have an input value: valueOne.
Suppose you had Schools and Students, every School had a primary key in Id, and a State
class School
{
public int Id {get; set;}
public int CityId {get; set;}
...
}
public class Student
{
public int Id {get; set;}
public string Name {get; set;}
public int CityId {get; set;}
}
A note so strange query would be:
Give me all Students that live in the same City as School 10
int schoolId = 10;
var studentsInSameCityAsSchool10 = dbContext.Schools
// keep only school with Id == 10
.Where(school => school.Id == 10)
// get all Students on these Schools
.GroupJoin(dbContext.Students,
school => school.CityId,
student => student.CityId,
(school, studentsWithSameCityId) => new
{
SchoolId = school.Id,
Students = studentsWithSameCityId.Select(student => new
{
StudentId = student.Id,
Name = student.Name,
...
})
.ToList(),
});
Very straightforward, isn't it. Now let's do a query in just one table:
Give me all Schools with the same CityId as School [10]
int schoolId = 10;
var studentsInSameCityAsSchool10 = dbContext.Schools
// keep only school with Id == 10
.Where(school => school.Id == 10)
// get all Students on these Schools
.GroupJoin(dbContext.Students,
school => school.CityId,
student => student.CityId,
(school, studentsWithSameCityId) => new
{
SchoolId = school.Id,
Students = studentsWithSameCityId.Select(student => new
{
StudentId = student.Id,
Name = student.Name,
...
})
.ToList(),
});
Very straightforward, isn't it. Now let's do a query in just one table:
Give me all Schools with the same CityId as School [10]
int schoolId = 10;
var studentsInSameCityAsSchool10 = dbContext.Schools
// keep only school with Id == 10
.Where(school => school.Id == 10)
// get all Students on these Schools
.Join(dbContext.Students, // Join Schools and Students
school => school.CityId, // from every School take the CityId
student => student.CityId, // from every Student take the CityId
(school, studentWithSameCityId) => new // from every School and every Student
{ // that has the same CityId, make one new
SchoolId = school.Id,
CityId = school.CityId,
StudentThatLiveInCityId = studentWithSameCityId,
});
Very straightforward, isn't it? Now let's do a query in just one table:
Give me all Schools with the same CityId as School [10]
int schoolId = 10;
var studentsInSameCityAsSchool10 = dbContext.Schools
.Where(school => school.Id == 10)
// get all Schools in the same City as School[10]
.Join(dbContext.Schools, // Join Schools and Schools
school1 => school1.CityId, // from every School1 take the CityId
school2 => school2.CityId, // from every School2 take the CityId
(school1, school2WithSameCityId) => new // from every School and every School
{ // that has the same CityId, make one new
...
});
But what does this have to do with my question
Now let's change some of the property names:
class School
{
public int ValueOne {get; set;} // was Id
public int ValueTwo {get; set;} // was CityId
}
int valueOne = 10; // was schoolId
Give me all Schools that have the same ValueTwo (was: located in the same City, or has same cityId) as School with ValueOne == valueOne (was: as School [10])
After the exercizes with Schools and Students this one should be easy:
int valueOne = 10;
var valuesWithSameValueTwo = dbContext.Schools
.Where(value => value.ValueOne == 10)
// get all Values with same ValueTwo Value[10]
.Join(dbContext.Schools, // Join Schools and Schools
val1 => val1.ValueTwo, // from every val1 take the ValueTwo
val2 => val2.ValueTwo, // from every val2 take the ValueTwo
(val, valWithSameValueTwoId) => new
{
...
});
Well, your query is easier: you only want to know if there is any such element. So you don't need to select any properties, and you can stop as soon as you've found one:
// Where and Join:
...
(val, valWithSameValueTwoId) => valWithSameTwoId)
.Any();

Concert tabular data into Objects through LINQ

StudentId Name AddResss
1 A ABC
1 A XYZ
How Can i convert above result into List of following Objects
Class Result {
int StudentId {get;set;}
List<String> Address {get;set;}
}
Basically need to group by on studentId
studentList.GroupBy(x => x.StudentId)
.Select(x => new Result
{
StudentId = x.Key,
Address = x.Select(y => y.AddResss).ToList()
});
var results =
from row in table
group row.Address by new { row.StudentId, row.Name } into g
select new Result { StudentId = g.Key.StudentId, Addresses = g.ToList() };

Filtering out values from a list of object in a List

I have an IEnumerable collection of UnitGroup: IEnumerable<UnitGroup>,
class UnitGroup
{
string key { get; set; }
List<UnitType> NameList { get; set; }
}
class UnitType
{
String UnitName{ get; set; }
Description { get; set; }
}
Now I want to filterIEnumerable<UnitGroup> based on UnitType's UnitName.
For example I want to get only the records of UnitName that contains a string and remove remaining.
something like this:
IEnumerable<UnitGroup> Groups;
IEnumerable<UnitGroup> filteredResult = Groups.NameList(o => o.UnitName.contains("test"));
And get IEnumerable<UnitGroup> with only filtered UnitNames under UnitType under UnitGroup.
What is the best way of acheiving this?
I'm not 100% sure what you're trying to achieve. Could you provide some sample data, to make it more clear?
Although, I think it may fit into your goal:
IEnumerable<UnitGroup> Groups;
var filteredResult = Groups.Select(g => new UnitGroup {
key = g.key,
NameList = g.NameList
.Where(n => n.UnitName == "test")
.ToList()
})
.Where(g => g.NameList.Count > 0);
Here is another way that should do what #MarcinJuraszek answers does. (I am guessing the intent of the question as well.)
IEnumerable<UnitGroup> Groups;
var filteredResult = Groups.Where (g => g.NameList.Count() > g.NameList.RemoveAll(nl => nl.UnitName != "Name1"));
If the number of removed items was less than the original count, then we have items that are of interest, so select the parent.
Note: This will modify the original collection, so if you need to filter it more than once then this is not the answer you are looking for.
Try this:
var filteredList = from g in Groups
where g.NameList.Exists(i=>i.UnitName=="test")
select g;

Find / Count Redundant Records in a List<T>

I am looking for a way to identify duplicate records...only I want / expect to see them.
So the records aren't duplicated completely but the unique fields I am unconcerned with at this point. I just want to see if they have made X# payments of the exact same amount, via the exact same card, to the exact same person. (Bogus example just to illustrate)
The collection is a List<> further whatever X# is the List<>.Count will be X#. In other words all the records in the list match (again just the fields I am concerned with) or I will reject it.
The best I can come up with is to take the first record get value of say PayAmount and LINQ the other two to see if they have the same PayAmount value. Repeat for all fields to be matched. This seems horribly inefficient but I am not smart enough to think of a better way.
So any thoughts, ideas, pointers would be greatly appreciated.
JB
Something like this should do it.
var duplicates = list.GroupBy(x => new { x.Amount, x.CardNumber, x.PersonName })
.Where(x => x.Count() > 1);
Working example:
class Program
{
static void Main(string[] args)
{
List<Entry> table = new List<Entry>();
var dup1 = new Entry
{
Name = "David",
CardNumber = 123456789,
PaymentAmount = 70.00M
};
var dup2 = new Entry
{
Name = "Daniel",
CardNumber = 987654321,
PaymentAmount = 45.00M
};
//3 duplicates
table.Add(dup1);
table.Add(dup1);
table.Add(dup1);
//2 duplicates
table.Add(dup2);
table.Add(dup2);
//Find duplicates query
var query = from p in table
group p by new { p.Name, p.CardNumber, p.PaymentAmount } into g
where g.Count() > 1
select new
{
name = g.Key.Name,
cardNumber = g.Key.CardNumber,
amount = g.Key.PaymentAmount,
count = g.Count()
};
foreach (var item in query)
{
Console.WriteLine("{0}, {1}, {2}, {3}", item.name, item.cardNumber, item.amount, item.count);
}
Console.ReadKey();
}
}
public class Entry
{
public string Name { get; set; }
public int CardNumber { get; set; }
public decimal PaymentAmount { get; set; }
}
The meat of which is this:
var query = from p in table
group p by new { p.Name, p.CardNumber, p.PaymentAmount } into g
where g.Count() > 1
select new
{
name = g.Key.Name,
cardNumber = g.Key.CardNumber,
amount = g.Key.PaymentAmount,
count = g.Count()
};
You're unique entries are based off of the 3 criteria of Name, Card Number, and Payment Amount so you group by them and then use .Count() to count how many of those unique values exist. where g.Count() > 1 filters the group to duplicates only.

Resources