0

I have been searching the NET and SO for some answers - I am pretty much a noob to Entity framework .. so I do have some confusion on related entities .. I am using C# , Winforms, Linq , EF6^ (Generated Reverse DB classes in a DAL project)

I am having trouble getting my head around this problem. I have 3 tables that are related by a fourth table - the 4th table uses the PK_ID columns from the other 3 Tables - configured as foreign Keys and they also make up the Composite Key (Primary Key).

My Crude ER Diagram from paint.

ER Diagram

What I need to do in a EF Query is to get multiple columns for data binding bind to a listview.

The Columns I would like to have are EnumerationCode, Part.Number, Part.Description.

I would be using ProdID (I have Products bound to DropDown ) as the getter for these additional values.

So basically I have this in mind Select From TernaryTable T Join EnumID on ENumTable.EnumID where T.ProdID = myProdID order by EnumCode [Join this to Select Part.Number, Part.Description From Parts where PartID = TernaryTable.PartID )

I think there is probably a simple solution for how to get this data and then how to save any changes to the data - but as I said I am lost.

I need 2 methods , 1 to get the data, and the other to save any changes that occur to the data.

I have read this

Get records from table and related related table using Linq to entity

Entity framework linq query Include() multiple children entities

but seems I am still lost on the multiple relationship.

EDIT

I think after Haralds Excellent reply I need and can post a better statement and also a real diagram!

Better SQL Diagram

And now for table information :

I would like to have MprnEnum.RefName, Part.PartNumber, Part.Name, Part.Description Where ProductID = [Product.ID - this ID I would provide] Ordered By MPRNENUM.RefName. I would like to be able to perform CRUD on the ProdPartEnum using my entity classes generated by VS EF from DB.

There are 20 RefNames, Each Product has many EnumID's and EnumIDs can belong to many Products, each of these ProdEnums has one part associated. Many parts can belong to many ProdEnums

A typical table entry might look like this: ProdID = 1, EnumID = [1-20] , PartID [1-1000] Where product ID = 1 , we have 20 rows of EnumID's and each of those Rows would have one PartNumber .

I can post more details if need be.

Ken
  • 1,711
  • 2
  • 16
  • 29

1 Answers1

3

My, this is a strange many-to-many-to-many!

Normally one would have the following design:

  • Every Product has zero or more Parts, Every Part is used in zero or more Products (many-to-many ProductPart)
  • Every Product has zero or more Enumerations, Every Enumeration is used by zero or more Products (many-to-many ProductEnumeration)
  • Every Part has zero or more Enumerations, Every Enumeration is used in zero or more Parts (many-to-many EnumerationPart)

This would lead to three junction tables ProductPart, ProductEnumeration, and EnumerationPart

You chose for a design with only one junction table.

You do realize that in your design, that as soon as a Product has a Part, that the Product and the Part both have the same Enumeration, don't you? You can't have a product with more or other Enumerations than the Parts of the product. You can't have a Product with a Part but without Enumerations.

If you really want to limit your database to this, you'll have classes like this:

class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Junction> Junctions { get; set; }
}
class Part
{
    public int Id { get; set; }
    public int Number { get; set; }
    public string Description { get; set; }
    public virtual ICollection<Junction> Junctions { get; set; }
}
class Enumeration
{
    public int Id { get; set; }
    public int Code { get; set; }
    public virtual ICollection<Junction> Junctions { get; set; }
}

Your junction table will be like:

public class Junction
{
    // foreign keys; composite primary key
    public int ProductId { get; set; }
    public int EnumId { get; set; }
    public int PartId { get; set; }

    public virtual Product Product { get; set; }
    public virtual Enumeration Enumeration { get; set; }
    public virtual Part Part { get; set; }
}

Be aware: In entity framework the non-virtual properties represent the actual columns in your tables; the virtual properties represent the relations between the tables

Your DbContext will have your four tables:

class ManyDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }
    public DbSet<Enumeration> Enumerations { get; set; }
    public DbSet<Part> Parts { get; set; }
    public DbSet<Junction> Junctions {get; set;}
}

