Linq to Sql Group By Class
I would like to pass the groupedList to several functions, however, I am getting strange results when I try to group using a class and not an anonymous type. In the following example, the anonymous type returns 2 results as expected, but the concrete class returns 5 as if it were not even grouped.
Question: is it possible to Linq in Sql Group By with specific classes?
public class person
{
public string Name;
public string State;
}
public class personGroup
{
public string State;
public personGroup(string personState)
{
State = personState;
}
}
void Main()
{
var people = new List<person>();
people.Add(new person {Name = "Bob", State = "Tx"});
people.Add(new person {Name = "Bill", State = "Tx"});
people.Add(new person {Name = "Tracy", State = "Tx"});
people.Add(new person {Name = "Steve", State = "Md"});
people.Add(new person {Name = "Kelly", State = "Md"});
var groupedPeople = people.GroupBy (p => p.State );
groupedPeople.Count().Dump();//Returns 2
var morePeople = people.GroupBy (p => new personGroup(p.State) );
morePeople.Count().Dump();//Returns 5
}
source to share
The method GroupBy
uses EqualityComparer<T>.Default
to compare the elements in question when a custom one is IEqualityComparer
not provided (you did not provide one). It will be based on the implementation IEquatable<T>
of this type T
, if any, and if not, it will be simple to use object.Equals
and object.GetHashCode
the type.
Your own type does not provide any implementation, it relies entirely on the implementations defined in object
, which for a reference type are based on an object reference. Each of the objects you create personGroup
has a different reference, so they are different.
Anonymous types do not use this equality by default; they redefine Equals
and GetHashCode
as dependent on the identity of each of the properties they represent.
If you want to use your own custom type for the group, and the semantics of the default equality - this is not what you want, you need to either provide a custom implementation IEqualityComparer
, or overridden Equals
, and GetHashCode
for the type.
source to share
There are 5 groups in your second group because the key is always different. personGroups
are compared by reference and all objects have different references. You need to override methods Equals
and GetHashCode
in the class personGroup
to compare your instances based on state, or implement IEqualityComparer<personGroup>
and pass it GroupBy
.
source to share
As stated above, Servy states that you need to create a custom IEqualityComparer of type PersonGroup for your example to work.
I've worked a lot with this myself, so I've created a generic EqualityComparer that will work with any model (that uses properties)!
/// <summary>
/// Given two models, return TRUE if all the properties are equal,
/// else return FALSE
/// </summary>
/// <typeparam name="TModel">Type of the models being compared</typeparam>
public class PropertyEqualityComparer<TModel> : IEqualityComparer<TModel>
{
public bool Equals(TModel x, TModel y)
{
PropertyInfo[] props = typeof(TModel).GetProperties();
foreach (PropertyInfo prop in props)
{
if (!Object.Equals(prop.GetValue(x), prop.GetValue(y)))
{
return false;
}
}
return true;
}
public int GetHashCode(TModel obj)
{
int hash = 1;
PropertyInfo[] props = typeof(TModel).GetProperties();
foreach (PropertyInfo prop in props)
{
hash ^= prop.GetValue(obj).GetHashCode();
}
return hash;
}
}
With this class, you can do what you need.
// NOTE: Use properties instead of public variables
public class Person
{
public string Name { get; set; }
public string State { get; set; }
}
// NOTE: Use properties instead of public variables
public class personGroup
{
public string State { get; set; }
}
void Main()
{
var people = new List<Person>();
people.Add(new Person{Name = "Bob", State = "Tx"});
people.Add(new Person{Name = "Bill", State = "Tx"});
var morePeople = people.GroupBy(p => new PersonGroup{State = p.State}, new PropertyEqualityComparer<PersonGroup>());
morePeople.Count();
}
source to share