GroupBy with a known class as the grouping key [duplicate] - c#

This question already has an answer here:
Why does using anonymous type work and using an explicit type not in a GroupBy?
(1 answer)
Closed 7 years ago.
We wanted to GroupBy with a known class as the grouping key. It doesn't seem to work - LINQ appears to need to group with an anonymous class.
Is it possible to use GroupBy with a known class as the grouping key?
If not, why not?
If so, how?
This question is different than Why does using anonymous type work and using an explicit type not in a GroupBy? because it asks how to use GroupBy with a known class.
Here is a fiddle that shows what works and what fails. The one that fails doesn't create a group.
public static void Works(EnumerableRowCollection<DataRow> dataRows)
{
var grouped = dataRows.GroupBy(
row => new {
Name = row["Name"]
},
(k, g) => new {
Key = k,
Group = g.CopyToDataTable()
});
}
public static void Fails(EnumerableRowCollection<DataRow> dataRows)
{
var grouped = dataRows.GroupBy(
row => new GroupingKey {
Name = row["Name"]
},
(k, g) => new {
Key = k,
Group = g.CopyToDataTable()
});
}

The difference is in the way that comparison is performed on anonymous types vs. concrete types.
According to the GroupBy documentation:
The default equality comparer Default is used to compare keys.
For anonymous types, the default equality comparer uses a property-by-property comparison, so any two anonymous types with the same members and values will be considered equivalent.
For concrete types, the default equality comparer uses reference comparison, so any two instances of the object are always considered inequivalent, regardless of their values.
To make GroupBy work with your GroupingKey class, you can either use the overload that takes an IEqualityComparer<T> to define comparisons, or override Object.Equals in GroupingKey to perform the desired comparison.

Related

c# distinct doesn't work. what i'm doing wrong?

