Matthias Burger Matthias Burger - 1 month ago 7
C# Question

Casting of self-referencing entities, detect generic type

Once, I got an interface for all entities:

public interface IEntity
{
int Id { get; set; }
}


For some entities a mutationtable will exist, to log, what was done for which entity (CRUD)

public interface IMutation<TEntity> where TEntity : IEntity
{
ICollection<Mutation<TEntity>> Mutations { get; set; }
}


For each entity that implements
IMutation
Entity Framework will create a table with name
Mutation<EntityName>

So,
Mutation<EntityName>
is an entity, too.

public class Mutation<TEntity> : Entity where TEntity : IEntity
{
public int EntityId { get; set; }
public TEntity Entity { get; set; }
}


I implemented the interface
IEntity
on a class, that some entities will inherit.

public class Entity : IEntity
{
public int Id { get; set; }
}


The entity
Test
inherits from
Entity
(becuase it's an entity) and implements IMutation with a reference to itself

public class Test : Entity, IMutation<Test>
{
public ICollection<Mutation<Test>> Mutations { get; set; } = new List<Mutation<Test>>();
}


Entity Framework gets it, and creates the two tables:


  • Test
    with properties
    Id
    and
    Name

  • Mutation<Test>
    with property
    Id
    (the PK from
    IEntity
    ) and
    EntityId
    (the FK referencing the
    Test
    -entity)



this all works great. DB-schema and so on.

So what I want to do is, always, when one entity taht implements
IMutation<EntityName>
is changed, a new dataset shall be created.
There is the possibility to override
SaveChanges
of DbContext. Nice, so I tried it:

public override int SaveChanges()
{
IEnumerable<EntityEntry> entries = ChangeTracker.Entries(); // gets me all entries that were changed

IEnumerable<IEntity> mutationEntries =
entries.Select(s => s.Entity).Where(
w =>
w.GetType()
.GetInterfaces()
.Any(
x =>
x.GetTypeInfo().IsGenericType && x.GetGenericTypeDefinition() == typeof(IMutation<>)))
.Select(s => (IEntity)s);

// so here now I got the entries that implement IMutation<?> <-- call this now ?-type
// what I'd now want to do is:
foreach(var entry in mutationEntries)
{
IMutation<?> mutationEntry = (IMutation<?>)entry;
mutationEntry.Mutations.Add(new Mutation<?>{ /* later on, add here CRUD, Id, user who changed,... */ });
}
return base.SaveChanges();
}


The problem now is, that I never know, what my ?-Type is. I know it has to be from Type
IEntity
.
But when I try to parse the Entity to
IMutation<IEntity>
i get an error, saying, he cannot cast from
IMutation<Test>
to
IMutation<IEntity>
. (But
Test
implements
IEntity
)

Tried it this way:

IEnumerable<IMutation<IEntity>> mutationEntries =
entries.Select(s => s.Entity).Where(
w =>
w.GetType()
.GetInterfaces()
.Any(
x =>
x.GetTypeInfo().IsGenericType && x.GetGenericTypeDefinition() == typeof(IMutation<>)))
.Select(s => (IMutation<IEntity>)s);


But I'm already checking, whether my Entity implements
IMutation
.
Maybe someone has an idea, how I could solve this issue?

Answer

It's hard to work with generic interfaces that are not covariant and have no non generic counterparts (like IEnumerable<T> -> IEnumerable, IQueryable<T> -> IQueryable etc.).

The only remaining choice in such case is reflection or dynamic dispatch.

For instance, you could add a method like this:

private void ProcessMutationEntity<TEntity>(TEntity entity)
    where TEntity : IEntity, IMutation<TEntity>
{
    entity.Mutations.Add(new Mutation<TEntity> { EntityId = entity.Id, Entity = entity});
}

and then use DLR to call it (using the code from the first example):

// ...
foreach (var entry in mutationEntries)
{
    ProcessMutationEntity((dynamic)entry);
}
// ...