According to DDD principles, external objects should only call methods on an aggregate root, not on other entities in the aggregate, right ?
In case of nested entities, for example: SeatingPlan -> Sections -> Rows -> Seats
SeatingPlan is the aggregate root, while sections, rows and seats are entities that are meaningless outside its parent entity.
Lets say I want to add seats in the seating plan.
I would create SeatingPlan.AddSeat(sectionId, rowId, seatNo) in order to prevent external objects to call SeatingPlan.Sections[x].Rows[y].Seat[s].Add, which is bad, right ?
But still, the AddSeat method of SeatingPlan must delegate the seat creation to the row object, because the seat is a composite of the row, the row owns the seats. So it has to call Sections[x].Rows[y].AddSeat(seatNo).
Now my question is how can I prevent external objects from calling Row.AddSeat method, while allowing the aggregate root to call it ?
internal visibility is too large, even namespace visibility (assuming it would even exists in c#) would be too large. I need an aggregate visibility.
I thought about nesting the class Row in the SeatingPlan class, and making the Row.AddSeat method private. But is it a good practice ? Because the class would have to be public and I remember having read something about it saying that we should avoid public nested classes.
Conflicting domain model role: commands vs queries
I'm suspecting that the reason you allowed external deep navigation of your aggregate root is for querying needs and that's what's causing issues. If you could avoid exposing entities outside the root then this problem goes away. You must not forget that the primary role of the domain model is to process commands while protecting invariants; not fulfilling query needs.
A single model can't be optimized for both, commands & queries. When the model is starting to fail you in one of the two roles it may be time to segregate them. That's called Command Query Responsibility Segregation (CQRS). You'd by-pass the domain model entirely for queries and go straight to the DB, after which you could get rid of most state-exposing members in your aggregate roots.
CQRS scares me...
If you dont want to go that route then you will have to live with the pains of having a single model stretched in different directions. One thing you could do to mitigate the problem you described is to use readonly interfaces, such as IRowView which do not expose any mutating methods. Alternatively, you could also return value objects describing the state of the sub-entity, such as RowDescriptor or RowState. What you will start to realize however is that you will start being forced to invent new concepts that dont exist in your Ubiquitous Language, just to solve technical problems (e.g. preserve encapsulation).
Beware large aggregate roots
The Stadium AR seems to be very large as a consistency boundary. That's usually a good indicator that the boundaries might be wrong. You shouldn't attempt to model the real world: just because a stadium contains sections which have rows, etc., in the real world doesn't mean your model should be composed that way.
Also, dont rely on the rule "A can't exist or doesn't make sense without B" to model aggregates. It's does more harm than good most of the time.
Since that's not the core of the question I'll just leave you to read this excellent Vaughn Vernon article, Effective Aggregate Design.
Firstly I would point out that DDD is a set of guidelines, not rules. Do whatever makes sense in your situation, don't just follow DDD blindly.
That said you can use interfaces/base classes to do what you want. Here is a simple example.
public interface IRow
{
IReadOnlyList<Seat> Seats {get;}
}
public class Stadium
{
private List<Row> _rows = new List<Row>();
public IReadOnlyList<IRow> Rows => _rows;
public void AddSeat(Seat seat, int rowNum) => _rows[rowNum].AddSeat(seat);
private class Row : IRow
{
private List<Seat> _seats = new List<Seat>();
public IReadOnlyList<Seat> Seats => _seats;
public void AddSeat(Seat seat) => _seats.Add(seat);
}
}
In java, I protect the access to internal objects of an aggregate by the scope of those objects. I structure the code so that each aggregate is in a package , named with aggregate name. Internal entities and value objects would be package scoped (no visibility scope keywords when defining them), so that those objects are just accessible from the same package. The aggregate root entity would be public.
According to DDD principles, external objects should only call methods on an aggregate root (AR), not on other entities in the aggregate
Id rather say that an aggregate root is a consistency boundary. That's why "external objects should only call methods on the aggregate root".
On the other hand your value objects (VOs) or entities can be quite rich and encapsulate a lot of their internal rules.
E.g SeatNumber cannot be negative, Seat can have a method Book(Person person) that makes sure that it's booked by only one person, Row can have methods BookASeat(SeatNumber seatId, Person person) and AddASeat(Seat seat), ...
public class Seat : Entity
{
private Person _person;
public Seat(SeatNumber id)
{
SeatId = id;
}
public SeatNumber SeatId { get; }
public void Book(Person person)
{
if(_person == person) return;
if (_person != null)
{
throw new InvalidOperationException($"Seat {SeatId} cannot be booked by {person}. {_person} already booked it.");
}
_person = person;
}
public bool IsBooked => _person != null;
}
I would create SeatingPlan.AddSeat(sectionId, rowId, seatNo) in order to prevent external objects to call SeatingPlan.Sections[x].Rows[y].Seat[s].Add, which is bad, right?
But still, the AddSeat method of SeatingPlan must delegate the seat creation to the row object, because the seat is a composite of the row, the row owns the seats. So it has to call Sections[x].Rows[y].AddSeat(seatNo).
It's not bad to call Sections[sectionNumber].Rows[rowNo].Seat[seat.SeatNo].Add(seat) as long as Sections is a private collection (dictionary) and SeatingPlan doesn't expose it to an outside world.
IMHO: A disadvantage of this approach is the following - all domain rules are maintained by your aggregate root. It cam make you aggregate root too complex too understand or maintain.
In order to keep your aggregate simple I'd recommend to split into multiple entities and make each of them responsible for enforcing their own domain rules:
Row is responsible for maintaining an internal list of its seats, has methods AddASeat(Seat seat) and BookASeat(SeatNumber seatId, Person person)
Section is responsible for maintaining an internal list of rows, knows how to add an entire valid row (AddARow(Row row)) or just to add a seat to an existing row (AddASeat(RowNumber rowId, Seat seat))
Stadium (or a seat plan) can have methods like AddASection(Section section), AddARow(Row row, SectionCode sectionCode), AddASeat(Seat seat, RowNumber rowNumber, SectionCode sectionCode). It all depends on the interface that you provide to your users.
You can describe your aggregate root without exposing internal collections:
/// <summary>
/// Stadium -> Sections -> Rows -> Seats
/// </summary>
public class Stadium : AggregateRoot
{
private readonly IDictionary<SectionCode, Section> _sections;
public static Stadium Create(StadiumCode id, Section[] sections)
{
return new Stadium(id, sections);
}
public override string Id { get; }
private Stadium(StadiumCode id, Section[] sections)
{
_sections = sections.ToDictionary(s => s.SectionId);
Id = id.ToString();
}
public void BookASeat(SeatNumber seat, RowNumber row, SectionCode section, Person person)
{
if (!_sections.ContainsKey(section))
{
throw new InvalidOperationException($"There is no Section {section} on a stadium {Id}.");
}
_sections[section].BookASeat(row, seat, person);
}
public void AddASeat(Seat seat, RowNumber rowNumber, SectionCode sectionCode)
{
_sections.TryGetValue(sectionCode, out var section);
if (section != null)
{
section.AddASeat(rowNumber, seat);
}
else
{
throw new InvalidOperationException();
}
}
public void AddARow(Row row, SectionCode sectionCode)
{
_sections.TryGetValue(sectionCode, out var section);
if (section != null)
{
section.AddARow(row);
}
else
{
throw new InvalidOperationException();
}
}
public void AddASection(Section section)
{
if (_sections.ContainsKey(section.SectionId))
{
throw new InvalidOperationException();
}
_sections.Add(section.SectionId, section);
}
}
public abstract class AggregateRoot
{
public abstract string Id { get; }
}
public class Entity { }
public class ValueObject { }
public class SeatNumber : ValueObject { }
public class RowNumber : ValueObject { }
public class SectionCode : ValueObject { }
public class Person : ValueObject { }
public class StadiumCode : ValueObject { }
public class Row : Entity
{
private readonly IDictionary<SeatNumber, Seat> _seats;
public Row(RowNumber rowId, Seat[] seats)
{
RowId = rowId;
_seats = seats.ToDictionary(s => s.SeatId);
}
public RowNumber RowId { get; }
public void BookASeat(SeatNumber seatId, Person person)
{
if (!_seats.ContainsKey(seatId))
{
throw new InvalidOperationException($"There is no Seat {seatId} in row {RowId}.");
}
_seats[seatId].Book(person);
}
public bool IsBooked(SeatNumber seatId) { throw new NotImplementedException(); }
public void AddASeat(Seat seat)
{
if (_seats.ContainsKey(seat.SeatId))
{
throw new InvalidOperationException();
}
_seats.Add(seat.SeatId, seat);
}
}
public class Section : Entity
{
private readonly IDictionary<RowNumber, Row> _rows;
public Section(SectionCode sectionId, Row[] rows)
{
SectionId = sectionId;
_rows = rows.ToDictionary(r => r.RowId);
}
public SectionCode SectionId { get; }
public void BookASeat(RowNumber rowId, SeatNumber seatId, Person person)
{
if (!_rows.ContainsKey(rowId))
{
throw new InvalidOperationException($"There is no Row {rowId} in section {SectionId}.");
}
_rows[rowId].BookASeat(seatId, person);
}
public void AddASeat(RowNumber rowId, Seat seat)
{
_rows.TryGetValue(rowId, out var row);
if (row != null)
{
row.AddASeat(seat);
}
else
{
throw new InvalidOperationException();
}
}
public void AddARow(Row row)
{
if (_rows.ContainsKey(row.RowId))
{
throw new InvalidOperationException();
}
_rows.Add(row.RowId, row);
}
}
how can I prevent external objects from calling Row.AddSeat method, while allowing the aggregate root to call it ?
If you do not expose a Row or Rows as public property it automatically prevents others from calling it. E.g. in my example only Section has access to its own private collection of _rows and calls method AddSeat on a single row.
If you keep a state of the aggregate root private to itself it means that it can be changed through aggregate root methods only.
Related
I am playing with the Object Calisthenics rules and I am having some troubles to see when to use the first class collections when using C#.
I mean I hardly see when it's supposed to be used, for example it would be hard to apply that rule to an EF DbContext.
Let's say, we design a Board class.
public class Board
{
public IList<BoardRow> Rows { get; }
public IList<BoardColumn> Columns { get; }
public Board()
{
Rows = new List<BoardRow>();
Columns = new List<BoardColumn>();
}
}
So according to that rule, we would have to turn the code above into:
// Is it really that better than just using List<BoardRow>?
public class BoardRowCollection : IEnumerable<BoardRow>
{
public void Add(BoardRow row) { /*...*/ }
public void Remove(BoardRow row) { /*...*/ }
// IEnumerable<BoardRow> Impl goes here...
}
// Is it really that better than just using List<BoardColumn>?
public class BoardColumnCollection : IEnumerable<BoardColumn>
{
public void Add(BoardColumn column) { /*...*/ }
public void Remove(BoardColumn column) { /*...*/ }
// IEnumerable<BoardRow> Impl goes here...
}
public class Board
{
public BoardRowCollection Rows { get; }
public BoardColumnCollection Column { get; }
// Rest of the impl, ctor, etc.
}
I am not really sure to get the point of this rule when you already have base classes that can be leveraged to achieve your goals.
Maybe the code above is not the best but I would like to see one example which can shed the light on the purpose of that rule.
Background
Assume you have a class Foo and for any reason it needs Rows from Board.
Now Foo needs to find the 5th item in Rows. Some days later you need a class Buu and it should find the 8th item in Rows.
Both Foo and Buu have its own implementation on how to find an item in Rows.
// ...
Foo foo = new Foo(board.getRows());
Buu buu = new Buu(foo.getRows());
BoardRow row5 = foo.find5thRow();
BoardRow row8 = buu.find8thRow();
Only a Collection itself should know how to do operations on it
From the Objects Calisthenics:
Application of this rule is simple: any class that contains a collection should contain no other
member variables. Each collection gets wrapped in its own class, so now behaviors related to
the collection have a home. You may find that filters become a part of this new class. Also,
your new class can handle activities like joining two groups together or applying a rule to each
element of the group.
If we would create an First Class Collection for Rows we could pass an instance of it to Foo and Boo and call a method on it:
class Foo {
RowCollection rowCollection;
// constructor and more ...
public BoardRow find5thRow() {
rowCollection.findByIndex(5);
}
}
Summary
A First Class Collection should cover operations like create, read, update, delete, filtering, merging and more. Pass an instance of a First Class Collection instead of the collection it self. The advantage is that you only have to delegate methods to your First Class Collection instead of writing a new copy of an operation.
I have a summary objects, who's responsibilities actually to combine a lot of things together and create a summary report, who later going to be serialized into the XML.
In this objects I have a lot of structures like this:
public class SummaryVisit : Visit, IMappable
{
public int SummaryId { get; set; }
public int PatientId { get; set; }
public int LocationId { get; set; }
public IMappable Patient
{
get
{
return new SummaryPatient(PatientBusinessService.FindPatient(this.PatientId));
}
}
public IMappable Location
{
get
{
return new SummaryLocation(LocationBusinessService.FindLocation(this.LocationId));
}
}
public IEnumerable<IMappable> Comments
{
get
{
return new SummaryComments(CommentBusinessService.FindComments(this.SummaryId, Location));
}
}
// ... can be a lot of these structures
// ... using different business services and summary objects
public IEnumerable<IMappable> Tasks
{
get
{
return new SummaryTasks(TaskBusinessService.FindTasks(this));
}
}
}
PatientBusinessService, LocationBusinessService etc. are statics.
And each of these SummaryPatient, SummaryLocation etc. have the same type of structure inside.
What is the best approach to refactor and unit test this?
Tried to replace static calls with calls via the interfaced proxies (or refactor statics to non-static classes & interfaces), but this class just got a lot of these interfaces as the constructor injection stuff and start to be super greedy. In addition, these interfaces have a one used method inside (if I going to create it just to this summary needs).
And as soon as this is a summary object, commonly this static services used just once for the whole structure to get appropriate properties for output.
You could change your tests to be more integrational (test more than one class at the time). You could try to modify your services to be more universal and be able to take data from different sources (like TestDataProvider and your current data provider).
Better solution I think is to modify classes you want to test:
Use strong typing for properties and gain all benefits. I think you should return more specific types instead of IMappable
It looks like some of your data is stored inside class (ids) some data is not (IMappable object references). I would refactor this to hold references to objects inside class:
private SummaryPatient _patient;
public SummaryPatient Patient
{
get
{
if (_patient == null)
_patient = new SummaryPatient(PatientBusinessService.FindPatient(this.PatientId));
return _patient;
}
}
Then you can assign your tests data in constructor or create static method CreateDummy(...) just for unit tests. This method then should use CreateDummy for child objects. You can use it in your unit tests.
I have an entity called "Set" which contains Cards. Sometimes I want to see the entire card and its contents (card view), when sometimes I just want to know how many cards are in the Set (table views). In my effort to keep things DRY, I decided to try and re-use my SetDto class with multiple constructors like this:
public class SetDto
{
public SetDto()
{
Cards = new List<CardDto>();
}
// Called via SetDto(set, "thin")
public SetDto (Set set, string isThin)
{
var setDto = new SetDto()
{
SetId = set.SetId,
Title = set.Title,
Details = set.Details,
Stage = set.Stage,
CardCount = set.Cards.Count
};
return setDto;
}
// Called via SetDto(set)
public SetDto(Set set)
{
SetId = set.SetId;
UserId = set.UserId;
Title = set.Title;
Details = set.Details;
FolderId = set.FolderId;
Stage = set.Stage;
IsArchived = set.IsArchived;
Cards = new List<CardDto>();
foreach (Card card in set.Cards)
{
Cards.Add(new CardDto(card));
}
}
/// property definitions
I originally had two different DTOs for sets - ThinSetDto and FullSetDto - but this seemed messy and tougher to test. Does the above solution seem ok, or am I breaking a known best-practice? Thank you for your time!
I would create three methods in the SetManager class (a class handling CRUD operations) not in the DTO.
The dto shold have no such a logic inside. Anyway I agree with you that the replication is useless (and evil).
public class BaseSetDTO
{
public BaseSetDTO()
{
Set();
}
internal virtual void Set()
{
//Do your base set here with base properties
}
}
public class SetDTO : BaseSetDTO
{
internal override void Set()
{
//Do a full set here
}
}
Create a base class, then let your types handle what they are supposed to set. Create a new on for your ThinSetDTO and override again.
Instead, I would prefer extension method by declaring all properties in Set class and modifying the properties by passing required parameters. Otherwise initialize a baseDTO and have various versions by adding required properties and call extension method to create required version DTO and return baseDTO.
public static Set SetDto(this Set set, bool isThin)
{
if(isThin)
{
}
return objSet;
}
A common solution to this is to have the repository (or equivalent) return the 'flavor' of the DTO/entity you want by either having different access methods ie: Get() ... GetSet(), or to enumerate your 'flavors' of the entity in question and pass that to your 'Get' (or equivalent) method ie:
enum ContactCollectionFlavors { Full, CountOnly, CountWithNames .... }
...
foo = ContactRepository.GetByLastName('Jones', ContactCollectionFlavors.CountWithNames);
This can get a little messy, from experience the entity in question should have some way of knowing what 'flavor' it is, which smells bad since it breaks encapsulation and seperation of concerns - but in my opinion its better hold your nose and keep some out of band data, so that later you can have lazy loading of the entity allowing you to turn 'light flavors' into fully populated entities.
I want to add metadata to my object graph for non-domain type data that will be associated to my objects but is not essential to the problem set of that domain. For example, I need to store sort settings for my objects so that the order in which they appear in the UI is configurable by the user. The sort indices should be serializable so that the objects remember their positions. That's just one among a few other metadata items I need to persist for my objects. My first thought is to solve this by having a MetadataItem and a MetadataItemCollection where the base Entity class will have a "Meta" property of type MetadataItemCollection. E.g.:
public class MetadataItem
{
public string Name;
public object Data;
}
public class MetadataItemCollection
{
/* All normal collection operations here. */
// Implementation-specific interesting ones ...
public object Get(string name);
public MetadataItem GetItem(string name);
// Strongly-type getters ...
public bool GetAsBool(string name);
public string GetAsString(string name);
// ... or could be typed via generics ...
public T Get<T>(string name);
}
public class Entity
{
public MetadataItemCollection Meta { get; }
}
A few concerns I can think of are:
Serialization - the database has a single table of EntityID | Name | Value where Value is a string and all types are serialized to a string?
Future Proofing - what if a metadata item's type (unlikely) or name needs to be changed?
Refactorability - should the keys come from a static list via enum or a class with static string properties, or should free-form strings be allowed:
var i = entity.Meta["SortIndex"];
vs.
public enum Metadatas { SortIndex };
var i = entity.Meta[Metadatas.SortIndex];
vs.
public static class Metadatas
{
public static string SortIndex = "SortIndex";
}
var i = entity.Meta[Metadatas.SortIndex];
Anything else?
Thoughts, ideas, gotchas???
Thanks for your time.
Solution:
Following #Mark's lead, and after watching the Udi video Mark linked to, I created two new interfaces: IUiPresentation and IUiPresentationDataPersistor. It's important to note that none of the objects in my Entity object model have any awareness of these interfaces; the interfaces are in a separate assembly and never referenced by my Entity object model. The magic is then done via IoC in the presentation models. It would be something like the following:
public class PhoneViewModel
{
IUiPresentationDataPersistor<Phone> _uiData
IUiPresentation<Phone> _presenter;
// Let IoC resolve the dependency via ctor injection.
public PhoneViewModel(Phone phone, IUiPresentationDataPersistor<Phone> uiData)
{
_uiData = uiData;
_presenter = uiData.Get(phone); // Does a simple lookup on the phone's ID.
}
public int SortIndex
{
get { return _presenter.SortIndex; }
set { _presenter.SortIndex = value; }
}
public void Save()
{
_uiData.Save();
}
}
It's a little more complicated in that the ViewModel implements INotifyPropertyChanged to get all the goodness that it provides, but this should convey the general idea.
Metadata literally means data about data, but what you seem to be asking for is a way to control and change behavior of your objects.
I think such a concern is much better addressed with a Role Interface - see e.g. Udi Dahan's talk about Making Roles Explicit. More specifically, the Strategy design pattern is used to define loosely coupled behavior. I'd look for a way to combine those two concepts.
As we already know from .NET, the use of static, weakly typed attributes severely limits our options for recomposing components, so I wouldn't go in that direction.
Don't be scared of the extensive code. The problem is general. I just provided the code to understand the problem better.
I am trying to find out a standard approach of manipulating tables with many-to-many relationships. And I am almost done. Here Teacher and Course have M:M relationship. I have designed my classes as follows:
Teacher - class:
public class Teacher
{
public int ID{get;set;}
public string TeacherName{get;set;}
private List<Course> _items = null;
public List<Course> Items
{
get
{ if (_items == null) {_items = Course.GetCoursesByTeacherID(_ID);}
return _items;
}
set {_items = value;}
}
public int Save()
{ //...
CourseTeacher.DeleteCoursesByTeacherID(tc, id);
CourseTeacher.SaveCoursesWithTeacherID(tc, id, this.Items);
//...
}
public bool Update()
{ //...
CourseTeacher.DeleteCoursesByTeacherID(tc, this.ID);
CourseTeacher.SaveCoursesWithTeacherID(tc, this.ID, this.Items);
//...
}
public static Teacher Get(int id)
{ //...
item.Items = CourseTeacher.GetCoursesByTeacherID(tc, item.ID);//...
}
public static List<Teacher> Get()
{ //...
items[i].Items = CourseTeacher.GetCoursesByTeacherID(tc, items[i].ID);//...
}
public static List<Teacher> GetTeachersByCourseID(int id)
{ //...
items = CourseTeacher.GetTeachersByCourseID(tc, id);//...
}
public bool Delete()
{ //...
CourseTeacher.DeleteCoursesByTeacherID(tc, this.ID);//...
}
}
Course is absolutely similar to Teacher - class. And the mapping class is as follows:
public class CourseTeacher
{
public int CourseID{get;set;}
public int TeacherID{get;set;}
public static void SaveCoursesWithTeacherID(TransactionContext tc, int teacherID, List<Course> items){}
public static void SaveTeachersWithCourseID(TransactionContext tc, int courseID, List<Teacher> items){}
private void Save(TransactionContext tc){}
public static void DeleteCoursesByTeacherID(TransactionContext tc, int teacherID){}
public static void DeleteTeachersByCourseID(TransactionContext tc, int courseID){}
public static List<Teacher> GetTeachersByCourseID(TransactionContext tc, int courseID){}
public static List<Course> GetCoursesByTeacherID(TransactionContext tc, int teacherID){}
}
Now my problem is, this code is not working?
Teacher professorXyz = Teacher.Get(2);
Course cpp = Course.Get(3);
Course java = Course.Get(2);
professorXyz.Items.Remove(cpp);
professorXyz.Items.Remove(java);
professorXyz.Update();
This is not working because it is not probably finding a match or get accessor is returning readonly List.
How should I refactor my Teacher/Course - class to achieve this?
No exception. No problem with persistence code. Items are not being removed.
why professorXyz.Items.Contains(cpp); is returning false?
What to check for?
This is not a direct answer, but...
Your design is very (very) Relational. That makes persisting to a DB easier but you do not have a proper OO model. Maybe you should consider using DataTables in a DataSet and reap the benefits of the Relation class.
To take a shot:
Teacher professorXyz = Teacher.Get(2);
Course cpp = Course.Get(3);
I suspect that the cpp course is being loaded twice, and that there are 2 instances of that course in memory. A very bad consequence of your design. By default, those 2 instances will not be equal and that is why Remove does not work. You could overload Equals, == and GethashCode but that is not recommended for mutable types.
What you really need is a design where for a given Teacher or Course there never exists more than 1 instance in memory.
Re Comment: A MxM relation in OO looks like:
class Teacher
{
public readonly List<Course> Courses = ...;
}
class Course
{
public readonly List<Teacher> Teachers = ...;
}
This will take a little more work to write to a DB but it solves a lot of other problems.
What are you trying to do? Your sample looks like you want to build a relational database table implemented in C#.
If you want to have an OO representation then get rid of the entire CourseTeacher class. That has absolutely nothing to do with OO.
seems you already solved this problem, but consider following code where I overrode bool Equals; C# couldn't knew how to compare your new cpp instance with another instance in your List<Course>, so we need to tell it by creating a more specialized Equals method:
class Teacher
{
private List<Course> items = new List<Course>();
public int ID { get; set; }
public List<Course> Items { get { return items; } }
}
class Course
{
public int ID { get; set; }
public override int GetHashCode() { return base.GetHashCode(); }
public override bool Equals(object obj) { return Equals(obj as Course); }
public bool Equals(Course another)
{
return another != null && this.ID.Equals(another.ID);
}
}
static void Main(string[] args)
{
Teacher teacher = new Teacher { ID = 2 };
teacher.Items.AddRange(
new Course[] {
new Course{ ID = 2 }, // java
new Course{ ID = 3 } }); // cpp
Course cpp = new Course { ID = 3 }; // previous problem: another instance
teacher.Items.Contains(cpp); // now returns true
teacher.Items.Remove(cpp); // now returns true
}
Henk is correct; your design is very, very relational. For this sort of scenario, though, you're better off focusing on behaviour in your objects, and using an object-relational mapping (ORM) tool to translate between your objects and your database.
ADO.NET's DataTable and DataSet don't really offer object-relational mapping capabilities; because they're so tightly coupled to the underlying database schema, they force you to think in terms of columns, tables and relations, when you really want to be thinking in terms of teachers and courses.
I would seriously recommend looking at Castle ActiveRecord for this scenario. It uses the same approach as your example - static Teacher.Get() to retrieve an instance, myTeacher.Save() to save your changes - but there's a LOT of necessary complexity that your example is missing, and using an ORM framework will allow you to ignore this complexity and focus on your own project's requirements.
Here's an example of many-many associations from the Castle ActiveRecord documentation that you may find helpful.
How about adding and removing done within the Teacher Class?
public class Teacher
{
//.... Original implementations
public bool AddCourse(Course course) {
if(_items.Contains(course)) return false;
_items.Add(course);
return true;
}
// similarly for remove course
}