在循环引用的接口中使用泛型

时间:2016-10-21 20:43:22

标签: c# entity-framework generics entity-framework-core

假设我有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 Migrations。当我尝试运行迁移时,它失败并显示错误:

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

我不能成为第一个遇到这个问题的人。提前谢谢。

2 个答案:

答案 0 :(得分:2)

要使用EF Core执行此操作,您需要在导航表达式中进行投射:

        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);

答案 1 :(得分:1)

以下解决方案适用于我(包括使用具体类创建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使用的具体实现并不是真正可以自由支持多种类型,只有接口可以支持多种类型。