Использование обобщений в интерфейсах с циклической ссылкой

#c# #entity-framework #generics #entity-framework-core

#c# #entity-framework #обобщения #сущность-фреймворк-ядро

Вопрос:

Давайте предположим, что у меня есть 4 интерфейса, которые выглядят следующим образом:

 interface IMain
{
  ICollection<ISub> Subs { get; set; }
}

interface ISub
{
  ICollection<ISubPart> SubParts { get; set; }
}

interface IPart
{
  ICollection<ISubPart> SubParts { get; set; }
}

interface ISubPart
{
  ISub Sub { get; set; }
  IPart Part { get; set; }
}
  

Конечная цель для меня — использовать эти интерфейсы в одной библиотеке классов и реализовать их с классами в другой.

Если я попытаюсь реализовать интерфейсы, содержащая их коллекция или объект все равно должны иметь тип интерфейса, а не тип класса. Например:

 public class Sub : ISub
{
  ICollection<ISubPart> SubParts { get; set; }
}
  

Я использую Entity Framework и миграции EF. Когда я пытаюсь запустить миграцию, она завершается ошибкой:

 The entity type 'MyProject.ISubPart' provided for the argument 'clrType' must be a reference type.
  

Чтобы попытаться обойти это, я подумал, что мог бы передать тип как универсальный. Это легко сделать, пока я не доберусь до ISubPart, поскольку ISubPart должен был бы использовать обобщения, которые имели бы циклическую ссылку обратно на родительский элемент. Итак, что-то вроде этого:

 interface ISubPart<TSub, TPart>
  where TSub : class
  where TPart : class
{
  TSub Sub { get; set; }
  TPart Part { get; set; }
}
  

Но, если iSub нужен общий тип, переданный для определения ISubPart, тогда общий тип для ISubPart также должен был бы передать содержащий тип. Итак, мне почти нужно что-то вроде следующего, чего, как я знаю, не существует:

 interface ISubPart<TSub<TSubPart>, TPart>
  where TSub : class
  where TSubPart : this
  where TPart : class
{
  TSub<TSubPart> Sub { get; set; }
  TPart Part { get; set; }
}
  

Вот мой DbContext:

 public abstract class MyDbContext : MyDbContext<Main, Sub, Part>
{
    protected MyDbContext() { }

    public MyDbContext(DbContextOptions options) : base(options) { }
}

public abstract class MyDbContext<TMain, TSub, TPart> : DbContext
    where TMain : Main
    where TSub : Sub
    where TPart : Part
{
    protected MyDbContext() { }
    public MyDbContext(DbContextOptions options) : base(options) { }

    public DbSet<TMain> Mains { get; set; }
    public DbSet<TSub> Subs { get; set; }
    public DbSet<TPart> Parts { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<SubPart>()
            .HasOne(o => o..Sub)
            .WithMany(m => m.SubParts as List<SubPart>)
            .HasForeignKey(f => f.SubId);

        builder.Entity<SubPart>()
            .HasOne(o => o.Part)
            .WithMany(m => m.SubParts as List<SubPart>)
            .HasForeignKey(f => f.SubId);

        base.OnModelCreating(builder);
    }
}
  

Я не могу быть первым, кто столкнулся с этой проблемой. Заранее спасибо.

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

1. Я не понял вопроса

2. Я обновил вопрос, добавив больше контекста.

3. У вас не может быть коллекции интерфейсов в объектах EF.

4. Я знаю. Итак, если я передаю тип, как я могу передавать типы по цепочке отношений?

5. Что такое DbContextOptions ? И о какой конкретно EF-версии мы говорим?

Ответ №1:

Чтобы сделать это с EF Core, вам необходимо ввести выражение навигации:

 protected override void OnModelCreating(ModelBuilder builder)
{
    builder.Entity<SubPart>()
        .HasOne(o => (Sub)o.Sub)
        .WithMany(m => m.SubParts)
        .HasForeignKey(f => f.Id);

    builder.Entity<SubPart>()
        .HasOne(o => (Part)o.Part)
        .WithMany(m => m.SubParts)
        .HasForeignKey(f => f.Id);

    base.OnModelCreating(builder);
}
  

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

1. Идем дальше. Спасибо, Андрей! Я собираюсь опубликовать решение здесь, когда оно заработает.

2. У меня возникла проблема с другим свойством навигации. Я расскажу вам об этом завтра.

Ответ №2:

У меня работает следующее решение (включая создание базы данных EF с конкретными классами

 interface IMain<TSub, TPart, TSubPart>
    where TSubPart : ISubPart<TSub, TPart, TSubPart>
    where TSub : ISub<TSub, TPart, TSubPart>
    where TPart : IPart<TSub, TPart, TSubPart>
{
    ICollection<TSub> Subs { get; set; }
}

interface ISub<TSub, TPart, TSubPart>
    where TSub : ISub<TSub, TPart, TSubPart>
    where TPart : IPart<TSub, TPart, TSubPart>
    where TSubPart : ISubPart<TSub, TPart, TSubPart>
{
    ICollection<TSubPart> SubParts { get; set; }
}

interface IPart<TSub, TPart, TSubPart>
    where TPart : IPart<TSub, TPart, TSubPart>
    where TSub : ISub<TSub, TPart, TSubPart>
    where TSubPart : ISubPart<TSub, TPart, TSubPart>
{
    ICollection<TSubPart> SubParts { get; set; }
}

interface ISubPart<TSub, TPart, TSubPart>
    where TSubPart : ISubPart<TSub, TPart, TSubPart>
    where TSub : ISub<TSub, TPart, TSubPart>
    where TPart : IPart<TSub, TPart, TSubPart>
{
    TSub Sub { get; set; }
    TPart Part { get; set; }
}

class SubPart : ISubPart<Sub, Part, SubPart>
{
    public long Id { get; set; }

    public Sub Sub { get; set; }

    public Part Part { get; set; }
}

class Sub : ISub<Sub, Part, SubPart>
{
    public long Id { get; set; }

    public ICollection<SubPart> SubParts { get; set; }
}

class Part : IPart<Sub, Part, SubPart>
{
    public long Id { get; set; }

    public ICollection<SubPart> SubParts { get; set; }
}

class Main : IMain<Sub, Part, SubPart>
{
    public long Id { get; set; }

    public ICollection<Sub> Subs { get; set; }
}


class MyContext : DbContext
{
    public DbSet<Main> MainEntities { get; set; }
}
  

Как вы можете видеть, конкретные реализации, которые используются EF, на самом деле не могут свободно поддерживать несколько типов, только интерфейс может поддерживать некоторое разнообразие.

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

1. В процессе реализации этого. Я опубликую ответ завтра.

2. Вы абсолютно уверены, что у вас не осталось никакого интерфейса в объектах EF? Как я уже сказал, я фактически протестировал код моего ответа и создал с его помощью реальный файл базы данных с EF 6.1.3. Итак, я думаю, проблема в том, что вы сделали что-то отличное от того, что сделал я.

3. Я добавил свой контекст к сообщению

4. Я удалил пример проекта на jasongaylord.com/media/webapplication1.zip

5. @JasonN. Gaylord Хорошо, я перепроверил, допустил ошибку… EF6 действительно создал миграцию, но любезно проигнорировал сопряженные свойства ISub<TSelf> и IPart<TSelf> Part . Превращение их в конкретные типы настолько подрывает универсальную модель, что я подвергаю сомнению (даже гипотетическую) ценность этого. Вы все равно не получите большой гибкости, но посмотрите мою правку.