LINQ distinct on class item? - linq

Note this question is similar this one except I'm not working with linq-to-sql, so the "let" is not usable.
Basically I have a select of the type
... .Select(c => new SampleClass { Id = c.Location.Name, Name = c.Location.Name }).Distinct().ToList()
which used to work when I just had
... .Select(c => c.Location.Name).Distinct().ToList()
How would I make a distinct call on one of the items within the SampleClass?

You can group items by the key, and then select what item from the group you want to use as value. I use FirstOrDefault as an example:
... .Select(c => new SampleClass { Id = c.Location.Name, Name = c.Location.Name })
.GroupBy(c => c.Id)
.Select(group => group.FirstOrDefault())
.ToList()

Is this what you need: http://sprokhorenko.blogspot.com/2009/11/convenient-distinct.html ?
This is an extension for IEnumerable that allows you to .Distinct() for any field (or even several ones using lambdas), which creates IEqualityComparer for you on the fly.

Related

LINQ GroupBy on single property

I am just not understanding the LINQ non-query syntax for GroupBy.
I have a collection of objects that I want to group by a single property. In this case Name
{ Id="1", Name="Bob", Age="23" }
{ Id="2", Name="Sally", Age="41" }
{ Id="3", Name="Bob", Age="73" }
{ Id="4", Name="Bob", Age="34" }
I would like to end up with a collection of all the unique names
{ Name="Bob" }
{ Name="Sally" }
Based on some examples I looked at I thought this would be the way to do it
var uniqueNameCollection = Persons.GroupBy(x => x.Name).Select(y => y.Key).ToList();
But I ended up with a collection with one item. So I though maybe I was over complicating things with the projection. I tried this
var uniqueNameCollection = Persons.GroupBy(x => x.Name).ToList();
Same result. I ended up with a single item in the collection. What am I doing wrong here? I am just looking to GroupBy the Name property.
var names = Persons.Select(p => p.Name).Distinct().ToList()
If you just want names
LINQ's GroupBy doesn't work the same way that SQL's GROUP BY does.
GroupBy takes a sequence and a function to find the field to group by as parameters, and return a sequence of IGroupings that each have a Key that is the field value that was grouped by and sequence of elements in that group.
IEnumerable<IGrouping<TSource>> GroupBy<TSource, TKey>(
IEnumerable<TSource> sequence,
Func<TSource, TKey> keySelector)
{ ... }
So if you start with a list like this:
class Person
{
public string Name;
}
var people = new List<Person> {
new Person { Name = "Adam" },
new Person { Name = "Eve" }
}
Grouping by name will look like this
IEnumerable<IGrouping<Person>> groups = people.GroupBy(person => person.Name);
You could then select the key from each group like this:
IEnumerable<string> names = groups.Select(group => group.Key);
names will be distinct because if there were multiple people with the same name, they would have been in the same group and there would only be one group with that name.
For what you need, it would probably be more efficient to just select the names and then use Distinct
var names = people.Select(p => p.Name).Distinct();
var uniqueNameCollection = Persons.GroupBy(x => x.Name).Select(y => y.Key).ToList();
Appears valid to me. .net Fiddle showing proper expected outcome: https://dotnetfiddle.net/2hqOvt
Using your data I ran the following code statement
var uniqueNameCollection = people.GroupBy(x => x.Name).Select(y => y.Key).ToList();
The return results were List
Bob
Sally
With 2 items in the List
run the following statement and your count should be 2.
people.GroupBy(x => x.Name).Select(y => y.Key).ToList().Count();
Works for me, download a nugget MoreLinq
using MoreLinq
var distinctitems = list.DistinctBy( u => u.Name);

LINQ to entity, sort the subset of returned values

var myView = await _context.foo
.Include(p => p.subFoos)
.SingleOrDefaultAsync(m => m.FooID == id);
This code returns a row from the database and the linked sub rows held in the collection subFoos, what I'd like to do is order the collection rather than have them displayed in table order. I've got a field OrderBy but I can't figure out how to use it.
var myView = await _context.foo
.Include(p => p.subFoos)
.OrderBy(x => x.OrderBy)
.SingleOrDefaultAsync(m => m.FooID == id);
This orders by the top level row, rather than the collection. How do I apply OrderBy to the collection.
Thanks
Couldn't get any of the above to work so just retrieved the data using
var myView = await _context.foo
.Include(p => p.subFoos)
.SingleOrDefaultAsync(m => m.FooID == id);
then ordered the myView.subFoos by converting them to a List
myView.subFoos = myView.subFoos.ToList().OrderBy(x => x.OrderBy).ToList();
Only one database call, works nicely.

How does one use .ToList() Inside of a Linq Query?

