Приведение объектов, ссылающихся на себя, определение универсального типа

#c# #entity-framework #generics #asp.net-core #entity-framework-core

#c# #entity-framework #универсальные #asp.net-core #entity-framework-core

Вопрос:

Однажды я получил интерфейс для всех объектов:

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

Для некоторых объектов будет существовать таблица изменений, в которую будет записываться, что было сделано для какого объекта (CRUD)

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

Для каждого объекта, который реализует IMutation Entity Framework, будет создана таблица с именем Mutation<EntityName>
Итак, Mutation<EntityName> это тоже объект.

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

Я реализовал интерфейс IEntity для класса, который унаследуют некоторые объекты.

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

Объект Test наследуется от Entity (потому что это объект) и реализует имитацию со ссылкой на себя

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

Entity Framework получает это и создает две таблицы:

  • Test с помощью свойств Id и Name
  • Mutation<Test> с помощью свойств Id (PK из IEntity ) и EntityId (FK, ссылающийся на Test объект)

все это отлично работает. DB-schema и так далее.

Итак, что я хочу сделать, всегда, когда изменяется одна сущность, которую реализует taht IMutation<EntityName> , должен быть создан новый набор данных. Существует возможность переопределить SaveChanges DbContext. Неплохо, поэтому я попробовал это:

 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 amp;amp; 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();
}
  

Проблема сейчас в том, что я никогда не знаю, какой мой?-Тип есть. Я знаю, что это должно быть из Type IEntity .
Но когда я пытаюсь разобрать объект на IMutation<IEntity> , я получаю сообщение об ошибке, в котором говорится, что он не может выполнить приведение из IMutation<Test> в IMutation<IEntity> . (Но Test реализует IEntity )

Попробовал это таким образом:

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

Но я уже проверяю, реализует ли моя сущность IMutation .
Может быть, у кого-нибудь есть идея, как я мог бы решить эту проблему?

Ответ №1:

Сложно работать с универсальными интерфейсами, которые не являются ковариантными и не имеют аналогов, отличных от универсальных (таких как IEnumerable<T> -> IEnumerable , IQueryable<T> -> IQueryable и т.д.).

Единственным оставшимся выбором в таком случае является отражение или динамическая отправка.

Например, вы могли бы добавить метод, подобный этому:

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

и затем используйте DLR для его вызова (используя код из первого примера):

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

Комментарии:

1. Пожалуйста, измените свою ProcessMutationEntity-функцию на private void ProcessMutationEntity<TEntity>(TEntity entity) where TEntity : IEntity, IMutation<TEntity> { entity.Mutations.Add(new Mutation<TEntity> { EntityId = entity.Id, Entity = entity}); } — ты гениален, чувак! Наконец-то это работает. Вы наверняка сэкономили мне еще 5 часов.

2. Вот так:) Рад, что помогло. Приветствия.