Handling a Recursive entity property with Entity Framework Core.

Member Groups

As previously discussed in this series, when the Csv file of Members, as generated by the sport’s association, is parsed line by line for member information, the Member_Type property is a comma separated list of the type of member. This can include one or more of Athlete, Coach, Official or other. The app automatically assigns a member to these groups as part of the Csv file parsing for each member based upon what is listed in their Member_Type. These groups are then used for targeted emails. Age based groups are also generated based upon Members’ Date of Birth. For example Under 20 Males is one group. Whilst this was initially done as an on-the-day calculation, it was simplified somewhat because the association defines age groups based only upon the year that members are born. Other ad hoc groups such as Committee require manual assignment. Note that the auto-generated group memberships could be done purely as a Members table query when a group is required. The choice was made to include the generation as part of the Csv file paring. Note that when the file is parsed, member group membership for auto=generated groups are cleared first. That way, for example, a member who is and just an Athlete one year and only an Official the next, is properly updated.

Group Team Leader and Recursion

A recursive issue with Entity Framework arose when it was decided to add a Team Leader to each Team Group where each member of the group is of type Member as is the Team Leader. Having the TeamLeader means that when a message is sent by the organisation to all members in the group, the TeamLeader is addressed in the CC field.

As discussed previously, the group has an IList of Members as a virtual property of the class. As a first iteration one may simply add the TeamLeader property to the Group class where the TeamLeader property is of Member type. As noted elsewhere, this can enable quite complex structures.

    public class MemberGroup
    {
        [Key]
        public int Id { get; set; }
        public string GroupName { get; set; }
        public Member TeamLeader { get; set; }
        public IList<Member> Members { get; set; }
    }

A simpler but related scenario is where with a business, the Employee class has a Manager property which is also an Employee.

public class Employee
{
    [Key]
    publlic int Id {get; set;}
    public string Name {get; set;}
    ...
    public Employee Manager {get; set;}
}

Whilst this will compile with a C# Blazor Entity Framework Core app, you get and a recursion error when the class is called upon.

This recursion is discussed such posts as Recursive Data With Entity Framework Core and SQL Server. Another post is Implementing a recursive projection query in C# and Entity Framework Core. The issue is also discussed on StackOverflow.

The Solution

The solution used here in the Group class is:

  • Add the TeamLeader of type Member to the Group class.
    • Give the TeamLeader property the DataAnnotation NotMapped by the database.
  • Add the TeamLeaderId of type int (where int is the PK type for the Member class) to the Group class.
  • When adding a TeamLeader to a group just set the Group.TeamLeaderId property from the TeamLeader’s Member.Id property
        public int TeamLeaderId { get; set; }
        [NotMapped]
        public Member TeamLeader { get; set; }

The issue then is getting the TeamLeader entity when doing a Group Selection query. One could, in Client code, explicitly do a query from Members using the Group.TeamLeaderId. In this solution this is “buried” in the DataService on the Server using the DbContext when the Groups are queried for:

    async Task<List<MemberGroup>> IDataAccessService.GetGroups()
    {
        var list = await _context.MembetGroups.ToListAsync();
        foreach (var group in list)
        {
            if (group.TeamLeaderId != 0)
            {
                group.TeamLeader = (await _context.Members
                .Where(e => e.Id == group.TeamLeaderId)
                .ToListAsync()).FirstOrDefault();
            }
        }
        return list;
    }

The foreach loop could be replaced by some succinct Linq code as per Michael Cobers article above, but hey, it works! 😃


Further, the TeamLeader property could be configured such that (not done here):

  • The Set just assigns the team leader’s PK Id to TeamLeaderId
  • The Get does a query based upon that TeamLeaderId to Members

This would require a query to the server every time the TeamLeader property is accessed. It is probably better to populate, on the server, the TeamLeader property whenever a group or group lists are requested by the client. This means that when a group or list of groups is requested by the client, the group information is seamlessly complete when received thus hiding this complexity.


Conclusion

An entity can recursively have itself as a self property by implementing some code with the DBContext that when the entity is queried for, the property instance is further queried for. This can aso be used where a class that groups another entity, has an instance of that entity as property, as for a Team Leader of a group. In both cases what is explicitly saved to the database is the entity instance Id (eg Team Leader Id) which is then used to get the entity instance when the high level query is queried for.


 TopicSubtopic
   
 This Category Links 
Category:Blazor Index:Blazor
  Next: > Blazor Helpers App
<  Prev:   Blazor Helpers App Members