Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Storing an interface based relationship in Entity Framework

I have this domain design idea I would like to implement. I'm using Entity Framework as the storage mechanism and the only way I can see to do so doesn't seem ideal.

I have lots of domain objects and many of them need to be "flaggable". The users need to be able to "flag" each of them as needing attention in some way.

So I thought I would create this interface:

public interface IFlaggable
{
    [Required]
    Tally Tally { get; }
    Guid? TallyId { get; }
}

and these classes:

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

FlagIndication is an enum if that's not obvious.

Then all the other domain objects have to do is implement IFlaggable and they become flaggable.

public class TestFlaggable : IFlaggable
{
    public Guid Id { get; internal set; }
    public Tally Tally { get; internal set; }
    public Guid? TallyId { get; internal set; }
}

Seems great. In terms of DB it should work just fine in that it's a one to one relationship and the other tables would always contain a foreign key to the id field of the Tallies table.

Trouble is that Entity Framework takes a look at the interface and chokes. Obviously. We know Entity Framework doesn't play well with interfaces.

The only way I can see to make it work with Entity Framework is by scrapping the interface, creating child classes such as "TestFlaggableTally" that inherit from Tally and then each other object can have it's own special snowflake Tally class.

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

That should work but it seems silly and bloated.

So, I'm here to ask. Is there a better way?

Do I have some fundamental misunderstanding?

Is that my best option?

ok, hit me

like image 452
Tory Netherton Avatar asked Aug 29 '14 20:08

Tory Netherton


1 Answers

I think EF doesn't have such support for interfaces. - NOT TRUE, SEE UPDATE

I would suggest rewriting your interface into abstract class:

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

Note that I added Id property to the base class, to ensure that any entity that derives from FlaggableBase has a key (EF requires this to be able to distinguish entities).

Then use it like this:

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

The only drawback of using abstract class instead of interface I see right away, is that you will not be able to leverage lazy-loading, as you cannot apply virtual keyword on overrides.

If, for some reason, you still rely on the IFlaggable interface, simply implement it in the base class:

public abstract class FlaggableBase<T> : IFlaggable

UPDATE

I played around a bit in VS and it turns out you actually can do what you want using interfaces:

public interface IFlaggable<out T>
{
    T Id { get; }
    Tally Tally { get; }
    Guid? TallyId { get; }
}

Then in your Tally class:

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

Here's how it will look in the DB: enter image description here

like image 82
t3z Avatar answered Sep 22 '22 06:09

t3z