17

I am having trouble with understanding and implementing a many to many repationship using the FLuent API and EF Core.

I have looked at this question and set up my relationship exactly as that but I am getting the following error:

Error CS1061 'CollectionNavigationBuilder' does not contain a definition for 'WithMany' and no extension method 'WithMany' accepting a first argument of type 'CollectionNavigationBuilder' could be found (are you missing a using directive or an assembly reference?)

This is my intention. I have a client who has many jobs. I should be able to get all the jobs linked to that client. EF should create the join table in the background...

Here are my classes:

public class Client : IEntityBase
{
    public int Id { get; set; }

    public int? JobId { get; set; }
    public ICollection<Job> Jobs { get; set; }
}

public class Job : IEntityBase
{
    public int Id { get; set; }
}

//my interface
public interface IEntityBase
{
    int Id { get; set; }
}

EDIT Here is the Fluent API I tried and where I get the error on the ".withMany"

        modelBuilder.Entity<Client>()
            .HasMany(p => p.Jobs)
            .WithMany(p => p.clients)
            .Map(m =>
            {
                m.MapLeftKey("ClientId");
                m.MapRightKey("JobId");
                m.ToTable("ClientJob");
            });

I am using a generic repository pattern as per Chris Sakell's blog. Here is the code for retrieving clients:

        IEnumerable<Client> _clients = _clientRepository
           .AllIncluding(s => s.Creator, s => s.Jobs, s => s.State)
           .OrderBy(s => s.Id)
           .Skip((currentPage - 1) * currentPageSize)
           .Take(currentPageSize)
           .ToList();

and I am using the generic code as per:

    public virtual IEnumerable<T> AllIncluding(params Expression<Func<T, object>>[] includeProperties)
    {
        IQueryable<T> query = _context.Set<T>();
        foreach (var includeProperty in includeProperties)
        {
            query = query.Include(includeProperty);
        }
        return query.AsEnumerable();
    }

How do I configure this so I can retrieve the jobs as well using the includeproperty as per the Allincluding statement above?

Community
  • 1
  • 1
si2030
  • 3,895
  • 8
  • 38
  • 87
  • Take a look at EF Core documentation - [Relationships](https://learn.microsoft.com/en-us/ef/core/modeling/relationships) - **Many-to-many** – Ivan Stoev Feb 20 '17 at 09:18

3 Answers3

23

The Fluent API example you are trying to implement comes from EF 6. Many-to-Many relationship configuration is a little different in EF Core. For a start, you need to include an entity to represent the join/bridging table:

public class ClientsJobs
{
    public int ClientId { get; set; }
    public int JobId { get; set; }
    public Client Client { get; set; }
    public Job Job { get; set; }
}

Then you configure it like this in the OnModelCreating method:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<ClientsJobs>()
        .HasKey(x => new { x.ClientId, x.JobId });

    modelBuilder.Entity<ClientsJobs>()
        .HasOne(x => x.Client)
        .WithMany(y => y.Jobs)
        .HasForeignKey(y => y.JobId);

    modelBuilder.Entity<ClientsJobs>()
        .HasOne(x => x.Job)
        .WithMany(y => y.Clients)
        .HasForeignKey(y => y.ClientId);
}

See more about it here: http://www.learnentityframeworkcore.com/configuration/many-to-many-relationship-configuration

Note: you do need to include navigational properties for both ends of the relationship in the related classes, so you need to add a Clients property to your Job entity.

Mike Brind
  • 28,238
  • 6
  • 56
  • 88
  • is this still today the case? – Emil Jun 27 '20 at 17:41
  • 1
    According to the documentation on the EF Core site, you should be ok without defining the Join entity: https://learn.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key#other-relationship-patterns Is the site incorrect or outdated? – Federico M. Rinaldi Oct 28 '20 at 13:05
  • @FedericoM.Rinaldi The docs refer to a feature that was added to 5.0 rc1. The site hasn't caught up yet. – Mike Brind Oct 28 '20 at 17:10
  • Thanks for the clarification it took me a while to realize that the docs are more updated than EF itself LOL – Federico M. Rinaldi Oct 29 '20 at 18:56
  • `Clients` and `Jobs` are collections of the `ClientsJobs` class right, and not `Client` nor `Job`? – Jeppe Nov 27 '20 at 18:45
11

For EF Core 6.0 and up, you may (finally) use direct relationships for many-to-many:

modelBuilder
.Entity<Post>()
.HasMany(p => p.Tags)
.WithMany(p => p.Posts)
.UsingEntity(j => j.ToTable("PostTags"));

Source: Relationships - EF Core | Microsoft (many-to-many)

Ross Brasseaux
  • 3,879
  • 1
  • 28
  • 48
  • This goes within the OnModelCreating method of the DbContext (in case that isn't obvious). You can find more examples in the source. – Ross Brasseaux Apr 09 '21 at 01:18
  • Is there a way to have the navigation on one side only? Aka `.WithMany()` ? – Nathan Goings May 04 '21 at 05:01
  • There should be an override that takes a generic type parameter. .WithMany() – Ross Brasseaux May 04 '21 at 05:05
  • There is not in EF Core 5.0 — https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.metadata.builders.collectionnavigationbuilder?view=efcore-5.0 I've started a bounty on this question: https://stackoverflow.com/questions/66790470/can-many-to-many-relationships-in-ef-core-5-0-be-configured-keeping-just-one-nav – Nathan Goings May 04 '21 at 05:24
  • Looks like uni-directional is not supported (yet?): https://github.com/dotnet/efcore/issues/3864 – Nathan Goings May 04 '21 at 05:40
  • @NathanGoings You may be right. I am holding off using 5.0 until it is more supported by Azure App Services, so I am certainly no expert on all the breaking changes. Reading the github issue #3864 that you linked, he states "_We are going to enable unbound navigation properties only in metadata so that they can be used in the model snapshot for Migrations (see #2140) but we plan to block their use in runtime scenarios._" I suppose this means allowing fluent API configs to build the DB, but no current plans to allow `Include` statements on those shadow properties. – Ross Brasseaux May 04 '21 at 15:37
  • 1
    I've tested this with EF Core 6.0 and it does work as intended at runtime. I've updated my answer. – Ross Brasseaux Jan 23 '23 at 19:01
-3

You should add the Clients property to the Job class too:

public class Job : IEntityBase
{
   public int Id { get; set; }
   public ICollection<Client> Clients{ get; set; }
}

Then everything should be in order.

Mihail Stancescu
  • 4,088
  • 1
  • 16
  • 21
  • 4
    Still getting the error "CollectionNavigationBuilder does not contain a definition for "withMany" even after I have added this... – si2030 Feb 20 '17 at 10:31