I'm using the following generic function to determine whether a class implements a specified interface:
private static bool HasFieldType<TEntity, TInterface>()
{
return typeof(TInterface).IsAssignableFrom(typeof(TEntity));
}
This works fine for the majority of the time.
However, I now have an interface which has a generic parameter:
public interface IStatusField<TEnum> where TEnum : System.Enum
{
TEnum Status { get; set; }
}
And this causes the HasFieldType
function to break with an unexpected use of unbound generic name
error.
Ideally, I want to call the function like:
if (HasFieldType<TEntity, IStatusField<>>())
{
// builder is an EntityTypeBuilder instance
builder.Property("Status")
.HasMaxLength(255)
.HasConversion(new EnumToStringConverter<>());
}
But this won't work as I'm not specifying the generic type for both the IStatusField<>
or the EnumToStringConverter<>
.
Is there any way around this?
This code forms part of a generic base IEntityTypeConfiguration
class as follows:
public abstract class EntityTypeConfiguration<TPrimaryKey, TEntity> : IEntityTypeConfiguration<TEntity> where TEntity : Entity<TPrimaryKey>
{
public void Configure(EntityTypeBuilder<TEntity> builder)
{
builder.HasKey(e => e.Id);
builder.Property(e => e.Id)
.IsRequired()
.HasMaxLength(13)
.HasValueGenerator<PrimaryKeyValueGenerator>();
// Apply the generic interface properties
builder.ApplyInterfaceFields<TPrimaryKey, TEntity>();
// Apply any additional configuration
this.OnConfigure(builder);
}
protected abstract void OnConfigure(EntityTypeBuilder<TEntity> builder);
}
// In an extension class, I have
public static void ApplyInterfaceFields<TPrimaryKey, TEntity>(this EntityTypeBuilder<TEntity> builder) where TEntity : Entity<TPrimaryKey>
{
// Check other implementations (removed for brevity)
// IStatusField implementation
if (HasFieldType<TEntity, IStatusField<>())
{
builder.Property("Status")
.HasMaxLength(255)
.HasConversion(new EnumToStringConverter<>());
}
}
At the point of checking for IStatusField
implementation, I know nothing about the generic type specified. I think this may be the bigger problem...
Instead of trying to wrestle with resolving generic type arguments from nothing, you might consider approaching it from the opposite direction, by getting a list of interfaces implemented by TEntity
, filtering it to search for an IStatusField
. Once you've located the field, you can get its' generic type arguments and pass those to your EnumToStringConverter
:
var statusField = typeof(TEntity)
.GetInterfaces()
.FirstOrDefault(x => x.Name.StartsWith("IStatusField"));
Value given TEntity : IStatusField<ConsoleColor>
:
statusField.GenericTypeArguments = [ typeof(System.Color) ]
From there though you're not done; you must still construct an instance of the generic type EnumToStringConverter<System.Color>
. This is rather simple and outlined here.
Edit: I realized that because you'd be invoking a constructor, it's not quite the same. Here's how you'd accomplish this:
var statusField = typeof(TEntity)
.GetInterfaces()
.FirstOrDefault(x => x.Name.StartsWith("IStatusField"));
if (statusField != null)
{
var enumType = statusField.GenericTypeArguments[0]; // get the IStatusField<T> value
// get the default constructor after supplying generic type arg
var converterType = typeof(EnumToStringConverter<>)
.MakeGenericType(enumType)
.GetConstructors()[0];
// invoke the constructor. Note the null (optional) param
dynamic converter = converterType.Invoke(new Object[1]);
builder.Property("Status")
.HasMaxLength(255)
.HasConversion(converter);
}