many-to-many relation not returning any

43 views Asked by At

I have a many-to-many relationship between DeviceModel and ComponentModel.

public class DeviceModel 
{
    public int ID { get; set; }
    public string TagNumber { get; set; } = "";
    public List<ComponentModel> Components { get; set; } = [];
}

public class ComponentModel 
{
    public int ID { get; set; }
    string vendor = "";
    public string Vendor { get => vendor.TrimEnd(); set => vendor = value; }
    string model = "";
    public string Model { get => model.TrimEnd(); set => model = value; }     
    public List<DeviceModel> Devices { get; set; } = [];
}

[PrimaryKey(nameof(ComponentID), nameof(DeviceID))]
public class DeviceComponentModel 
{
    [ForeignKey("Components")]
    public int ComponentID { get; set; }

    [ForeignKey("Devices")]
    public int DeviceID { get; set; }
}

OnModelCreating has

modelBuilder.Entity<ComponentModel>()
    .HasMany(e => e.Devices)
    .WithMany(e => e.Components)
    .UsingEntity<DeviceComponentModel>();

All tables are populated.
But the Components List isn't getting populated when it selects a device.

device = deviceSummaryContext.Devices
    .Where(x => x.ID == deviceID)
    .AsEnumerable()
    .FirstOrDefault(new DeviceModel());

We are trying to follow the conventions spelled out here but we're clearly missing something as neither the unit tests using Effort as an in-memory DB, or live using SQL, populate the lists.

1

There are 1 answers

3
BWhite On

tl;dr; Svyatoslav Danyliv got me pointed in the right direction, but still couldn't get it to work properly. Ended up doing it manually instead.

I tried Lazy (with proxy), Eager, and Explicit loading, but none worked.

Eager uses Include and looks like this:

device = deviceSummaryContext.Devices
    .Where(x => x.ID == deviceID)
    .Include(x => x.Components)
    .AsEnumerable()
    .FirstOrDefault(new DeviceModel());

Lazy can use a proxy, which is injected into the constructor, like so:

private DeviceModel(ILazyLoader lazyLoader)
{
    LazyLoader = lazyLoader;
}

but first it complains that there is no constructor that takes no arguments. After I add one, not surprisingly, it only hits that constructor, not this one.

We tried Explicit loading, as:

 context.Entry(device)
    .Collection(x => x.Components)
    .Load();

None of those worked. What we ended up with was:

List<ComponentModel> components = context.Components
.Join(context.DeviceComponents,
    c => c.ID,
    dc => dc.ComponentID,
    (c, dc) => new { c, dc })
    .Where(x => x.dc.DeviceID == device.ID)
    .Select(x => x.c).ToList();
foreach(var item in components) {
    model.Components.Add(item);
}
return model;

And that worked perfectly fine. ¯\(ツ)