#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
. Превращение их в конкретные типы настолько подрывает универсальную модель, что я подвергаю сомнению (даже гипотетическую) ценность этого. Вы все равно не получите большой гибкости, но посмотрите мою правку.