0

[I'm surprised that I didn't find an existing question with an answer to this conceptual question. Maybe I just didn't search the right keywords.]

Consider an entity type "Singer" with some of its own properties. Next, consider an entity type "Quartet" that has exactly four Singers in it, assigned to the roles "Tenor", "Lead", "Baritone", and "Bass" in the quartet.

The simplest way to model this code first is to just add four Singer navigation properties to the Quartet entity class, with the names of the four singer roles in that quartet. This would lead to a Quartet table and a Singers table with four rows with foreign keys referencing the quartet, and we have a one-to-four relationship. Not horrible, and this works, but it leads to some awkwardness in the program later.

There are numerous operations that would either need to be iterated over the four singers and done for each one, or conditional queries that need to operate on just one of the singers, depending on the value of some external enum that indicates which singer to act on.

Ideally, instead of having four separate Singer navigation properties, then, I'd like to have an array of Singers with a fixed size of four, where the elements of the array correspond directly to the enum values, and I could iterate over the array or go directly to a specific element based on the enum.

But this doesn't seem to model well in EF with SQL Server.

How can I accomplish what I need here?

  • Just supply a singer with Enum - SingerType. Then there will be two navigation properties. From Singer to Quartet and from Quartet to Array of Singers. That's all. :) Your restriction that quartet should have 4 singer at max is a part of business logic, rather that part of Data Access logic. You cannot say to SQL that you have one to four relation, better you can say that you have one to many relation and implement restriction on BL side. – Maris Apr 14 '18 at 07:48
  • Also Quartet table is a bad design, from my point of view. What will happen if you will need to implement quintet or trio as well? You will have to introduce additional Tables - Quintet and Trio. Better make a table called SingerGroup, with a singer group type. – Maris Apr 14 '18 at 07:51
  • have you succeeded? – Maris Apr 17 '18 at 06:47
  • Not yet, @Maris. In this case, Quartet is perfect design. It was always be exactly four. This is a Barbershop quartet activity :) I will have logic that needs to iterate and repeat over the exactly four parts (Tenor, Lead, Baritone, Bass) and do the same for each. I also have logic that currently says "if it's the Bass part, then use the Bass singer, else if ...." and that's very awkward. – Scot Brennecke Apr 17 '18 at 15:03
  • Once more, it's better to distinguish singer on level of Business logic rather then Data Access layer. – Maris Apr 18 '18 at 07:44
  • Maris, you are not helping me to answer the question. Instead, you are just quibbling about philosophical questions. This doesn't help me write any less awkward code. – Scot Brennecke Apr 18 '18 at 13:58
  • To write or not to write a shitty code is up to you. But can you specify why my first comment doesn't answer your question, what is a issue you are facing? – Maris Apr 19 '18 at 06:48
  • So ok, I wrote a code sample, take a look into my answer. – Maris Apr 19 '18 at 07:12

1 Answers1

3

Consider a next code-sample:

public class Singer {
    public int Id { get; set; }
    public string Name { get; set; }
    public SingerType SingerType { get; set; }
    public virtual Quartet Quartet { get; set; }
}

public class Quartet {
    public int Id { get; set; }
    public virtual List<Singer> Singers { get; set; }
    public string Name { get; set; }

    public Singer GetByType(SingerType type) {
        return Singers.FirstOrDefault(e => e.SingerType == type);
    }

    public void AddSinger(Singer singer) {
        if (Singers.Any(e => e.SingerType == singer.SingerType)) {
            throw new Exception($"You cannot add additional-{singer.SingerType} to quartet->{this.Name}");
        }
        if (Singers.Count > 3) {
            throw new Exception($"You cannot add additional singer->{singer.Name} to quartet->{this.Name} cause quartet already more than 3 members");
        }
        Singers.Add(singer);
    }
}

public class MyFancyClass {
    private readonly Context _context;

    public MyFancyClass(Context context) {
        _context = context;
    }

    public Quartet DoWhatEverWithQartet(string name) {
        var myQuartet = _context.Quartets.FirstOrDefault(e => e.Name == name);
        foreach (var singer in myQuartet.Singers) {
            // Do whatever logic with singer
        }
        return myQuartet;
    }

    public void DoWhatEverLogicWithBassOfQuartet(string name) {
        var myQuartet = _context.Quartets.FirstOrDefault(e => e.Name == name);
        var bass = myQuartet.GetByType(SingerType.Bass);
        // Do whatever logic with bass
    }
}

public enum SingerType {
    Unknown = 0,
    Bass = 1,
    Bariton = 2,
    Lead = 3,
    Tenor = 4,
    Bullshit = 5,
    WhatEver = 6,
}

public class Context : DbContext {

    public IDbSet<Singer> Singers { get; set; }
    public IDbSet<Quartet> Quartets { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder) {
        modelBuilder.Configurations.Add(new QuartetConfiguration());
        base.OnModelCreating(modelBuilder);
    }
}

public class QuartetConfiguration : EntityTypeConfiguration<Quartet> {
    public QuartetConfiguration() {
        HasKey(e => e.Id);
        HasMany(e => e.Singers).WithRequired(e => e.Quartet).WillCascadeOnDelete(false);
    }
}

In that case you have restriction on how much singers quartet will have on BL level, and you have a capability to iterate over all singers in Quartet.

Maris
  • 4,608
  • 6
  • 39
  • 68
  • Thank you for making this extra effort to show an example of your suggestion, Maris. I see that it does most of the things I need -- associates all of the singers with the quartet, ensures there is no more than one of each part, allows me to query for a singer of any given part, and to iterate over all singers in the list. But I have two issues with this model: 1. It does not guarantee that I have exactly one of each part. 2. It requires that I query the list just to access a specific member. – Scot Brennecke Apr 19 '18 at 13:28
  • How is it better that I execute a query to access a member? Why is it better to have a list, when it is a REQUIRED attribute of the quartet to have EXACTLY four members, one of each part? – Scot Brennecke Apr 19 '18 at 13:30
  • @ScotBrennecke You can extend the business logic that I have wrote with your restrictions: 1) At the moment of saving new quartet - ensure that it has one of each type of member. 2) What is the problem of querying a single quartet member by(for instance) - "quartet name" and "singer type"? – Maris Apr 23 '18 at 05:35
  • @ScotBrennecke Answering on your second comment - "Because it is a bad database design"! Instead of having one foreign key on Singer level you will have four foreign keys on level of quartet. – Maris Apr 23 '18 at 05:39
  • To proof my opinion I can ask you a question - "Why you ended up here, asking your question?". The main reason is - "Your model are not flexible enough". Also keep in mind that stackoverflow is not a platform where you can post a - "do everything for me" request, it is a platform where you can ask one specific question and receive an answer. Of-course my answer is not production ready, and you have to do additional work effort to meet your needs, it just shows how to achieve a behavior that you have described. – Maris Apr 23 '18 at 05:49
  • Although I appreciate you taking the time to write that code to share your philosophy, I did not ask for someone to "do everything for me". All I asked for is an answer to my question. What I have gotten from you is mostly philosophical answers about why you think my design sucks. I'm sorry that it offends your sensibilities, but your answer is not real-world helpful. – Scot Brennecke Apr 23 '18 at 13:44
  • @ScotBrennecke Ok, answering your question. No, you cannot have 1 to 4 relationship with a possibility to iterate over them. You can have 1 to many relationship, in that case you will be able to iterate over your singers. Is it an answer to your question? – Maris May 03 '18 at 11:25