Many to many object to object relation in C#

2020-06-03 08:43发布

问题:

I am working on a small educational project to exercise perst.net.

We have a tiny design problem, that is how to best resolve the relation between two classes of objects, that is the participant and championship. It is a many to many relation as participant can take part in many championships and championships can have a multiple participants.

Please advise how to do this best in C#, should I use the 'relational DB like' technical class?

回答1:

If you're looking for an object oriented (or domain driven design) approach then a third object to handle the 'join' is entirely the wrong way to go about this. You can use ILists / Enumerables with Add... methods to handle this as follows:

public class Participant
{
    private readonly IList<Championship> _championships = new List<Championship>();

    public IEnumerable<Championship> Championships
    {
        get { return _championships; }
    }

    internal void AddChampionship(Championship championship)
    {
        if (!_championships.Contains(championship))
            _championships.Add(championship);
    }
}

public class Championship
{
    private readonly IList<Participant> _participants = new List<Participant>();

    public IEnumerable<Participant> Participants
    {
        get { return _participants; }
    }

    public void AddParticipant(Participant participant)
    {
        if (_participants.Contains(participant)) return;
        _participants.Add(participant);
        participant.AddChampionship(this);
    }
}

The key here is to make sure you manage the relationship from one side only - e.g. in this case that would be by calling championship.AddParticipant()



回答2:

You can have any relationship where a particular item can have multiple items associated with it using IEnumerable (or some other collection) properties. In your example, this would look as follows:

class Participant {
   public IEnumerable<Championship> Championships { 
      get {
         return _championships;
      }
   }
   private List<Championship> _championships;
}

Some important things to keep in mind:

  • Always make this a read-only property. This is sometimes confusing to people, especially if you have something modifiable like an ICollection returned rather than an IEnumerable. Making the property read-only doesn't prevent modification of the collection but modification of the entire list.

  • Loading strategies - You'll notice in the example above the collection isn't initalized. You typically do this either in the constructor or when the property is first accessed (called lazy instantiation) - generally speaking lazy instantiation adds some complexity but can increase performance, especially if this collection is not used often or you have a lot of these sorts of properties.

  • Generally, it's a good idea to pick a class to "hold" the other class in the case of many-to-many (i.e. participants have championships properties but championships do not have participants properties or vice versa). This cuts down on the amount of code you have to write, and reduces complexity and your surface area with your database. If a call is needed for the other direction, consider a method call rather than a property. Remember, many-to-many from a relational sense doesn't necessarily mean that's the common use case (As a user, I might only want to add participants to championships rather than championships to participants)

  • If your collection is modifiable, remember that sending a Save implies that collections underneath it should be modified as well if they were changed. This can add a good deal of complexity (In the past, I've used internal lists to store added / deleted items)



回答3:

I'm not sure if this works for your design or requirements, but it might be possible...

Obviously a many-to-many relationship is easiest to represent with a third object that manages the relationship. In your case it would be a championshipparticipant. Rather than have a collection in your participant class that holds championships and vice versa, have them hold an instance to this third class. The championshipparticipant object manages this relationship. It can then be serialized to the database directly as a junction table.