From flat list of entities to list of groups

Viewed 74

I have an entity like this:

class MyEntity {
     public int Id{ get; set; }
     public string Type { get; set; }
     public EventData Data { get; set; }
     // other stuff...
}

where EventData is something like:

class EventData {
    public string GroupId { get; set; }
    public DateTime CreationDate { get; set; }
    public DateTime? TerminationDate { get; set; }
    // other stuff...
}

when "GroupId" is null or empty the entity is not part of a group. Entities with the same "GroupId" are part of the same group.

What i need to achieve is a list of groups in which the key is a complex type (anonymous or not... i don't know) and the corresponding value is the list of entities.

Each group key must be a complex type because i will use it to sort the groups according to one of its properties.

For example the key will be:

class MyKey {
    public bool IsGroup { get; set; }
    public string GroupId { get; set; }
    public DateTime CreationDate { get; set; }
}

Indeed, if "IsGroup" is false (and GroupId is null), the corresponding value will be a list containing only one entity.

The object should be something like:

List<IGrouping<Mykey, List<MyEntity>> ...

Thank you for your help. I hope it's clear enough :)

INPUT EXAMPLE

List<MyEntity> entities = new List<MyEntity>() 
{
    new MyEntity {
        Id = 1,
        Data = new EventData {
            GroupId = null
        }
    },
    new MyEntity {
        Id = 2,
        Data = new EventData {
            GroupId = null
        }
    },
    new MyEntity {
        Id = 3,
        Data = new EventData {
            GroupId = "1"
        }
    },
    new MyEntity {
        Id = 4,
        Data = new EventData {
            GroupId = "1"
        }
    }
}

Starting from this simplyfied input example, the output should be a list with 3 objects:

  1. Key with IsGroup=false and GroupId=null. The value is a list containing only the entity with id=1.
  2. Key with IsGroup=false and GroupId=null. The value is a list containing only the entity with id=2.
  3. Key with IsGroup=true and GroupId="1". The value is a list containing the entities with id=3 and id=4.
2 Answers

One way to do this is to override the Equals and GetHashCode methods of the MyKey class so that we can use it to GroupBy the list of entities (without doing this, the default "reference-equals" equality will be used and we'll get no matches when GroupBy compares two keys for equality).

Given the requirements that two groups are only equal if IsGroup is true, they have the same GroupId, and the same CreationDate, we can implement Equals like:

class MyKey : IEquatable<MyKey>
{
    public bool IsGroup { get; set; }
    public string GroupId { get; set; }
    public DateTime CreationDate { get; set; }

    public bool Equals(MyKey other)
    {
        // Note that we use 'other.IsGroup' to ensure that non-groups are not 
        // grouped together. Otherwise we would use 'other.IsGroup == IsGroup'.
        return other != null &&
               other.IsGroup &&
               other.GroupId == GroupId &&
               other.CreationDate == CreationDate;
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as MyKey);
    }

    public override int GetHashCode()
    {
        return IsGroup.GetHashCode() * 17 + 
               (GroupId?.GetHashCode() ?? 1) * 17 +
               CreationDate.GetHashCode();
    }
}

With that done, we can now use the MyKey class to group the list of entities:

List<IGrouping<MyKey, MyEntity>> groups = entities.GroupBy(e =>
    new MyKey
    {
        CreationDate = e.Data?.CreationDate ?? DateTime.MinValue,
        GroupId = e.Data?.GroupId,
        IsGroup = !string.IsNullOrEmpty(e.Data?.GroupId)
    })
    .ToList();

From what I see you don't need this redundant IsGroup property. The grouping key is either Entity.Id when EventData.GroupId is null, or else EventData.GroupId:

var grouped = entities.GroupBy(e => new
{
    EntityKey = e.Data.GroupId == null ? e.Id : 0,
    DataKey = e.Data.GroupId
});

This creates three groups having the keys...

EntityKey   DataKey
1           null
2           null
0           1

...and having items [entity 1], [entity 2], and [entity3, entity4], respectively

Related