简体   繁体   中英

EF Core - many to many relationship use / access custom join table

I am trying to implement a many to many relationship.

The Models -

public class User
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Book> OwnedBooks { get; set; }
}

public class Own
{
    public int UserId { get; set; }
    public int BookId { get; set; }
    public User User { get; set; }
    public Book Book { get; set; }
}

public class Book
{
    [Key]
    public int Id { get; set; }
    public int AuthorId { get; set; }
    public User Author { get; set; }
    public List<User> OwnedBy { get; set; } //Not really needed, but without it I can't create the join table "Own"
    [NotMapped]
    public int UsersReached; //Get this via the "Own" table
}

The DbContext -

public class TestContext : DbContext
{
    public DbSet<User> Users { get; set; }
    public DbSet<Book> Books { get; set; }
    public DbSet<Own> Own { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseSqlServer("Server=DESKTOP-BT4H8CA;Database=Test;Trusted_Connection=True");

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<Book>().HasOne(x => x.Author);

        builder.Entity<User>()
            .HasMany(x => x.OwnedBooks)
            .WithMany(x => x.OwnedBy)
            .UsingEntity(x => x.ToTable("Own"));

        builder.Entity<Own>()
            .HasKey(x => new {x.BookId, x.UserId});
    }
}

I am struggling with accessing the join table "Own". I need it to get the amount of each Book that is sold, without completely loading the users. That's why I don't want to use the auto generated one:

Cannot use table 'Own' for entity type 'BookUser (Dictionary<string, object>)' since it is being used for entity type 'Own' and potentially other entity types, but there is no linking relationship. Add a foreign key to 'BookUser (Dictionary<string, object>)' on the primary key properties and pointing to the primary key on another entity typed mapped to 'Own'.

Thanks in advance for your help!

You can actually use the auto-generated joining table and still get the count of each book sold, without completely loading the users .

With your current User and Book models, configure the relationships as -

protected override void OnModelCreating(ModelBuilder builder)
{
    builder.Entity<Book>()
        .HasOne(p => p.Author)
        .WithMany()
        .HasForeignKey(p => p.AuthorId)
        .OnDelete(DeleteBehavior.NoAction);

    builder.Entity<User>()
        .HasMany(p => p.OwnedBooks)
        .WithMany(p => p.OwnedBy);
}

Then you can query the books with their count of sales as -

var books = dbCtx.Books
        .Select(p => new Book
        {
            Id = p.Id,
            AuthorId = p.AuthorId,
            Author = p.Author,
            UsersReached = p.OwnedBy.Count  // this will not load User entities
        })
        .ToList();

EDIT:
You can use AutoMapper which can do the projection in .Select() method for you, like -

var dtos = _Mapper.ProjectTo<BookDTO>(dbCtx.Books).ToList();

For that, you'll need to -

  1. create a DTO model with properties you want from the query result, like -
public class BookDTO
{
    public int Id { get; set; }
    public string Author { get; set; }
    public int UsersReached { get; set; }
}
  1. define a map from Book to BookDTO -
CreateMap<Book, BookDTO>()
    .ForMember(d => d.Author, opt => opt.MapFrom(s => s.Author.Name))
    .ForMember(d => d.UsersReached, opt => opt.MapFrom(s => s.OwnedBy.Count));

You can remove the [NotMapped] property UsersReached from the Book model.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM