LINQ GroupBy on single property - linq

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

Related

Select distinct from property which is List<string>

I have a generic list of assets (List<Asset>) and one of the assets properties is called Tags which is a list of strings
How would I do a Linq query to get a distinct list of tags. I tried
assetList.Select(a => a.Tags).Distinct()
but this returns me an IEnumerable<List<string>> instead of an IEnumerable<string>
You was close. You need to use Enumerable.SelectMany to select all tags and flatten them into one sequence:
assetList.SelectMany(a => a.Tags).Distinct()
assetList.SelecMany(a => a.Tags).Distinct() . correct it
more information about differences Difference Between Select and SelectMany
Select only takes objects as they are and Tags is a list, so it takes lists. If you need items from these lists, you have to flatten them into one list and then proceed with other operations.
assetList.SelectMany(a => a.Tags).Distinct();
A nice example from MSDN on SelectMany
PetOwner[] petOwners =
{ new PetOwner { Name="Higa, Sidney",
Pets = new List<string>{ "Scruffy", "Sam" } },
new PetOwner { Name="Ashkenazi, Ronen",
Pets = new List<string>{ "Walker", "Sugar" } },
new PetOwner { Name="Price, Vernette",
Pets = new List<string>{ "Scratches", "Diesel" } } };
IEnumerable<string> query1 = petOwners.SelectMany(petOwner => petOwner.Pets);
produces the following list
Scruffy, Sam, Walker, Sugar, Scratches, Diesel

Complex foreach loop possible to shorten to linq?

I have a cluttery piece of code that I would like to shorten using Linq. It's about the part in the foreach() loop that performs an additional grouping on the result set and builds a nested Dictionary.
Is this possible using a shorter Linq syntax?
var q = from entity in this.Context.Entities
join text in this.Context.Texts on new { ObjectType = 1, ObjectId = entity.EntityId} equals new { ObjectType = text.ObjectType, ObjectId = text.ObjectId}
into texts
select new {entity, texts};
foreach (var result in q)
{
//Can this grouping be performed in the LINQ query above?
var grouped = from tx in result.texts
group tx by tx.Language
into langGroup
select new
{
langGroup.Key,
langGroup
};
//End grouping
var byLanguage = grouped.ToDictionary(x => x.Key, x => x.langGroup.ToDictionary(y => y.PropertyName, y => y.Text));
result.f.Apply(x => x.Texts = byLanguage);
}
return q.Select(x => x.entity);
Sideinfo:
What basically happens is that "texts" for every language and for every property for a certain objecttype (in this case hardcoded 1) are selected and grouped by language. A dictionary of dictionaries is created for every language and then for every property.
Entities have a property called Texts (the dictionary of dictionaries). Apply is a custom extension method which looks like this:
public static T Apply<T>(this T subject, Action<T> action)
{
action(subject);
return subject;
}
isn't this far simpler?
foreach(var entity in Context.Entities)
{
// Create the result dictionary.
entity.Texts = new Dictionary<Language,Dictionary<PropertyName,Text>>();
// loop through each text we want to classify
foreach(var text in Context.Texts.Where(t => t.ObjectType == 1
&& t.ObjectId == entity.ObjectId))
{
var language = text.Language;
var property = text.PropertyName;
// Create the sub-level dictionary, if required
if (!entity.Texts.ContainsKey(language))
entity.Texts[language] = new Dictionary<PropertyName,Text>();
entity.Texts[language][property] = text;
}
}
Sometimes good old foreach loops do the job much better.
Language, PropertyName and Text have no type in your code, so I named my types after the names...

LINQ (Dynamic): OrderBy within a GroupBy using dynamic linq?

