Search code examples
c#entity-frameworkef-code-firstcode-first

EF Code first - Detect one-to-many relationships property for given type


We know that we do not need to configure for one-to-many relationships using Data Annotations, one-to-many relationship configured by convention.

In following example ICollection<Student> Students is an relationship property

public class Student
{
    public Student() { }

    public int StudentId { get; set; }
    public string StudentName { get; set; }

    public virtual Standard Standard { get; set; }
}

public class Standard
{
    public Standard()
    {
        Students = new List<Student>();
    }
    public int StandardId { get; set; }
    public string Description { get; set; }

    public virtual ICollection<Student> Students { get; set; }
}

So my question is, how can I detect a relationship propertys for a given type? My goal is to test the value of the property and how many items it contains

Something like that:

static void Test(object givenInstanse)
{
    foreach (PropertyInfo p in TryGetOneToManyRelationshipsPropertys(typeof(givenInstanse), dc))
    {
        var val = (ICollection)p.GetValue(givenInstanse);
        Console.WriteLine(val.Count);
    }
}

static IEnumerable<PropertyInfo> TryGetOneToManyRelationshipsPropertys(Type t, DbContext dc)
{
    // ...
}

Solution

  • In its simplest form (Not taking into considerations any custom attributes or custom Mappings). You can do this:

    IEnumerable<PropertyInfo> GetOneToManyRelationships<T>()
    {
        var collectionProps = from p in typeof(T).GetProperties()
                              where p.PropertyType.IsGenericType
                                    && p.PropertyType.GetGenericTypeDefinition() 
                                                       == typeof(ICollection<>)
                              select p;
    
        foreach (var prop in collectionProps)
        {
            var type = prop.PropertyType.GetGenericArguments().First(); 
    
            // This checks if the other type has a One Property of this Type.
            bool HasOneProperty = type.GetProperties().Any(x => x.PropertyType == typeof(T));
    
            if(!HasOneProperty)
            {
                string pkName = typeof(T).Name + "Id";
    
                HasOneProperty = type.GetProperties().Any(x => x.Name.Equals(pkName, 
                                                          StringComparison.OrdinalIgnoreCase));
            }
    
            if (HasOneProperty)
            {
                yield return prop;
            }
        }
    }
    

    Usage:

    var oneToManyProps = GetOneToManyRelationships<Standard>();
    
    foreach(var prop in oneToManyProps)
    {
        Console.WriteLine(prop.Name);
    }
    

    Output:

    Students
    

    You can extend this to check for Attributes tagged on properties, but i'll leave that to you as it is outside the scope of your question.