Kazu
Kazu

Reputation: 637

Either OData or Code-First fails: derived navigation properties

I'm having a complex situation where I either get a problem with the Code-First portion of my project, or a problem with the OData portion of my project...

I have an abstract class Person of which I derive two classes User and Driver:

[DataContract]
[KnownType(typeof(User))]
[KnownType(typeof(Driver))]
public abstract class Person
{
    [Key, DataMember]
    public int ID { get; set; }
    [DataMember]
    public int RelationID { get; set; }
}

public class User : Person { }
public class Driver : Person { } // shortened for sake of readability

Now I have a model Relation, which can contain both users and/or drivers.

[DataContract]
public class Relation
{
    [Key, DataMember]
    public int ID { get; set; }
    [DataMember]
    public virtual ICollection<User> Users { get; set; }
    [DataMember]
    public virtual ICollection<Driver> Drivers { get; set; }
}

The problem is, when I let Code-First generate migration code, I can see that it wants to add two columns Relation_ID and Relation_ID1 in order to cope with the mapping of Users and Drivers. I applied the [InverseProperty("ID")] attribute to both navigation properties in order to try to solve this (as described here: Code First DataAnnotations). But still, the problem persists. So, this is not working.

Now, you would say: why not make it an ICollection<Person> People property, and use this.db.People.OfType<User>() in order to cast the objects to the right model and retrieve it that way. This is perfectly acceptable, but now it turns into an OData problem.

I want to be able to call public IQueryable<User> GetUsers([FromODataUri] int key) from the Relations controller in order to use /odata/Relations(16)/Users to get all users from a relation. If there is no Users property present in the Relation model, the OData parser rejects any such call because it can not resolve to the correct method.

So I'm stuck between the designs of both worlds. I'm guessing that in order to correctly solve this problem, the focus should lie on the Code-First part, not on the OData part. Any ideas are appreciated, thanks in advance!

Upvotes: 1

Views: 491

Answers (1)

RaghuRam Nadiminti
RaghuRam Nadiminti

Reputation: 6793

You can add the ICollection People property as well and exclude Users and Drivers properties from the Data model. For example,

[DataContract]
public class Relation
{
    [Key, DataMember]
    public int ID { get; set; }

    [DataMember]
    public virtual ICollection<Person> People { get; set; }      

    [DataMember]
    public virtual ICollection<User> Users
    {
        get
        {
            return People.OfType<User>();
        }
    }

    [DataMember]
    public virtual ICollection<Driver> Drivers
    {
        get
        {
            return People.OfType<Driver>();
        }
    }
}

In your DbModelBuilder code exclude the Users and Drivers properties by doing,

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        var relation = modelBuilder.Entity<Relation>();
        relation.Ignore(p => p.Users);
        relation.Ignore(p => p.Drivers);
    }

In your ODataModelBuilder, exclude the People property if you want to.

Upvotes: 1

Related Questions