I had the following query using normal linq and it was working great (using anonymous type),
var result = from s in Items
group s by s.StartTime into groupedItems
select new {groupedItems.Key, Items= groupedItems.OrderBy(x => x.Name) };
But using Dynamic Linq I cannot get it to order by within the groupby.
result = Items.GroupBy("StartTime", "it").OrderBy("Name");
It states the Name isn't available. It is worth noting that if I take my OrderBy off, everything works great but items inside each "Key" are not ordered.
This is a good question!
I simulated your situation by creating a class called Item.
public class Item
{
public DateTime StartTime { get; set; }
public string Name { get; set; }
}
and then created a basic list of items to do the groupby.
List<Item> Items = new List<Item>()
{
new Item() { StartTime = DateTime.Today, Name = "item2"},
new Item() { StartTime = DateTime.Today, Name = "item1"},
new Item() { StartTime = DateTime.Today.AddDays(-1), Name = "item3"},
};
Now the big difference in the 2 queries is where the order by is being performed. In the first query, when you perform groupedItems.OrderBy(x => x.Name) its being performed on a IGrouping<DateTime,Item> or a single entry as it iterates through all the groupings.
In the second query, the orderby is being performed after the fact. This means you're doing an orderby on a IEnumerable<IGrouping<DateTime,Item>> because the iterations have already happened.
Since Microsoft was nice they added something to help deal with this for expressions. This overload allows you to specify the item returned as it iterates through the collection. Here's an example of the code:
var expressionResult = Items.GroupBy(x => x.StartTime,
(key, grpItems) => new { key, Items = grpItems.OrderBy(y => y.Name) });
The second part of the GroupBy you can specify a lambda expression that takes a key and a grouping of items under that key and return an entry that you specify, which is the same as you're doing in the original query.
Hope this helps!

linq to sql batch delete

I have the following DB:
Posts which have an Id, Tags also with Id, and TagsToPosts table which have TagsToPosts.PostId => Posts.Id and TagsToPosts.TagId => Tags.Id FK relations.
I need to delete multiple items from TagsToPosts in following way.
I'm creating IList<Tag> newTags by parsing a string. Each tag have it's name. I want to delete all TagsToPosts items pointing to single post (TagsToPosts.PostId == mypostid) and which points to Tag with name which not in my newTags.
For instance I have one post with Id = 1, three tags: 1 => "tag1", 2 => "tag2", 3 => "tag3" And ManyToMany relations table TagsToPosts: 1 => 1, 1 => 2, 1 => 3
So all three tags are linked to my post.
After that I'll create a new IList<Tag> newList = new List<Tag>() by parsing a string. newList contains: 0 => "tag1", 0 => "tag2".
Now I want to remove third relation from table TagsToPosts because my new list of tags doesn't contain tag with name "tag3". So I need to find a difference. I know I can find similar items using JOIN but how to find difference?
I want this to happend in one DB query without iterating over each item to delete it.
You can't do this with LINQ-to-SQL.
LINQ-to-SQL is not good for batch operations - it can't do batch inserts, it can't do batch updates, and it can't do batch deletes. Every object in your collection is treated individually. You can do all the operations in one transaction, but there will always be a query for each record.
MSDN
A better option is to write a stored procedure that will do what you want.
Have you looked at the Linq Except operator?
For example:
var toDelete = (from t in TagsToPost
select t).Except(from nt in newList
select nt, new TagComparer());
class TagComparer: IEqualityComparer<TagsToPosts>
{
public bool Equals(TagsToPosts x, TagsToPosts y)
{
return x.Tag.Equals(y.Tag, CompareOptions.Ordinal);
}
}
PLINQO supports batch delete operations without retrieving the entities first.
var delete = from t in TagsToPost
select t).Except(from nt in newList
select nt, new TagComparer())
context.Tags.Delete(delete);
http://plinqo.com
My solution which lets you make deletions determined by a class field:
public static void DeleteByPropertyList<T, R>(List<T> listToDelete, Expression<Func<T, R>> getField, DataContext context) where T : class {
List<List<string>> partitionedDeletes = listToDelete.Select(d => string.Format("'{0}'", getField.Compile()(d).ToString())).ToList().Partition<string>(2000).ToList();
Func<Expression<Func<T, R>>, string> GetFieldName = propertyLambda => ((MemberExpression)propertyLambda.Body).Member.Name;
MetaTable metaTable = context.Mapping.GetTable(typeof(T));
string tableName = string.Format("{0}.{1}", metaTable.Model.DatabaseName, metaTable.TableName);
foreach (List<string> partitionDelete in partitionedDeletes) {
string statement = "delete from {0} where {1} in ({2})";
statement = string.Format(statement, tableName, GetFieldName(getField), string.Join(",", partitionDelete));
context.ExecuteCommand(statement);
}
}
public static IEnumerable<List<T>> Partition<T>(this IList<T> source, int size) {
for (int i = 0; i < Math.Ceiling(source.Count / (double)size); i++)
yield return new List<T>(source.Skip(size * i).Take(size));
}
Usage:
List<OrderItem> deletions = new List<OrderItem>();
// populate deletions
LinqToSqlHelper.DeleteByPropertyList<OrderItem, long>(deletions, oi => oi.OrderItemId, context);
It only works with a single field, but it could be extended to composite fields easily enough.

Multiple "order by" in LINQ