Finally in OnModelCreating you'll have to specify your design:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    var junctionConfig = modelBuilder.Entity<Junction>();

    // composite primary key:
    junctionConfig.HasKey(junc => new { junc.ProductId, junc.PartId, junc.EnumId);

    // every junctionconfig has a mandatory foreign key to Product in ProductId
    // which represent a one-to-many (one Product has many Junctions)
    junctionConfig.HasRequired<Product>(junc => junc.Product)
        .WithMany(product => product.Junctions)
        .HasForeignKey<int>(junc => junc.ProductId);

    // every junctionconfig has a mandatory foreign key to Enumerations in EnumerationId
    // which represent a one-to-many (one Enumeration has many Junctions)
    junctionConfig.HasRequired<Enumeration>(junc => junc.Enumeration)
        .WithMany(enumeration => enumeration.Junctions)
        .HasForeignKey<int>(junc => junc.EnumId);

    // every junctionconfig has a mandatory foreign key to Pars in PartId
    // which represent a one-to-many (one Part has many Junctions)
    junctionConfig.HasRequired<Part>(junc => junc.Part)
        .WithMany(part => part.Junctions)
        .HasForeignKey<int>(junc => junc.PartId);

    base.OnModelCreating(modelBuilder);
}

Back to your question

Given a productId, give me all EnumerationCodes, Part.Number, Part.Description records for the Product with this ProductId

When using entity framework, people tend to perform joins on foreign keys, instead of using virtual properties in the tables, making things more complicated then needed.

If you'd use the virtual properties the query would be easy and very intuitive:

var result = dbContext.Junctions
    .Where(junction => junction.ProductId == productId)
    .Select(junction => new
    {
        EnumerationCode = junction.Enumeration.EnumerationCode,
        PartNumber = junction.Part.Number,
        PartDescription = junction.Part.Description,
    });

Entity Framework is smart enough to detect which joins are needed.

If you really want to do the join you'll have to do a join with three tables. Such a join looks horrific:

var x = dbContext.Junctions                              // from all junctions
    .Where(junction => junction.ProductId == productId)  // take only the ones with productId

                                                          // The first join:
    .Join(dbContext.Parts,                                // Join with Parts
        junction => junction.PartId,                      // from the Junction take the PartId,
        part => part.Id,                                  // from the Parts take the Id
        (junction, part) => new                           // when they match make one new object
        {                                                 // containing some properties
            EnumerationId = junction.EnumId,
            PartNumber = part.Number,
            PartDescription = part.Description,
        })

                                                          // Second Join
        .Join(dbContext.Enumerations,                     // Join with enumerations
            junction => junction.EnumerationId,           // from the 1st join result take EnumerationId
            enumeration => enumeration.Id,                // from enumerations take Id
            (junction, enumeration) => new                // when they match make one new object
            {                                             // containing your desired properties
                EnumerationCode = enumeration.Code,
                PartNumber = junction.PartNumber,
                PartDescription = junction.PartDescription,
            });

You are lucky you don't want the product's Description. If you'd use the virtual properties this would be easy:

var result = dbContext.Junctions
    .Where(junction => junction.ProductId == productId)
    .Select(junction => new
    {
        Description = junction.Product.Description,
        EnumerationCode = junction.Enumeration.EnumerationCode,
        PartNumber = junction.Part.Number,
        PartDescription = junction.Part.Description,
    });

Up to you to write a join with four tables. See the difference and decide which method you want to use from now on.

Harald Coppoolse
  • 24,051
  • 6
  • 48
  • 92
  • Thank you for your complete answer this does help me. Yes the relations are strange (maybe I have part of it wrong) because every [Product / Part ] will have one enumeration associated with it, ProductPart + Enum that goes with it. I am not sure if the entities were created with navigation properties, I did not see that; they look like decorated POCO's. I used VS EF code first from DB to generate them. – Ken Aug 21 '18 at 12:28
  • After your comment I updated my question with a proper diagram and also a better clarification of the model. I think the update is a more clear description. – Ken Aug 21 '18 at 16:50
  • I like the Navigation properties - my mind was thinking SQL. This gets the data I want and I looked into OrderBy and (p => p.EnumerationCode) works. I am all set I think - and I should be able to make changes to the data and save it - not sure how all the plumbing makes that happen when calling save changes - but I am hoping it is straight forward. – Ken Aug 21 '18 at 19:35