Cannot automatically bind the navigation property 'Players' on entity type 'Data.Team' for the source entity set 'Teams'

I'm using EF6 Code First.

I have the following classes:

public class Player
{
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Required, MinLength(2, ErrorMessage = "Player name must be at least 2 characters length")]
    public string Name { get; set; }

    [Required]
    public int TeamClubId { get; set; }

    [ForeignKey("TeamClubId")]
    public virtual Team Club { get; set; }

    [Required]
    public int TeamNationalId { get; set; }

    [ForeignKey("TeamNationalId")]
    public virtual Team National { get; set; }

}

public class Team
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Required, MinLength(2, ErrorMessage = "Team name must be at least 2 characters length")]
    public string Name { get; set; }

    [Required]
    public TeamType Type { get; set; }

    public virtual ICollection<Player> Players { get; set; }

    public virtual ICollection<Match> Matches { get; set; }
}


public class Tournament
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Required, MinLength(2, ErrorMessage = "Tournament name must be at least 2 characters length")]
    public string Name { get; set; }

    public int Year { get; set; }

    public DateTime StartDate { get; set; }

    public DateTime EndDate { get; set; }

    public ICollection<Match> Matches { get; set; }

}

In the DBContext class I have:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    //player - national team relations
    modelBuilder.Entity<Player>()
        .HasRequired<Team>(p => p.National)
        .WithMany()
        .WillCascadeOnDelete(false);

    //player - club team relations
    modelBuilder.Entity<Player>()
        .HasRequired<Team>(p => p.Club)
        .WithMany()
        .WillCascadeOnDelete(false);

    //match - home team relations
    modelBuilder.Entity<Match>()
        .HasRequired<Team>(p => p.HomeTeam)
        .WithMany()
        .WillCascadeOnDelete(false);

    //match - away team relations
    modelBuilder.Entity<Match>()
        .HasRequired<Team>(p => p.AwayTeam)
        .WithMany()
        .WillCascadeOnDelete(false);

    base.OnModelCreating(modelBuilder);
}

I get the following error when trying to connect via OData:

Cannot automatically bind the navigation property 'Players' on entity type 'Data.Team' for the source entity set 'Teams' because there are two or more matching target entity sets. The matching entity sets are: Players, Tournaments.

[NotSupportedException: Cannot automatically bind the navigation property 'Players' on entity type 'Data.Team' for the source entity set 'Teams' because there are two or more matching target entity sets. The matching entity sets are: Players, Tournaments.] System.Web.Http.OData.Builder.EntitySetConfiguration.FindBinding(NavigationPropertyConfiguration navigationConfiguration, Boolean autoCreate) +736 System.Web.Http.OData.Builder.EdmModelHelperMethods.AddNavigationBindings(EntitySetConfiguration configuration, EdmEntitySet entitySet, EntitySetLinkBuilderAnnotation linkBuilder, ODataModelBuilder builder, Dictionary2 edmTypeMap, Dictionary2 edmEntitySetMap) +391 System.Web.Http.OData.Builder.EdmModelHelperMethods.AddEntitySets(EdmModel model, ODataModelBuilder builder, EdmEntityContainer container, Dictionary2 edmTypeMap) +635 System.Web.Http.OData.Builder.EdmModelHelperMethods.BuildEdmModel(ODataModelBuilder builder) +200 System.Web.Http.OData.Builder.ODataConventionModelBuilder.GetEdmModel() +664 ODataAPI.WebApiConfig.Register(HttpConfiguration config) +221 System.Web.Http.GlobalConfiguration.Configure(Action1 configurationCallback) +65

[HttpException (0x80004005): Cannot automatically bind the navigation property 'Players' on entity type 'Data.Team' for the source entity set 'Teams' because there are two or more matching target entity sets. The matching entity sets are: Players, Tournaments.] System.Web.HttpApplicationFactory.EnsureAppStartCalledForIntegratedMode(HttpContext context, HttpApplication app) +12582201 System.Web.HttpApplication.RegisterEventSubscriptionsWithIIS(IntPtr appContext, HttpContext context, MethodInfo[] handlers) +175 System.Web.HttpApplication.InitSpecial(HttpApplicationState state, MethodInfo[] handlers, IntPtr appContext, HttpContext context) +304 System.Web.HttpApplicationFactory.GetSpecialApplicationInstance(IntPtr appContext, HttpContext context) +404 System.Web.Hosting.PipelineRuntime.InitializeApplication(IntPtr appContext) +475

[HttpException (0x80004005): Cannot automatically bind the navigation property 'Players' on entity type 'Data.Team' for the source entity set 'Teams' because there are two or more matching target entity sets. The matching entity sets are: Players, Tournaments.] System.Web.HttpRuntime.FirstRequestInit(HttpContext context) +12599232 System.Web.HttpRuntime.EnsureFirstRequestInit(HttpContext context) +159 System.Web.HttpRuntime.ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context) +12438981

What's my problem?

Thanks

Answers


You have two navigation properties in Player pointing to Team, National and Club, and then you also have the property Players from Team, that is the one EF cannot resolve because it cannot guess if this collection is pointing to National or Team property so you have to help him, one way to do it is decorating the properties with InverseProperty attribute:

