I have the following classes (omit syntax errors please)
class Message {
public Person Recipient
...
}
class Person {
public List<Group> Groups
...
}
class Group {
public GroupTypesEnum GroupType
...
}
I want to generate a count of messages by group type.
Example:
Message "hello", to "Person A", which is in "Group X" (type T1), "Group Y" (type T2)
Message "blah", to "Person B", which is in "Group Z" (type T1)
The query that I need would output:
Group type: T1, message count: 2
Group type: T2, message count: 1
How can I achieve this in Linq To Entities?
considering that a person might be in zero groups
I know how to achieve this in memory, but I want to use Linq-To-Entities:
Dictionary<GroupTypesEnum, int> count = new ...
foreach (Message msg in db.Messages) {
foreach (Group group in msg.Recipient.Groups) {
count[group.GroupType]++;
}
}
This is not so hard with SelectMany
var result = messages
.SelectMany(m => m.Recipient.Groups)
.GroupBy(t => t.GroupType)
.Select(g => new { gType = g.Key, count = g.Count()});
Then result.Dump() - in LinqPad
Full source for my test code is here: https://gist.github.com/hoganlong/5841555
Related
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 "".
I have a linq to sql and have been researching how to use linq to sql to group your results. I only see samples with count and sum in them. My model is that each customer order has a variety of notes and could have multiple notes. Right now it's listing all the customer orders and multiple times if it has multiple notes.
How do I use group by in Linq to Sql without the sums/counts aggregate
I have tried:
public IQueryable<object> getAllamcase()
{
try
{
var q = (from c in _context.Customer
join am in _context.table2 on c.id equals am.id
join ampn in _context.table3 on am.id equals ampn.id
join ay in _context.tabl4 on am.id equals ay.id
join oim in _context.table5 on am.id equals oim.id
group c.FileNum by new
{
FileNum = c.order,
assignmentdt = am.Assignment_DT,
oimname = oim.FullName,
notes = ampn.ProgressNotes,
years = ay.AMYear
}).AsQueryable();
return q;
}
catch (Exception ex)
{
_logger.LogError("Could not get......", ex);
return null;
}
}
My results are look as multiple jsons
Customer notes
1 notes 1
1 notes 2
1 notes 3
2 notes 1
2 notes 2
I just want it to return in one json like
Customer notes
1 notes 1
notes 2
notes 3
2 notes 1
notes 2
Your question is unclear, but as I #GertArnold stated, if you want to load notes of customers you should use Navigation properties. Also, please look at naming conventions. Your code will be much cleaner if you name variables, etc. correctly. But according to your question header, I can suggest you following. Imagine that you have Note class:
public class Note
{
public int CustomerId { get; set; }
public string NoteName { get; set; }
}
And you have list of Notes as following:
List<Note> notes = new List<Note>
{
new Note { CustomerId = 1, NoteName = "note 1" },
new Note { CustomerId = 1, NoteName = "note 2" },
new Note { CustomerId = 1, NoteName = "note 3" },
new Note { CustomerId = 1, NoteName = "note 4" },
new Note { CustomerId = 2, NoteName = "note 1" },
new Note { CustomerId = 2, NoteName = "note 2" },
new Note { CustomerId = 3, NoteName = "note 1" },
};
If you want to get CustomerId-s and related notes from this list you can easyli achieve it by grouping them:
var result = notes
.GroupBy(m => m.CustomerId)
.Select(g => new
{
CustomerId = g.Key,
Notes = g.Select(m => m.NoteName).ToList()
});
The result will be:
CustomerId || NoteName
1 note 1
note 2
note 3
note 4
2 note 1
note 2
3 note 1
I hope, it will help you.
I've written a Linq sentence like this:
var fs = list
.GroupBy(i =>
new {
X = i.X,
Ps = i.Properties.Where(p => p.Key.Equals("m")) <<<<<<<<<<<
}
)
.Select(g => g.Key });
Am I able to group by IEnumerable.Where(...) fields?
The grouping won't work here.
When grouping, the runtime will try to compare group keys in order to produce proper groups. However, since in the group key you use a property (Ps) which is a distinct IEnumerable<T> for each item in list (the comparison is made on reference equality not on sequence equality) this will result in a different collection for each element; in other words if you'll have two items:
var a = new { X = 1, Properties = new[] { "m" } };
var b = new { X = 1, Properties = new[] { "m" } };
The GroupBy clause will give you two distinct keys as you can see from the image below.
If your intent is to just project the items into the structure of the GroupBy key then you don't need the grouping; the query below should give the same result:
var fs = list.Select(item => new
{
item.X,
Ps = item.Properties.Where(p => p.Key == "m")
});
However, if you do require the results to be distinct, you'll need to create a separate class for your result and implement a separate IEqualityComparer<T> to be used with Distinct clause:
public class Result
{
public int X { get; set; }
public IEnumerable<string> Ps { get; set; }
}
public class ResultComparer : IEqualityComparer<Result>
{
public bool Equals(Result a, Result b)
{
return a.X == b.X && a.Ps.SequenceEqual(b.Ps);
}
// Implement GetHashCode
}
Having the above you can use Distinct on the first query to get distinct results:
var fs = list.Select(item => new Result
{
X = item.X,
Ps = item.Properties.Where( p => p.Key == "m")
}).Distinct(new ResultComparer());
I have a linq query which returns results ordered by first letter. Is there a way to return the first letter before the group ordered by that letter? For instance;
**A**
Acountants
Apothecary
**B**
Basketball
Biscuits
and so on. I tried grouping my results like this;
var companyquery = (from c in db.Categories
group c by c.Name.Substring(0, 1)
into cgroup
select new
{
FirstLetter = cgroup.Key,
Names = cgroup
}).OrderBy(letter => letter.FirstLetter);
return View(companyquery);
but got error:
"The model item passed into the dictionary is of type 'System.Data.Entity.Infrastructure.DbQuery1[<>f__AnonymousType31[System.String]]', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable`1[NewAtAClick.Models.Category]'.'
So now I'm using orderby instead of group _ into _ . Here is my query;
var companyquery = (from c in db.Categories
orderby c.Name.Substring(0, 1)
select c);
This returns them in alphebetical order. I tried saying
letter1 = companyquery.ToString().SubString(0,1)
then
return view(letter1 + companyquery.ToList());
But nothing.
Thanks in advance!
The error says it all. Your view expects a model of type IEnumerable<Category> but you passed in something else (in this case because you are creating a new anonymous type via the call to select new { ... }).
Instead you could declare a new type:
public class AlphabeticalMapping<T> {
public char FirstLetter { get; set; }
public List<T> Items { get; set; }
}
And change your query to return:
var companyquery = (from c in db.Categories
group c by c.Name.Substring(0, 1)
into cgroup
select new AlphabeticalMapping<Category>() {
FirstLetter = cgroup.Key,
Items= cgroup.ToList()
}).OrderBy(mapping => mapping.FirstLetter);
And change your view to expect IEnumerable<AlphabeticalMapping<Category>>
What happens if you try this?
var companyquery = (from c in db.Categories
group c by c.Name.Substring(0, 1)
into cgroup
select new,
{
FirstLetter = cgroup.Key,
Names = cgroup
})
.OrderBy(letter => letter.FirstLetter)
.ToDictionary(k => k.FirstLetter, e => e.Names);
Assume I have generic list L of some type in c#. Then, using linq, call OrderBy() on it, passing in a lambda expression.
If I then re-assign the L, the previous order operation will obviously be lost.
Is there any way I can 'save' the lambda expression I used on the list before i reassigned it, and re-apply it?
Use a Func delegate to store your ordering then pass that to the OrderBy method:
Func<int, int> orderFunc = i => i; // func for ordering
var list = Enumerable.Range(1,10).OrderByDescending(i => i); // 10, 9 ... 1
var newList = list.OrderBy(orderFunc); // 1, 2 ... 10
As another example consider a Person class:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
Now you want to preserve a sort order that sorts by the Name property. In this case the Func operates on a Person type (T) and the TResult will be a string since Name is a string and is what you are sorting by.
Func<Person, string> nameOrder = p => p.Name;
var list = new List<Person>
{
new Person { Id = 1, Name = "ABC" },
new Person { Id = 2, Name = "DEF" },
new Person { Id = 3, Name = "GHI" },
};
// descending order by name
foreach (var p in list.OrderByDescending(nameOrder))
Console.WriteLine(p.Id + ":" + p.Name);
// 3:GHI
// 2:DEF
// 1:ABC
// re-assinging the list
list = new List<Person>
{
new Person { Id = 23, Name = "Foo" },
new Person { Id = 14, Name = "Buzz" },
new Person { Id = 50, Name = "Bar" },
};
// reusing the order function (ascending by name in this case)
foreach (var p in list.OrderBy(nameOrder))
Console.WriteLine(p.Id + ":" + p.Name);
// 50:Bar
// 14:Buzz
// 23:Foo
EDIT: be sure to add ToList() after the OrderBy calls if you need a List<T> since the LINQ methods will return an IEnumerable<T>.
Calling ToList() or ToArray() on your IEnumerable<T> will cause it to be immediately evaluated. You can then assign the resulting list or array to "save" your ordered list.