Search code examples
asp.net-mvcentity-frameworkef-code-firstrepository-patternaspnetboilerplate

How to implement Create with child collection in Application Layer?


I have 2 entities named News and NewsAttachment in the .Core Project. These entities have a relationship through News_Id. In other words, when I create new News and add it into database, this News may have one or two attachment media that I want to insert in the related table named NewsAttachment. So I may want to retrieve News_Id and insert attachments in the related table.

I define 2 DTOs named NewsDto and CreateNewsDto in NewsAppService and I pass CreateNewsDto to insert new News, but I have no idea how to do it for NewsAttachment.

Here is my News entity:

public class News : FullAuditedEntity<long>
{
    public const int MaxTitleLength = 150;
    public const int MaxContentLength = 1200;
    public const int MaxMetaTagLength = 60;

    [Required]
    [MaxLength(MaxTitleLength)]
    public string Title { get; set; }

    [Required]
    public string Code { get; set; }

    [Required]
    [MaxLength(MaxContentLength)]
    public string Content { get; set; }

    public DateTime PublishDate { get; set; }

    [MaxLength(MaxMetaTagLength)]
    public string Tags { get; set; }

    public virtual NewsType Type { get; set; }

    public virtual ICollection<NewsAttachment> Attachments { get; set; }
}

and NewsAttachment entity:

public class NewsAttachment: FullAuditedEntity<long>
{
    public const int MaxTitleLength = 50;

    [Required]
    [MaxLength(MaxTitleLength)]
    public string FileName { get; set; }

    [Required]
    public byte[] File { get; set; }
    public string FileExtension { get; set; }
    public int FileSize { get; set; }

    [Required]
    public DateTime RegisterDate { get; set; }

    public virtual News News { get; set; }
}

and the DTOs:

public class NewsDto : EntityDto<long>
{
    public const int MaxTitleLength = 50;
    public const int MaxContentLength = 800;
    public const int MaxMetaTagLength = 60;

    [Required]
    [MaxLength(MaxTitleLength)]
    public string Title { get; set; }

    [Required]
    public string Code { get; set; }

    [Required]
    [MaxLength(MaxContentLength)]
    public string Content { get; set; }

    public DateTime PublishDate { get; set; }

    [MaxLength(MaxMetaTagLength)]
    public string Tags { get; set; }

    public virtual NewsType Type { get; set; }
}

and:

public class CreateNewsDto
{
    public const int MaxTitleLength = 50;
    public const int MaxContentLength = 800;
    public const int MaxMetaTagLength = 60;

    [Required]
    [MaxLength(MaxTitleLength)]
    public string Title { get; set; }

    [Required]
    public string Code { get; set; }

    [Required]
    [MaxLength(MaxContentLength)]
    public string Content { get; set; }

    public DateTime PublishDate { get; set; }

    [MaxLength(MaxMetaTagLength)]
    public string Tags { get; set; }

    public virtual NewsType Type { get; set; }

    public virtual ICollection<NewsAttachment> Attachments { get; set; }
}

Here is my presumptive method in NewsAppService to insert new News and add related media to NewsAttachment table:

public virtual NewsDto InsertWithMedia(CreateNewsDto input, NewsAttachmentDto attach)
{
   var news = ObjectMapper.Map<NewsManagement.News>(input);

    var newsAttachment = ObjectMapper.Map<NewsAttachment>(attach);
    newsAttachment.News.Id = news.Id;
    return MapToEntityDto(news);
}

Solution

  • Two choices:

    1. Add to ICollection and let EF handle the entities:
    public virtual NewsDto InsertWithMedia(CreateNewsDto input, NewsAttachmentDto attach)
    {
        var news = ObjectMapper.Map<NewsManagement.News>(input);
        news.Attachments = new List<NewsAttachment>(); // 1
    
        var newsAttachment = ObjectMapper.Map<NewsAttachment>(attach);
        news.Attachments.Add(newsAttachment);          // 2
    
        _newsRepository.Insert(news);                  // 3
        CurrentUnitOfWork.SaveChanges();               // 4
    
        return MapToEntityDto(news);
    }
    
    1. Add by Id instead of collection, with a foreign key:
    public class NewsAttachment: FullAuditedEntity<long>
    {
        // ...
    
        public virtual long NewsId { get; set; }
        public virtual News News { get; set; }
    }
    
    public virtual NewsDto InsertWithMedia(CreateNewsDto input, NewsAttachmentDto attach)
    {
        var news = ObjectMapper.Map<NewsManagement.News>(input);
        var newsId = _newsRepository.InsertAndGetId(news); // 1
    
        var newsAttachment = ObjectMapper.Map<NewsAttachment>(attach);
        newsAttachment.NewsId = newsId;                    // 2
    
        _newsAttachmentRepository.Insert(newsAttachment);  // 3
        CurrentUnitOfWork.SaveChanges();                   // 4
    
        return MapToEntityDto(news);
    }
    

    The second is good for updating — when newsId is already known — but may require additional steps if NewsDto also has Attachments (which should be ICollection<AttachmentDto> type).