Yves Schelpe
Yves Schelpe

Reputation: 3463

How do I encapsulate a collection into a "complex type" object with Ef Core (example included)

I would like to map, in Ef Core, a complex type (in other words a type not directly mapped to a table) that does hold a list of object that is directly mapped to a table.

Consider following example below, where you'll find that the Diary logic (of which I now only show AddEntry) is separated from the Request class, as it has no concern being there.

I have no issues using .OwnsOne in Ef Core when using simpler types where the data is stored in the same table (e.g.: here it would be in the Requests table); but I have no idea or clue how I would map in OnConfiguring this situation.

public class Request : DbEntity {
   public Diary { get; set; }
}

public class DiaryEntry : DbEntity {
   public DateTimeOffset Timestamp { get; set; }
   public Request Request { get; set; }
   public string Message { get; set; }   
}

public class Diary {
   private readonly List<DiaryEntry> _entries = new List<DiaryEntry>();
   public Request Request { get; set; }
   public IEnumerable<DiaryEntry> Entries => _entries;
   public Diary AddEntry(DiaryEntry diaryEntry) {
      if(diaryEntry != null)
         _entries.Add(diaryEntry);
      return this;
   }
}

Upvotes: 2

Views: 450

Answers (1)

Steve Py
Steve Py

Reputation: 34773

I don't believe anything like this is supported in Entity Framework as complex types are designed to support organizing properties on one table into related classes without relational tables. In your case you would like to take relational tables and map an intermediate class to avoid having rules about managing the child collection in the parent class.

One option I can put forward to help address the separation of concerns you appear to want to have between your classes is to leverage extension methods for the collection-specific logic you want exposed for a Diary.

For example:

[Table("Requests")]
public class Request
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int RequestId { get; set; }
    public string Name { get; set; }

    public virtual IEnumerable<DiaryEntry> Diary { get; protected set; } = new List<DiaryEntry>();
}

[Table("DiaryEntries")]
public class DiaryEntry
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int DiaryEntryId { get; set; }
    public string Value { get; set; }

    public virtual Request Request { get; set; }
}

Here we expose the Diary as IEnumerable<DiaryEntry> so consumers of the entity cannot modify the collection etc. From there the diary functionality can be managed by extensions:

public static class DiaryExtensions
{
    public static DiaryEntry AddEntry(this IEnumerable<DiaryEntry> diary, DiaryEntry newEntry)
    {
        ICollection<DiaryEntry> diaryList = (ICollection<DiaryEntry>)diary;
        if (newEntry != null)
            diaryList.Add(newEntry);

        return newEntry;
    }
}

Personally though I would have this logic in Request as the diary entries are associated to the request. To separate the diary specific logic I would utilize partial classes but maintain responsibility for the supported behaviour in the class that contains them.

Upvotes: 4

Related Questions