13

is there any way to use interfaces as navigation properties in EF6? I've found related topics for EF4 or earlier where it didn't seem to be possible; generally, inheritance seems to have improved a lot since then, but I haven't found a way to make this specific problem work yet.

Example:

public interface IPerson
{
  string name { get; set; }
}

public class Man : IPerson { /* ... */ }
public class Woman : IPerson { /* ... */ }

public interface ICar
{
  IPerson driver { get; set; }
}

public class Car : ICar
{
  public virtual IPerson driver { get; set; }  // This won't map
}

Is this possible in any way? If not, what'd be an advisable way to do this?

Because currently I don't see any way for an interface to have a set-able property whose type is some other interface (the IPerson property of ICar, for example), which kind of strikes me as a very serious design limitation?!

Bogey
  • 4,926
  • 4
  • 32
  • 57
  • What do you expect navigation property `Car.driver` would map to? – Floremin Aug 19 '14 at 13:57
  • This is not possible in EF. You can, however, use base classes and inherit from those. I'm not entirely sure about the nitty-gritty of this, but if it's possible to convert the Interfaces to Class, you can go that way. – Ivo Coumans Aug 19 '14 at 13:57
  • Why would you want to do this? What are you trying to accomplish. – Botonomous Aug 19 '14 at 13:59
  • 1
    Floremin: Couldn't this, in theory, work similarly to how EF maps polymorphic classes? For example, create a table "IPerson" with fields "id" and "lookupType" (which can in this case be "Man" or "Woman"), and then a table each for Man and Woman. Car.driver would then point to IPerson.id, which in turn redirects to the "real" table (Man or Woman). – Bogey Aug 19 '14 at 14:10
  • Tables on database are concrete types on Models on code. You can use Interfaces but do not map them to the db itself, only the concrete implementations. – Bart Calixto Aug 19 '14 at 14:14

1 Answers1

12

Okay, for those possibly facing the same issue in the future. After more testing around, this is how I'm doing it now.

public interface IPerson
{
  string name { get; set; }
}

public abstract class APerson : IPerson
{
  public string name { get; set; }
}

public class Man : APerson { /* ... */ }
public class Woman : APerson { /* ... */ }

public interface ICar
{
  IPerson driver { get; set; }
}

public class Car : ICar
{
  // This maps to the database
  public virtual APerson driver { get; set; }

  // And this implements the interface
  ICar.driver
  {
    get
    {
      return (IPerson)driver;
    }
    set
    {
      if(!(value is APerson))
        throw new InvalidCastException("driver must inherit from APerson");

      driver = (APerson)value;
    }
  }
}

This gets a bit more tricky when having one-to-many / many-to-many relations, for that case I've written a class that inherits from Collection<Interface type>, but also implements ICollection<Abstract base type>, and again throws an exception when someone tries adding/setting any object that doesn't inherit from the abstract base class. It's basically a Collection<IPerson> that's guaranteed to only contain objects inheriting that inherit APerson, if you will.

This solution is definitely not ideal, because it just throws an exception if somebody tries assigning a value to driver that does not inherit from APerson, so no compile-time safety here. But it's the best solution I could think of so far, if you really want to keep your interfaces separate and self-contained.

Bogey
  • 4,926
  • 4
  • 32
  • 57
  • This is the correct answer despite a little typeo in the code. I ran a bunch of tests including creating a database using code first and this works great. The typeo is that the ICar.driver property on class Car should actully read "IPerson ICar.driver". –  Apr 02 '16 at 19:23
  • 2
    I forgot to add that throwing in the setter is not a good Idea. Also, the value parameter in the setter is of IPerson, not APerson. In other words the error is driver must implement IPerson, not driver must inherit from APerson. –  Apr 02 '16 at 19:34
  • 5
    Actually this is not a good design, though it's not our fault, but EF's. We shouldn't be making poor design in domain just to fit EF needs, but there is no other way right now. Why poor design? Because the idea of using interfaces is exactly to hide the implementation details, but we are doing exactly the opposite, by internally using `APerson` in our Car. What if we had `ARobot` which was also `IPerson`? What if we had 10 possible `IPerson`? We would need all of them inside our `Car`. So there was no need to declare `IPerson` inside our `Car`, but again, not our (developers) fault. – Alisson Reinaldo Silva Sep 15 '16 at 11:39