Hiding join entity on many to many relationships

c# entity-framework entity-framework-core

Question

As EFCore requires you to explicitly create join entities I am trying to find ways to make this code more manageable. I am following this tutorial (4 part series, this part and next part are the relevant ones): https://blog.oneunicorn.com/2017/09/25/many-to-many-relationships-in-ef-core-2-0-part-2-hiding-as-ienumerable/

It hides the join class away as a private ICollection and then populates another ICollection with the joined entitis so it works in similar fashion to EF6. Here is my implementation for tracking what cars people own:

public class Person
{
    public Person() => 
        Cars = new JoinCollectionFacade<Car, PersonCar>
        (PersonCars, pc => pc.Car, c => new PersonCar { Person = this, Car = c });

    public int Id { get; set; }
    public string Name { get; set; }

    private ICollection<PersonCar> PersonCars { get; } = new List<PersonCar>();

    [NotMapped]
    public ICollection<Car> Cars { get; }
}

public class Car
{
    public Car() => Persons = new JoinCollectionFacade<Person, PersonCar>
        (PersonCars, pc => pc.Person, p => new PersonCar { Person = p, Car = this });

    public int Id { get; set; }
    public string Manufacturer { get; set; }

    private ICollection<PersonCar> PersonCars { get; } = new List<PersonCar>();

    [NotMapped]
    public ICollection<Person> Persons { get; }
}

public class PersonCar
{
    public Person Person { get; set; }
    public int PersonId { get; set; }
    public Car Car { get; set; }
    public int CarId { get; set; }
}   

My mappings:

modelBuilder.Entity<PersonCar>(e => 
{
    e.HasKey(t => new { t.PersonId, t.CarId });
    e.HasOne(pc => pc.Person).WithMany("PersonCars");
    e.HasOne(pc => pc.Car).WithMany("PersonCars");
});

And some seed data:

using (var db = new ManyDbContext())
{
    db.Database.EnsureDeleted();
    db.Database.EnsureCreated();

    db.Persons.AddRange(
        new Person() { Name = "John" },
        new Person() { Name = "Peter" },
        new Person() { Name = "Paul" }
    );

    db.Cars.AddRange(
        new Car() { Manufacturer = "Audi" },
        new Car() { Manufacturer = "Honda" },
        new Car() { Manufacturer = "Mercedes" },
        new Car() { Manufacturer = "Ferrai" },
        new Car() { Manufacturer = "Porche" }
        );

    db.SaveChanges();

    db.PersonCars.AddRange(
        new PersonCar() { PersonId = 1, CarId = 2},
        new PersonCar() { PersonId = 1, CarId = 3 },
        new PersonCar() { PersonId = 2, CarId = 2 },
        new PersonCar() { PersonId = 3, CarId = 1 }
        );

    db.SaveChanges();
}

If I pull back a list of people including cars like below it works and outputs the data:

var drivers = db.Persons.Include("PersonCars.Car").ToList();
foreach(var person in drivers)
{
    foreach(var car in person.Cars)
    {
        Console.WriteLine($"{person.Name} has a {car.Manufacturer}");
    }
}

However, if I try to view the Cars collection results inside debugger then VS2017 crashes which isn't great but at code level it seems to work.

But, lets say I want to filter the list to just be Audi drivers, the below results in 0 results:

var audiDrivers = db.Persons.Include("PersonCars.Car").Where(x => x.Cars.Any(c => c.Manufacturer == "Audi"));

The series of articles is mainly focused on improving adding/removing and doesn't mention filtering. I'd like that functionality but I want to be able to filter the Persons by their Cars.

If I make PersonCars collection public then I can do:

var audiDrivers = db.Persons
    .Include(i => i.PersonCars).ThenInclude(i => i.Car)
    .Where(x => x.PersonCars.Select(pc => pc.Car).Any(c => c.Manufacturer == "Audi"))
    .ToList();

So to summarize:

If navigation property is private like article suggests, can lookup be filtered?

Why does VS2017 crash when looking at Car collection?

Is there a better way to do this that is simple like in EF6?

1
2
2/13/2018 12:37:11 PM

Popular Answer

I personally would work directly with the join entities. It might not be "pretty" but it is the most straight forward approach. Ultimately it will be easier and faster to develop and maintain your code. The fact that you are hitting such basic problems with the alternative approach so early on in your development I think proves it.

My advice is: work with EF Core, not against it. It is not as mature as EF6 but it will get there eventually.

1
2/13/2018 1:24:41 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow