mardi 18 janvier 2022

How to determine that a class implements a specific interface with self-referencing, generic constraints?

I have a DbContext-derived class and want to get a list of all DbSet<Entity> properties whose generic argument Entity matches the following criteria:

  • Entity must not inherit from a class.
  • Entity must implement the interface IEntity.
  • Entity must implement the interface IEntity<TEntity> where TEntity has the following constraints:

where TEntity : class, IEntity, IEntity<TEntity>, new()

The second-last line in the code below attempts to do that but fails. Furthermore, I do not know how to express constraint checks at runtime.

The property DbSet<Employee> should pass the check whereas DbSet<Department> should not.

namespace DomainBuilder
{
    internal static class Program
    {
        private static void Main (string [] args)
        {
            var dbSetProperties = typeof (DomainBuilder.DomainBuilderContext)
                .GetProperties (BindingFlags.Instance | BindingFlags.Public)
                .Where (p => p.CanRead && p.CanWrite)
                .Where (p => p.GetGetMethod (nonPublic: false) != null)
                .Where (p => p.GetSetMethod (nonPublic: false) != null)
                .Where (p => (p.PropertyType.Name.StartsWith (nameof (DbSet<DomainBuilder.IEntity>))))
                .Where (p => (p.PropertyType.IsGenericType))
                .Where (p => (p.PropertyType.GenericTypeArguments.Length == 1))
                .ToList();

            foreach (var dbSetProperty in dbSetProperties)
            {
                Console.WriteLine ($@"DbSet<{dbSetProperty.PropertyType.GenericTypeArguments [0].Name}>: {DomainBuilder.DomainBuilderContext.ImplementsIEntityGenericWithConstraints (dbSetProperty)}.");
            }
        }
    }

    public interface IEntity
    {
        public long Id { get; set; }
        public DateTime DateTimeCreated { get; set; }
        public DateTime DateTimeModified { get; set; }
    }

    public partial interface IEntity<TEntity>: IEntity
        where TEntity : class, IEntity, IEntity<TEntity>, new()
    { }

    public partial class Employee: IEntity<Employee>
    {
        public long Id { get; set; }
        public DateTime DateTimeCreated { get; set; }
        public DateTime DateTimeModified { get; set; }

        public long? DepartmentId { get; set; }
        public Department? Department { get; set; }
    }

    public partial class Department: IEntity
    {
        public long Id { get; set; }
        public DateTime DateTimeCreated { get; set; }
        public DateTime DateTimeModified { get; set; }

        public string Name { get; set; } = "";
    }

    public partial class DomainBuilderContext: DbContext
    {
        public virtual DbSet<Employee>? Employees { get; set; }
        public virtual DbSet<Department>? Departments { get; set; }

        public static bool ImplementsIEntityGenericWithConstraints (PropertyInfo dbSetProperty)
        {
            var typeDbSet = dbSetProperty.PropertyType; // DbSet<Employee>.
            var typeEntity = typeDbSet.GenericTypeArguments [0]; // Employee.

            if (typeEntity.BaseType != typeof (object)) { return (false); }

            var interfaces = typeEntity.GetInterfaces();

            // Works fine.
            var isIEntity = interfaces.Any (i => i == typeof (IEntity));

            // How to ensure that [typeEntity] implements [IEntity<TEntity>]
            //  where TEntity: class, IEntity, IEntity<TEntity>, new().
            var isIEntityGeneric = interfaces.Any (i => i == typeof (IEntity<>));

            return (isIEntity && isIEntityGeneric);
        }
    }
}




Aucun commentaire:

Enregistrer un commentaire