public class Team
{ 
    [InverseProperty("Club")]   
    public virtual ICollection<Player> ClubPlayers { get; set; }
    [InverseProperty("National")]
    public virtual ICollection<Player> NationalPlayers { get; set; }
}

Of course, i think you could do the same using fluent mapping too.


I had the same problem here, but my problem was a typing error on WebApiConfig.cs OData contiguration:

           builder.EntitySet<User>("Users");
           builder.EntitySet<User>("Issues");

instead of

           builder.EntitySet<User>("Users");
           builder.EntitySet<Issue>("Issues");

What you are in effect describing is a Many:Many relationship between Player and Team. Even though you want to specify a 1:2 relationship (player:team) the EntityFramework works with 1:1-0, 1:1, 1:many. 2 needs to be many to work properly with EF, you can use business logic to constrain it to just two if that is neccesary.

Why is this an issue? EF requires that for each Navigation Property there is a single Collection Property on the Principal Entity... so that it can support the convention of automatically setting the ForeignKey property when an entity is added to the collection.

In your expected model, say you want to add a Player to the Team with the following code, how will EF know which of the team Ids to set on player record?

Player newPlayer = new Player();
Team team = db.Teams.FirstOrDefault(t => t.Id == 1); // Just illustrating that team is an existing data entity;
team.Players.Add(newPlayer);
// should this set TeamNationalId = 1
// or TeamClubId = 1

Just because you might not want to use the code above to add the players to a team does not mean that EF will simply ignore the convention.

What are your options:

  1. Separate Team into 2 tables, NationalTeam and ClubTeam
  2. Split the navigation property into two properties, NationalPlayers, ClubPlayers
  3. Add an intermediary table TeamPlayer

I don't want you to consider option 1, and I don't think that you want to either, so I'll skip past that one...

Then there is another issue... you have a similar relationship with Team and Match, a Match has a HomeTeam and an AwayTeam, only in Match it probably bares more resemblance to reality that a Team has a Collection of HomeMatches and AwayMatches but it is essentially the same situation with different data types.

So of the 3 options above, here is some code for option 2: (thanks to E-Bat's answer for attribute notation of Inverse Properties)

public class Team
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Required, MinLength(2, ErrorMessage = "Team name must be at least 2 characters length")]
    public string Name { get; set; }

    [Required]
    public TeamType Type { get; set; }

    //public virtual ICollection<Player> Players { get; set; }
    [InverseProperty("Club")]
    public virtual ICollection<Player> ClubPlayers { get; set; }
    [InverseProperty("National")]
    public virtual ICollection<Player> NationalPlayers { get; set; }


    //public virtual ICollection<Match> Matches { get; set; }
    [InverseProperty("HomeTeam")]
    public virtual ICollection<Match> HomeMatches { get; set; }
    [InverseProperty("AwayTeam")]
    public virtual ICollection<Match> AwayMatches { get; set; }

}

Now if that is an issue for some of your business logic code, the following class definition might help, by adding the Players and Matches properties to Team it might simplify processing, note that you also have to initialize the collections in the constructor to avoid errors, but you would do this anyway for navigation properties to avoid issues during serialization:

public class Team
{
    public Team()
    {
        ClubPlayers = new HashSet<Player>();
        NationalPlayers = new HashSet<Player>();
    }

    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Required, MinLength(2, ErrorMessage = "Team name must be at least 2 characters length")]
    public string Name { get; set; }

    [Required]
    public TeamType Type { get; set; }

    [InverseProperty("Club")]
    public virtual ICollection<Player> ClubPlayers { get; set; }
    [InverseProperty("National")]
    public virtual ICollection<Player> NationalPlayers { get; set; }

    public virtual IEnumerable<Player> Players
    {
        get
        {
            return ClubPlayers.Union(NationalPlayers);
        }
    }

    [InverseProperty("HomeTeam")]
    public virtual ICollection<Match> HomeMatches { get; set; }
    [InverseProperty("AwayTeam")]
    public virtual ICollection<Match> AwayMatches { get; set; }
    public virtual IEnumerable<Match> Matches
    {
        get
        {
            return HomeMatches.Union(AwayMatches);
        }
    }

}

Lets consider Option 3. An Intermediary table of TeamPlayers. This will allow you to add more metadata about the relationship, such as Sign/from date and end date. Maybe they are a temporary transfer or loan player?

Note that the above reasons do not really apply for option 3 in the context of the Matches relationship, a match will always have exactly 2 teams, and these teams wont change over time. Option 2 is in IMO the best one for the matches scenario.

You already have Year in the matches, which means you want to track and keep data over many seasons, there is a good probability that over the years players will change teams, your current model will not support this, for instance to run a report on the players in a grand final 5 years ago will either show too many or not enough players depending on player attrition rates... it's just a thought. Option 3 opens up some new possibilities for future functionality and reporting.


Need Your Help

WPF Style Validation Trigger Command

wpf validation mvvm triggers command

I have some dynamic generated Textboxes with Validators. I want them to send a Command to VM, if a validation error occurs. This Behavior is placed in a style, so I don't need to write it into the ...