I have managed to load data for the subtypes ImageCell
and TextCell
of Cell
.
public abstract class Cell : IAggregateRoot
{
public int CellId { get; set; }
public string CellType { get; set; }
public int RowIndex { get; set; }
public int ColIndex { get; set; }
public int RowSpan { get; set; }
public int ColSpan { get; set; }
public int PageId { get; set; }
public Page Page { get; set; }
}
public class TextCell : Cell
{
public String Text { get; set; }
}
public class ImageCell : Cell
{
public String Url { get; set; }
}
I added the discriminator value of CellType
based on an Enum.
public enum CellEnum
{
Cell,
TextCell,
ImageCell
}
In the DbContext.OnModelCreating()
I configured it like this:
builder.Entity<Cell>()
.HasDiscriminator(c => c.CellType)
.IsComplete(false);
From then I think I went into bad design by creating a service per CellType
.
Program.cs
builder.Services.AddScoped<IService<Cell>, GenericService<Cell>>();
builder.Services.AddScoped<IService<ImageCell>, GenericService<ImageCell>>();
builder.Services.AddScoped<IService<TextCell>, GenericService<TextCell>>();
IService.cs
namespace Core.Interfaces
{
public interface IService<T> where T : class, IAggregateRoot
{
Task<T> GetByIdAsync(int id);
Task<IEnumerable<T>> ListAsync();
// Adapt to use specifications
Task<IEnumerable<T>> ListAsyncWithSpec(Specification<T> spec);
Task DeleteByIdAsync(int id);
Task AddAsync(T t);
Task UpdateAsyc(T t);
}
}
GenericService
is just an implementation for the repository using IService<T>
.
I then created a DTO
.
namespace API.DTOs
{
public class CellDTO : DTO
{
public int CellId { get; set; }
public string CellType { get; set; }
public int RowIndex { get; set; }
public int ColIndex { get; set; }
public int RowSpan { get; set; }
public int ColSpan { get; set; }
public int PageId { get; set; }
// CellType specific properties
public string Url { get; set; }
public string Text { get; set; }
}
}
And created a MappingProfile
.
CreateMap<Cell, CellDTO>()
.ForMember(dto => dto.Text, options => options.MapFrom(src => src.CellType.Equals(CellEnum.TextCell.ToString()) ? ((TextCell)src).Text : null))
.ForMember(dto => dto.Url, options => options.MapFrom(src => src.CellType.Equals(CellEnum.ImageCell.ToString()) ? ((ImageCell)src).Url : null));
The Controller seems really messy now:
namespace API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class CellsController : BaseController<Cell, CellDTO>
{
private readonly IService<ImageCell> _imageCellService;
private readonly IService<TextCell> _textCellService;
public CellsController(IService<Cell> service, IService<ImageCell> imageCellService, IService<TextCell> textCellService, IMapper mapper) : base(service, mapper)
{
_imageCellService = imageCellService;
_textCellService = textCellService;
}
[HttpGet]
public override async Task<ICollection<CellDTO>> Get()
{
var result = new List<CellDTO>();
// Add ImageCells
ICollection<ImageCell> imageCells = (ICollection<ImageCell>)await _imageCellService.ListAsync();
result.AddRange(_mapper.Map<ICollection<ImageCell>, ICollection<CellDTO>>(imageCells));
// Add TextCells
ICollection<TextCell> textCells = (ICollection<TextCell>)await _textCellService.ListAsync();
result.AddRange(_mapper.Map<ICollection<TextCell>, ICollection<CellDTO>>(textCells));
return result;
}
}
}
How can I go about loading all the subclasses without duplicating this much code and using multiple IService<T>
for each dedicated type?
Edit #1
I'm using Ardalis.Specification
, but couldn't find anything usefull on that.
using Ardalis.Specification;
using Core.Entities.Cells;
namespace Core.Specifications
{
public class CellSpecification : Specification<Cell>
{
public CellSpecification()
{
}
}
}
Edit #2
Example output
[
{
"cellId": 1,
"cellType": "ImageCell",
"rowIndex": 0,
"colIndex": 0,
"rowSpan": 1,
"colSpan": 1,
"pageId": 1,
"url": "http://bild0.png",
"text": null
},
{
"cellId": 2,
"cellType": "ImageCell",
"rowIndex": 0,
"colIndex": 0,
"rowSpan": 1,
"colSpan": 1,
"pageId": 2,
"url": "http://bild1.png",
"text": null
},
{
"cellId": 3,
"cellType": "ImageCell",
"rowIndex": 0,
"colIndex": 0,
"rowSpan": 1,
"colSpan": 1,
"pageId": 3,
"url": "http://bild2.png",
"text": null
},
{
"cellId": 4,
"cellType": "ImageCell",
"rowIndex": 0,
"colIndex": 0,
"rowSpan": 1,
"colSpan": 1,
"pageId": 4,
"url": "http://bild3.png",
"text": null
},
{
"cellId": 5,
"cellType": "ImageCell",
"rowIndex": 0,
"colIndex": 0,
"rowSpan": 1,
"colSpan": 1,
"pageId": 5,
"url": "http://bild4.png",
"text": null
},
{
"cellId": 6,
"cellType": "TextCell",
"rowIndex": 0,
"colIndex": 1,
"rowSpan": 1,
"colSpan": 2,
"pageId": 1,
"url": null,
"text": "Es ist..."
},
{
"cellId": 7,
"cellType": "TextCell",
"rowIndex": 0,
"colIndex": 1,
"rowSpan": 1,
"colSpan": 2,
"pageId": 2,
"url": null,
"text": "Borja hockt..."
},
{
"cellId": 8,
"cellType": "TextCell",
"rowIndex": 0,
"colIndex": 1,
"rowSpan": 1,
"colSpan": 2,
"pageId": 3,
"url": null,
"text": "..."
},
{
"cellId": 9,
"cellType": "TextCell",
"rowIndex": 0,
"colIndex": 1,
"rowSpan": 1,
"colSpan": 2,
"pageId": 4,
"url": null,
"text": "«Das Versteck ..."
},
{
"cellId": 10,
"cellType": "TextCell",
"rowIndex": 0,
"colIndex": 1,
"rowSpan": 1,
"colSpan": 2,
"pageId": 5,
"url": null,
"text": "Die Ameise...:"
}
]
Cell is an entity, so just run
And remove or fix and simplify IService like this:
Which you can use like this