I'm developing an asp.net core application.
I have a code for getting property values.
var properties = _context.Properties.Select(p => new {
p.Name,
Values = p.Values.Distinct()
}).Distinct();
But Distinct() doesn't work. What I'm doing wrong?
The problem is that the second Distinct does not know how to compare the items. Probably you mean distinct by name, instead it does distinct by all the properties.
Instead of creating an anonymous type, create a named one (i.e. Property). Then declare a IEqualityComparer<T> for this type:
class PropertyNameComparer : IEqualityComparer<Property>
{
public bool Equals(Property x, Property y) => x.Name.Equals(y.Name);
public int GetHashCode(Property p) => p.Name.GetHashCode();
}
(for sake of simplicity, I'm not handling nulls here.)
var properties = _context.Properties.Select(p => new Property {
Name = p.Name,
Values = p.Values.Distinct()
})
.AsEnumerable()
.Distinct(new PropertyNameComparer());
Note the .AsEnumerable() to separate the query from the second Distinct to make it LINQ-to-Objects, because EF cannot convert the IEqualityComparer<T> to SQL.
But the real question is, why are you getting duplicate properties in the first place? And if you do, what do you want to happen to the values of the duplicates? Will they contain the same values or different values? My implementation just takes the first property with its values and ignores the values of the duplicates. Instead, you might want to group by name and to union the values. But that's not clear from your question.

Need help understanding .Select method C#

I am having difficulties understandting what type of statement this is and how to use the .select method.
var lines = System.IO.File.ReadLines(#"c:\temp\mycsvfil3.csv")
.Select(l => new
{
myIdentiafication= int.Parse(l.Split(',')[0].Trim()),
myName= l.Split(',')[1].Trim()
}
).OrderBy(i => i.Id);
any help is appreciated!
The Enumerable.Select method is an extension method for an IEnumerable<T> type. It takes a Func<TSource, TResult> that allows you to take in your IEnumerable<T> items and project them to something else, such as a property of the type, or a new type. It makes heavy use of generic type inference from the compiler to do this without <> everywhere.
In your example, the IEnumerable<T> is the string[] of lines from the file. The Select func creates an anonymous type (also making use of generic type inference) and assigns some properties based on splitting each line l, which is a string from your enumerable.
OrderBy is another IEnumerable<T> extension method and proceeds to return an IEnumerable<T> in the order based on the expression you provide.
T at this point is the anonymous type from the Select with two properties (myIdentiafication and myName), so the OrderBy(i => i.Id) bit won't compile. It can be fixed:
.OrderBy(i => i.myIdentiafication);
This is a LINQ query. Enumerable.Select projects each line from file into anonymous object with properties myIdentiafication and myName. Then you sort sequence of anonymous objects with Enumerable.OrderBy. But you should select property which exists in anonymous object. E.g. myIdentiafication because there is no id property:
var lines = File.ReadLines(#"c:\temp\mycsvfil3.csv") // get sequence of lines
.Select(l => new {
myIdentiafication = int.Parse(l.Split(',')[0].Trim()),
myName= l.Split(',')[1].Trim()
}).OrderBy(i => i.myIdentiafication);
NOTE: To avoid parsing each line twice, you can use query syntax with introducing new range variables:
var lines = from l in File.ReadLines(#"c:\temp\mycsvfil3.csv")
let pair = l.Split(',')
let id = Int32.Parse(pair[0].Trim())
orderby id
select new {
Id = id,
Name = pair[1].Trim()
};
From each string returned by ReadLines create an anonymous object with two properties (myIdentiaficiation and myName). Within the Select the context variable l represents a single line from the set returned by ReadLines.

Union Multiple Classes, Sort on Common Interface

This question is chiefly about LINQ and possibly covariance.
Two of my Entities implement the IDatedItem interface. I'd like to union, then sort these, for enumerating as a single list. I must retain entity-specific properties at enumeration-time.
To clarify by example, one approach I tried was:
Context.Table1.Cast<IDatedItem>().
Union(Context.Table2.Cast<IDatedItem>()).
SortBy(i => i.Date).
ForEach(u => CustomRenderSelector(u, u is Table1));
In trying to do this various ways, I've run into various errors.
LINQ to Entities only supports casting EDM primitive or enumeration types.
Unable to process the type '.IDatedItem[]', no known mapping to the value layer
Unable to create a constant value of type 'IDatedItem'. Only primitive types
etc.
The bigger picture:
The IDatedItem interface shown here is a simplification of the actual shared properties.
In practice, the tables are filtered before the union.
The entity-specific properties will be rendered, in order, in a web page.
In a parallel feature, they will be serialized to a JSON result hierarchy.
I'd like to be able to perform LINQ aggregate operations on the results as well.
This requires more space than a comment offers. On the other hand, this is not really an answer, because there is no satisfying answer, really.
For a Union to succeed, both collections must have the same type (or have intrinsic conversions to common types, that's what covariance is about).
So a first go at getting a correct Union could be:
Context.Table1.Select(t1 => new {
A = t1.PropA,
B = t1.PropB,
Date = t1.Date
})
.Union(
Context.Table1.Select(t2 => new {
A = t2.PropC,
B = t2.PropD,
Date = t2.Date
}))
.OrderBy(x => x.Date)
.ToList();
which projects both tables to the same anonymous type. Unfortunately, because of the anonymous type, you can't do .Cast<IDatedItem>().
Therefore, the only way to get a List<IDatedItem> is to define a type that implements IDatedItem and project both tables to that type:
Context.Table1.Select(t1 => new DateItem {
A = t1.PropA,
B = t1.PropB,
Date = t1.Date
})
.Union(
Context.Table1.Select(t2 => new DateItem {
A = t2.PropC,
B = t2.PropD,
Date = t2.Date
}))
.OrderBy(item => item.Date)
.AsEnumerable()
.Cast<IDatedItem>()
Which (I think) is quite elaborate. But as long as EF doesn't support casting to interfaces in linq queries it's the way to go.
By the way, contrary to what I said in my comment, the sorting will be done in SQL. And you can use subsequent aggregate functions on the result.
Here's the working code. My solution was to ensure all data was local, to keep LINQ-to-EF from trying to do all manner of things it knows it can't that were causing many unclear errors. Then a simple type declaration on the generic Union can take hold.
That means that, aside from the annoyances of LINQ-to-EF, the main issue here is really a duplicate of LINQ Union different types - dynamically casting to an interface? .
public virtual ActionResult Index() {
return View(StatusBoard().OrderBy(s => s.Status));
}
private IEnumerable<DefensiveSituationBoardMember> StatusBoard() {
DateTime now = DateTime.UtcNow;
DateTime historicalCutoff = now.AddDays(-1);
IEnumerable<Member> activeMembers = Context.Members.Where(n => !n.Disabled).ToList();
// IncomingAttack and Reinforcements both implement IDefensiveActivity
IEnumerable<IncomingAttack> harm = Context.IncomingAttacks.Where(n => n.IsOngoingThreat || n.ArrivalOn > historicalCutoff).ToList();
IEnumerable<Reinforcements> help = Context.Reinforcements.Where(n => !n.Returned.HasValue || n.Returned > historicalCutoff).ToList();
// Here's the relevant fix
IEnumerable<IDefensiveActivity> visibleActivity = help.Union<IDefensiveActivity>(harm);
return from member in activeMembers
join harmEntry in harm on member equals harmEntry.DestinationMember into harmGroup
join activityEntry in visibleActivity on member equals activityEntry.DestinationMember into activityGroup
select new DefensiveSituationBoardMember {
Member = member,
Posture = harmGroup.Max(i => (DefensivePostures?)i.Posture),
Activity = activityGroup.OrderBy(a => a.ArrivalOn)
};
}

anonymous types, when is it useful?

How could this anonymous type be useful in real life situation? Why is it good to be anoynimous?
// sampleObject is an instance of a simple anonymous type.
var sampleObject =
new { FirstProperty = "A", SecondProperty = "B" };
From MSDN Anonymous Types (C# Programming Guide):
Anonymous types typically are used in the select clause of a query
expression to return a subset of the properties from each object in
the source sequence.
...
The most common scenario is to initialize an anonymous type with
properties from another type.
For extra info, you may read Anonymous Types in Query Expressions.
Also, consider reading SO What is the purpose of anonymous types?
Consider an example from How to: Join by Using Composite Keys (C# Programming Guide):
var query = from o in db.Orders
from p in db.Products
join d in db.OrderDetails
on new { o.OrderID, p.ProductID }
equals new { d.OrderID, d.ProductID }
into details
from d in details
select new { o.OrderID, p.ProductID, d.UnitPrice };
This example shows how to perform join operations in which you want to
use more than one key to define a match. This is accomplished by using
a composite key. You create a composite key as an anonymous type or
named typed with the values that you want to compare.
And an example of using anonymous types for grouping dara to encapsulate a key that contains multiple values from Group by Multiple Columns using Anonymous Types in LINQ to SQL:
var months = from t in db.TransactionData
group t by new { month = t.Date.Month, year = t.Date.Year }
into d
select new { t.Key.month, t.Key.year };
or beter How to: Group Query Results (C# Programming Guide):
An anonymous type is used because it is not necessary to use the
complete object to display the results
Note that the properties in the anonymous type become properties on
the Key member and can be accessed by name when the query is executed.
Typically, when you use an anonymous type to initialize a variable, you declare the variable as an implicitly typed local variable by using var. The type name cannot be specified in the variable declaration because only the compiler has access to the underlying name of the anonymous type. For more information about var, see Implicitly Typed Local Variables (C# Programming Guide).

Group by a distinct list of integers

MyObject()
{
String dept;
List<int> id;
Object obj;
}
Using LINQ, how can I return a list of the above objects organized as follows:
Group all of the obj objects by [ department and EQUAL id list ]. The list being considered equal if it contains the same numbers, not necessarily the same order(a set).
GroupBy has an overload that accepts a custom IEqualityComparer<MyObject>. Write one that regards two objects as equal when dept is equal and id is set-equal, and pass it as an argument.
A convenient way to implement set equality is to write
new HashSet(x.id).SetEquals(new HashSet(y.id))
although this will end up being inefficient and probably not the best idea if there are lots of comparisons to make.
Building off of Jon's answer, if efficiency is an issue, you can store the HashSet for each object in an anonymous object:
myObjects.Select(x => new { myObject = x, hashSet = new HashSet(x.id) })
.GroupBy(x => x.hashSet, HashSet<int>.CreateSetComparer())
.SelectMany(x => x.GroupBy(y => y.myObject.dept))
If you want to perform only one GroupBy you could store the HashSet in a Tuple or custom class, but then you would have to create your own IEqualityComparer.

Categories

Resources