在Entity Framework中存储基于接口的关系

Tor*_*ton 7 c# entity-framework

我有这个想要实现的域设计理念.我正在使用Entity Framework作为存储机制,我能看到这样做的唯一方法似乎并不理想.

我有很多域对象,其中许多都需要"标记".用户需要能够以某种方式"标记"它们中的每一个.

所以我想我会创建这个界面:

public interface IFlaggable
{
    [Required]
    Tally Tally { get; }
    Guid? TallyId { get; }
}
Run Code Online (Sandbox Code Playgroud)

和这些类:

public class Flag
{
    public Guid Id { get; internal set; }
    [Required]
    public Tally Tally { get; internal set; }
    public Guid TallyId { get; internal set; }
    [Required]
    public User Creator { get; internal set; }
    public Guid CreatorId { get; internal set; }
    [Required]
    public FlagIndication Indication { get; internal set; }
}

public class Tally
{
    public Guid Id { get; internal set; }
    public IFlaggable Subject { get; internal set; }
    public ICollection<Flag> Flags { get; internal set; }
}
Run Code Online (Sandbox Code Playgroud)

如果不明显,FlagIndication是一个枚举.

然后所有其他域对象必须实现IFlaggable并且它们变为可标记的.

public class TestFlaggable : IFlaggable
{
    public Guid Id { get; internal set; }
    public Tally Tally { get; internal set; }
    public Guid? TallyId { get; internal set; }
}
Run Code Online (Sandbox Code Playgroud)

看起来很棒.就DB而言,它应该工作得很好,因为它是一对一的关系,而其他表总是包含Tallies表的id字段的外键.

麻烦的是,实体框架会查看界面和扼流圈.明显.我们知道实体框架不能很好地与接口配合使用.

我可以看到让它与Entity Framework一起工作的唯一方法是废弃接口,创建子类,例如从Tally继承的"TestFlaggableTally",然后每个其他对象都可以拥有它自己的特殊雪花Tally类.

public class Tally
{
    public Guid Id { get; internal set; }
    public ICollection<Flag> Flags { get; internal set; }
}

[Table("TestFlaggableTallies")]
public class TestFlaggableTally : Tally
{
    public TestFlaggable Subject { get; internal set; }
}

public class TestFlaggable
{
    public Guid Id { get; internal set; }
    public TestFlaggableTally Tally { get; internal set; }
    public Guid? TallyId { get; internal set; }
}
Run Code Online (Sandbox Code Playgroud)

这应该工作,但它似乎愚蠢和臃肿.

所以,我在这里问.有没有更好的办法?

我有一些根本的误解吗?

这是我最好的选择吗?

好的,打我

t3z*_*t3z 3

我认为 EF 对接口没有这样的支持。-不正确,请参阅更新

我建议将您的接口重写为抽象类:

public abstract class FlaggableBase<T>
{
    public abstract T Id { get; internal set; }
    public abstract Tally Tally { get; internal set; }
    public abstract Guid? TallyId { get; internal set; }
}
Run Code Online (Sandbox Code Playgroud)

请注意,我将 Id 属性添加到基类中,以确保从 FlaggableBase 派生的任何实体都有一个键(EF 要求它能够区分实体)。

然后像这样使用它:

public class TestFlaggable : FlaggableBase<Guid>
{
    public override Guid Id { get; internal set; }
    public override Tally Tally { get; internal set; }
    public override Guid? TallyId { get; internal set; }
}

public class Tally
{
    public Guid Id { get; internal set; }
    public FlaggableBase<Guid> Subject { get; internal set; }
    public ICollection<Flag> Flags { get; internal set; }
}

public class Flag
{
    public Guid Id { get; internal set; }
    [Required]
    public Tally Tally { get; internal set; }
    public Guid TallyId { get; internal set; }
    public Guid CreatorId { get; internal set; }
}
Run Code Online (Sandbox Code Playgroud)

我立即看到使用抽象类而不是接口的唯一缺点是您将无法利用延迟加载,因为您无法在覆盖上应用virtual关键字。

如果由于某种原因,您仍然依赖 IFlaggable 接口,只需在基类中实现它:

public abstract class FlaggableBase<T> : IFlaggable
Run Code Online (Sandbox Code Playgroud)

更新

我在 VS 中玩了一下,结果发现你实际上可以使用接口做你想做的事情:

public interface IFlaggable<out T>
{
    T Id { get; }
    Tally Tally { get; }
    Guid? TallyId { get; }
}
Run Code Online (Sandbox Code Playgroud)

然后在你的 Tally 类中:

public class Tally
{
    public Guid Id { get; internal set; }
    public IFlaggable<Guid> Subject { get; internal set; }
    public ICollection<Flag> Flags { get; internal set; }
}
Run Code Online (Sandbox Code Playgroud)

这是它在数据库中的样子: 在此输入图像描述