Search code examples
c#navigation-propertiesef-core-2.2

EF Core get Navigation Properties of an entity from Model with multiplicity Zero or One


I am creating a generic class to seed the database with Entities, needed for integration tests. I can create individual entities but when one depends from another, I have to manually instruct my class first create the parent entity and then proceed. I am trying to make this detection automatic, getting from the Model definition, the list of navigation properties with multiplicity 0 or 1 (Reference navigation properties), once that is done, recursively my class will invoke itself to create parent entities first (Circular dependencies are out of scope here).

I used to do this in EF for .net Framework, but EF Core has changed a lot. What I am missing in EF Core is the RelationshipMultiplicity, I cannot find any reference to Multiplicity in the official documentation, and even tough the hacky solution is to check if the navigation property is a collection, I would like to have more control and keeps things simple.

So far I am exploring the Model definition using:

var modelData = _context.Model.GetEntityTypes()
    .Select(t => new
    {
        t.ClrType.Name,
        DerivedNavigationProperties = t.FindDerivedNavigations(t.ClrType.Name),
        DefiningNavigationProperties = t.FindDefiningNavigation(),
        DeclaredForeignKeys = t.GetDeclaredForeignKeys(),
        DeclaredNavigations = t.GetDeclaredNavigations(),
        DerivedNavigations = t.GetDerivedNavigations(),
        DerivedNavigationsInclusive = t.GetDerivedNavigationsInclusive(),
        Navigations = t.GetNavigations() // This returns all Navigation Properties (INavigation)
    });

Solution

  • After inspecting the source code in GitHub, I can say with enough confidence that there is no such thing as Multiplicity in EF Core.

    I created an enumeration like the one used .net Framework 3.5+ (See: Official documentation):

    public enum RelationshipMultiplicity
    {
        Many = 2,
        One = 1,
        ZeroOrOne = 0
    }
    

    And then an extension method which allows getting all navigation properties using the enum as a filter. The key things I used are:

    The method allows to get all Navigations properties by relation type

    public static class ModelExtensions
    {
        /// <summary>
        /// Extension method used to get from the entity all navigation properties by multiplicity
        /// </summary>
        /// <typeparam name="T">Entity from where the navigation properties are taken</typeparam>
        /// <param name="model">Context Model</param>
        /// <param name="multiplicity">Type of multiplicity to use</param>
        /// <returns>List of PropertyInfo of Navigation Properties</returns>
        public static IEnumerable<PropertyInfo> GetNavigationProperties<T>(this IModel model, RelationshipMultiplicity multiplicity)
        {
            var navigations = model.GetEntityTypes().FirstOrDefault(m => m.ClrType == typeof(T))?.GetNavigations();
            var properties = new List<PropertyInfo>();
    
            switch (multiplicity)
            {
                case RelationshipMultiplicity.Many | RelationshipMultiplicity.ZeroOrOne:
                    return navigations?
                        .Select(nav => nav.PropertyInfo);
                case RelationshipMultiplicity.Many:
                    return navigations?
                        .Where(nav => nav.IsCollection())
                        .Select(nav => nav.PropertyInfo);
                case RelationshipMultiplicity.One:
                    return navigations?
                        .Where(nav => !nav.IsCollection() && nav.ForeignKey.IsRequired)
                        .Select(nav => nav.PropertyInfo);
                case RelationshipMultiplicity.ZeroOrOne:
                    return navigations?
                        .Where(nav => !nav.IsCollection())
                        .Select(nav => nav.PropertyInfo);
                default:
                    return null;
            }
    
            return properties;
        }
    }
    

    Usage example:

    var oneToManyRelations = _context.Model.GetNavigationProperties<Transaction>(
        RelationshipMultiplicity.ZeroOrOne);
    
    var manyToOneRelations = _context.Model.GetNavigationProperties<Transaction>(
        RelationshipMultiplicity.Many);
    
    var allRelations = _context.Model.GetNavigationProperties<Transaction>(
        RelationshipMultiplicity.Many | 
        RelationshipMultiplicity.ZeroOrOne);