Consider the following model (.NET 8, C# 12):
public partial interface IEntity
{
public long Id { get; set; }
}
public partial interface IEntity<TEntity>
where TEntity : class, IEntity, IEntity<TEntity>, new()
{
}
public partial class User: IEntity, IEntity<User>
{
public long Id { get; set; }
}
public partial class Currency: IEntity, IEntity<Currency>
{
public long Id { get; set; }
}
public partial class ApplicationDbContext: DbContext
{
public virtual DbSet<User> Users { get; set; }
public virtual DbSet<Currency> Currencies { get; set; }
//// TODO: Fix to get a list of DbSets.
//public static List<object> GetDbSets () => typeof(ApplicationDbContext)
// .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 == typeof(DbSet<>))
// .Cast<object>()
// .ToList();
//// TODO: Fix to get a list of entities.
//public static List<Type> GetEntities () => typeof(ApplicationDbContext)
// .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.IsGenericType)
// .Where(p => p.PropertyType.Name.StartsWith(typeof(DbSet<>).Name))
// //.Where(p => p.PropertyType == typeof(DbSet<IEntity>))
// .Select(p => p.PropertyType.GetGenericArguments() [0])
// //.Cast<object>()
// .ToList();
public static List<Type> GetEntityTypesOrderedDelete () => new []
{
typeof(User),
typeof(Account),
typeof(Transaction),
typeof(AccountCategory),
typeof(Currency),
}
.ToList();
}
private static void Main ()
{
}
I want to get a list of properties List<System.Reflection.PropertyInfo>
values using the following criteria:
DbSet<IEntity<TEntity>>
.Dbset
properties must have exactly one generic parameter IEntity<TEntity>
.IEntity<TEntity>
must have exactly one nested generic parameter TEntity
.TEntity
must adhere to the generic type constraints enforced by IEntity<TEntity>
.The first two methods of the ApplicationDbContext
class do not return anything. As a workaround, I took a hard-coded approach in the third method, which of course is not sustainable for production code.
The ideal solution would have the following signature:
public static ReadOnlyCollection<PropertyInfo> GetApplicationDbContextDbSets ()
{
var properties = typeof(ApplicationDbContext)
.GetProperties();
return (properties.AsReadOnly());
}
I must be missing something or using the APIs incorrectly.
Any advice would be appreciated.
This filtering should fulfill you requirements:
public static ReadOnlyCollection<PropertyInfo> GetApplicationDbContextDbSets()
{
var properties = typeof(ApplicationDbContext)
// Properties must be instance, public, and virtual.
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.GetGetMethod()?.IsVirtual == true)
// Properties must inherit from DbSet<IEntity<TEntity>>
.Where(x => x.PropertyType.IsGenericType)
.Where(x => x.PropertyType.GetGenericTypeDefinition().IsAssignableFrom(typeof(DbSet<>)))
.Where(x => x.PropertyType.GetGenericArguments().Length == 1)
// The generic Dbset properties must have exactly one generic parameter IEntity<TEntity>
.Where(x => x.PropertyType.GetGenericArguments()[0]
.GetInterfaces()
.Where(y => y.IsGenericType)
.Where(y => y.GetGenericTypeDefinition().IsAssignableFrom(typeof(IEntity<>)))
// The generic parameter IEntity<TEntity> must have exactly one nested generic parameter TEntity.
.Where(y => y.GetGenericArguments().Length == 1)
// TEntity must adhere to the generic type constraints enforced by IEntity<TEntity>
.Where(y => y.GetGenericArguments()[0].IsAssignableFrom(x.PropertyType.GetGenericArguments()[0])).Any()
)
.ToList();
return (properties.AsReadOnly());
}
In production code I would write this as full for each loop instead of LINQ to have better debuggability and less duplicated expressions.