I've got a class that contains a list item. I would like for a linq query to populate the class, including this list. Here is my query:
var query = from c in context.Cars
select new CarListItem()
{
ID = c.ID,
Make = c.Make,
AvailableColors = context.CarColors.Where(u => u.CarID == c.ID).ToList()
};
Basically, I want to get a list of all of the cars, including a list of the available colors for each respective car.
The problem is that the inclusion of .ToList() within the query results in an error: An error occurred:
LINQ to Entities does not recognize the method 'System.Collections.Generic.List`1[CarSystem.Models.CarColors] ToList[CarColors](System.Collections.Generic.IEnumerable`1[CarSystem.Models.CarColors])' method, and this method cannot be translated into a store expression.
At this point, I don't know whether I am just using wrong syntax within the Linq query (should I use something other than .ToList()?) or if maybe the architecture of the models is wrong.
You can't. EF tries to translate ToList() to SQL and doesn't know how.
You could project to another type, then call ToList():
var query = (from c in context.Cars
select new
{
ID = c.ID,
Make = c.Make,
AvailableColors = context.CarColors.Where(u => u.CarID == c.ID)
}).ToList()
.Select(c => new CarListItem()
{
ID = c.ID,
Make = c.Make,
AvailableColors = c.AvailableColors.ToList()
});
or change the type of CarListItem.AvailableColors to IEnumerable<CarColor>:
var query = from c in context.Cars
select new CarListItem()
{
ID = c.ID,
Make = c.Make,
AvailableColors = context.CarColors.Where(u => u.CarID == c.ID)
};

could not resolve property (complex properties)

I have a asp.net mvc application with NHibernate and I do not know how to resolve a problem to query some data. I have this query:
// create query
var query = session.QueryOVer<Laudo>().Fetch(x => x.Equipament).Eager;
// add some filters
if (idEquipament.HasValue)
query = query.And(x => x.Equipament.Id == idEquipament.Value);
//I got the error here...
if (idCompany.HasValue)
query = query.And(x => x.Equipament.Company.Id == idCompany.Value);
When I try to execute this query, I've got an exception with this message:
"could not resolve property: Equipament.Company.Id of: DomainModel.Laudo"
what can I do to fix this problem?
Thanks
You cannot use another entity property like that. NHibernate expects expression that can be evaluated to property of the current entity. You need to use JoinQueryOver or JoinAlias to join another entity, and perform where after that.
With JoinQueryOver:
// ...
query = query.JoinQueryOver(x => x.Equipment)
.JoinQueryOver(x => x.Company)
.Where(c => c.Id == idCompany.Value);
With JoinAlias:
Equipment equipment = null;
Company company = null;
// ...
query = query.JoinAlias(x => x.Equipment, () => equipment)
.JoinAlias(() => equipment.Company, () => company)
.Where(() => company.Id == idCompany.Value);
Some more info:
What is the difference between JoinQueryOver and JoinAlias?
What can be used as a NHibernate QueryOver alias?
Complex nHibernate QueryOver expression
The tags chosen for your question make me think you didn't want to use QueryOver, but LINQ.
This is achieved by using the extension method Query, in the NHibernate.Linq namespace:
var query = session.Query<Laudo>().Fetch(x => x.Equipament);
if (idEquipament.HasValue)
query = query.Where(x => x.Equipament.Id == idEquipament.Value);
if (idCompany.HasValue)
query = query.Where(x => x.Equipament.Company.Id == idCompany.Value);

How to include related objects in grouped Entity LINQ queries?

I have some Setting entities that are related to a SettingDescription which is related to a SettingGroup.
Setting history is preserved by making a "Modified" field part of the key.
To get the settings matching a specific category I use this query (after help from here):
var latestSettings =
context.Settings.Include("Description.SettingGroup")
.OrderByDescending(x => x.Modified)
.GroupBy(x =>
new {
x.Category,
x.Group,
x.Name,
x.Target }, x => x)
.Where(x => x.Key.Category == category)
.Select(result => result.FirstOrDefault())
.ToArray();
This returns a set of the latest settings, but the "Include" part is completely ignored. However, I can force load the descriptions by running a second dummy query that loads the descriptions into the context.
var latestSettings =
context.Settings.Include("Description.SettingGroup")
.OrderByDescending(x => x.Modified)
.GroupBy(x =>
new {
x.Category,
x.Group,
x.Name,
x.Target }, x => x)
.Where(x => x.Key.Category == category)
.Select(result => result.FirstOrDefault())
.ToArray();
var settingDescriptions =
context.SettingDescriptions.Include("SettingGroup")
.Where(x => x.Category == category)
.ToArray();
Why is the include ignored in the "stand alone" group query?
Can I combine the setting and description loading into a single query?
AlexJ from the EF team posted an excellent series of tips, including:
"Tip 22 - How to make Include really Include"
http://blogs.msdn.com/b/alexj/archive/2009/06/02/tip-22-how-to-make-include-really-include.aspx
It looks to me like your query is returning "Settings" entities (without a "change of shape") so this tip should apply.

Resources