I have two tables, movies and categories, and I want to get an ordered list by categoryID first and then by Name.
The movie table has three columns ID, Name and CategoryID.
The category table has two columns ID and Name.
I tried something like the following, but it didn't work.
var movies = _db.Movies.OrderBy( m => { m.CategoryID, m.Name })
This should work for you:
var movies = _db.Movies.OrderBy(c => c.Category).ThenBy(n => n.Name)
Using non-lambda, query-syntax LINQ, you can do this:
var movies = from row in _db.Movies
orderby row.Category, row.Name
select row;
[EDIT to address comment] To control the sort order, use the keywords ascending (which is the default and therefore not particularly useful) or descending, like so:
var movies = from row in _db.Movies
orderby row.Category descending, row.Name
select row;
Add "new":
var movies = _db.Movies.OrderBy( m => new { m.CategoryID, m.Name })
That works on my box. It does return something that can be used to sort. It returns an object with two values.
Similar, but different to sorting by a combined column, as follows.
var movies = _db.Movies.OrderBy( m => (m.CategoryID.ToString() + m.Name))
Use the following line on your DataContext to log the SQL activity on the DataContext to the console - then you can see exactly what your LINQ statements are requesting from the database:
_db.Log = Console.Out
The following LINQ statements:
var movies = from row in _db.Movies
orderby row.CategoryID, row.Name
select row;
AND
var movies = _db.Movies.OrderBy(m => m.CategoryID).ThenBy(m => m.Name);
produce the following SQL:
SELECT [t0].ID, [t0].[Name], [t0].CategoryID
FROM [dbo].[Movies] as [t0]
ORDER BY [t0].CategoryID, [t0].[Name]
Whereas, repeating an OrderBy in LINQ, appears to reverse the resulting SQL output:
var movies = from row in _db.Movies
orderby row.CategoryID
orderby row.Name
select row;
AND
var movies = _db.Movies.OrderBy(m => m.CategoryID).OrderBy(m => m.Name);
produce the following SQL (Name and CategoryId are switched):
SELECT [t0].ID, [t0].[Name], [t0].CategoryID
FROM [dbo].[Movies] as [t0]
ORDER BY [t0].[Name], [t0].CategoryID
I have created some extension methods (below) so you don't have to worry if an IQueryable is already ordered or not. If you want to order by multiple properties just do it as follows:
// We do not have to care if the queryable is already sorted or not.
// The order of the Smart* calls defines the order priority
queryable.SmartOrderBy(i => i.Property1).SmartOrderByDescending(i => i.Property2);
This is especially helpful if you create the ordering dynamically, f.e. from a list of properties to sort.
public static class IQueryableExtension
{
public static bool IsOrdered<T>(this IQueryable<T> queryable) {
if(queryable == null) {
throw new ArgumentNullException("queryable");
}
return queryable.Expression.Type == typeof(IOrderedQueryable<T>);
}
public static IQueryable<T> SmartOrderBy<T, TKey>(this IQueryable<T> queryable, Expression<Func<T, TKey>> keySelector) {
if(queryable.IsOrdered()) {
var orderedQuery = queryable as IOrderedQueryable<T>;
return orderedQuery.ThenBy(keySelector);
} else {
return queryable.OrderBy(keySelector);
}
}
public static IQueryable<T> SmartOrderByDescending<T, TKey>(this IQueryable<T> queryable, Expression<Func<T, TKey>> keySelector) {
if(queryable.IsOrdered()) {
var orderedQuery = queryable as IOrderedQueryable<T>;
return orderedQuery.ThenByDescending(keySelector);
} else {
return queryable.OrderByDescending(keySelector);
}
}
}
There is at least one more way to do this using LINQ, although not the easiest.
You can do it by using the OrberBy() method that uses an IComparer. First you need to
implement an IComparer for the Movie class like this:
public class MovieComparer : IComparer<Movie>
{
public int Compare(Movie x, Movie y)
{
if (x.CategoryId == y.CategoryId)
{
return x.Name.CompareTo(y.Name);
}
else
{
return x.CategoryId.CompareTo(y.CategoryId);
}
}
}
Then you can order the movies with the following syntax:
var movies = _db.Movies.OrderBy(item => item, new MovieComparer());
If you need to switch the ordering to descending for one of the items just switch the x and y inside the Compare()
method of the MovieComparer accordingly.
If use generic repository
> lstModule = _ModuleRepository.GetAll().OrderBy(x => new { x.Level,
> x.Rank}).ToList();
else
> _db.Module.Where(x=> ......).OrderBy(x => new { x.Level, x.Rank}).ToList();

Resources