Environment:
I am working in Webapi. There is 2 entity classes which are follows;
public class Class1
{
public Class1()
{
this.items = new HashSet<Class2>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Class2> items { get; set; }
}
public class Class2
{
public int Id { get; set; }
public string Name { get; set; }
public int Class1Id { get; set; }
public virtual Class1 class1 { get; set; }
}
Business Layer:
The buniess layer have the following codes;
public class Class1Logic : IClass1Logic
{
private readonly IClass1Repository _repo;
public Class1Logic(IClass1Repository repository)
{
_repo = repository;
}
public async Task<bool> AddClass1ItemAsync(Class1 item)
{
_repo.Add(item);
bool status = await _repo.SaveAsync();
return status;
}
public async Task<Class1> GetClass1ItemAsync(int id)
{
return await _repo.GetAsync(id);
}
}
public class Class2Logic : IClass1Logic
{
private readonly IClass2Repository _repo;
public Class2Logic(IClass2Repository repository)
{
_repo = repository;
}
public async Task<bool> AddClass2ItemAsync(Class2 item)
{
_repo.Add(item);
bool status = await _repo.SaveAsync();
return status;
}
public async Task<Class2> GetClass2ItemAsync(int id)
{
return await _repo.GetAsync(id);
}
}
ViewModels:
public class Class1Model
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Class2Model
{
public int Id { get; internal set; }
public string Name { get; set; }
public int Class1Id { get; set; }
public string Class1Name { get; internal set; }
}
Controllers:
There are 2 contrtollers like Class1Controller and Class2Controller. Both have all CRUD operations.
[RoutePrefix("api/class1items")]
public class Class1Controller : ApiController
{
private readonly IClass1Logic _class1Logic;
private ModelFactory TheFactory;
public Class1Controller(IClass1Logic class1Logic)
{
_class1Logic = class1Logic;
TheFactory = new ModelFactory();
}
[Route("")]
public async Task<IHttpActionResult> Post(Class1Model class1Model)
{
var item = TheFactory.Parse(class1Model);
bool result = await _class1Logic.AddClassItemAsync(item);
if (!result)
{
return BadRequest("Error");
}
string uri = Url.Link("GetLabById", new { id = item.Id });
return Created(uri, TheFactory.Create(item));
}
[Route("{id:int}", Name = "GetClass1ItemById")]
public async Task<IHttpActionResult> GetClass1Item(int id)
{
Class1 item = await _class1Logic.GetClassItemAsync(id);
if (item == null)
{
return NotFound();
}
return Ok(TheFactory.Create(item));
}
}
[RoutePrefix("api/class2items")]
public class Class2Controller : ApiController
{
private readonly IClass2Logic _class2Logic;
private ModelFactory TheFactory;
public Class2Controller(IClass2Logic class2Logic)
{
_class2Logic = class2Logic;
TheFactory = new ModelFactory();
}
[Route("")]
public async Task<IHttpActionResult> Post(Class2Model class2Model)
{
var item = TheFactory.Parse(class2Model);
***//Here item should include Class1 object even if user give ClassId in class2Model***
bool result = await _class2Logic.AddClassItemAsync(item);
if (!result)
{
return BadRequest("Error");
}
string uri = Url.Link("GetClass2ItemById", new { id = item.Id });
return Created(uri, TheFactory.Create(item));
}
}
There is not dependecies in Class1. So all operations are fine. In Class2Controller post method, I got the model object as following to create Class2.
{
"id": 0,
"name": "string",
"class1Id": 1
}
Understanding:
I need to return this viewmodel to user after the create the record. The record created successfully but when mapping to viewmodel i got null exception as Class1 object not in the Class2 object.
In order to get the Class2 object including class1 object, I need to give the class1Object in the request object.
For this i need to find the Class1 object with Class1Id in the request object.
ViewMapper Code:
public class ModelFactory
{
public Class1Model Create(Class1 item)
{
return new Class1Model
{
Id = item.Id,
Name = item.Name
};
}
public Class2Model Create(Class2 item)
{
return new Class2Model
{
Id = item.Id,
Name = item.Name,
Class1Id = item.class1.Id,
Class1Name = item.class1.Name
};
}
public Class1 Parse(Class1Model modelItem)
{
return new Class1
{
Id = modelItem.Id,
Name = modelItem.Name
};
}
public Class2 Parse(Class2Model modelItem)
{
return new Class2
{
Id = modelItem.Id,
Name = modelItem.Name,
Class1Id = modelItem.Class1Id,
***/*Issue Place*/
//class1 = Need to set property by getting object using modelItem.Class1Id***
};
}
}
Issue:
Now i need to call get method of Class1Controller by passing Class1Id.
How to call and is this correct? or my design is bad?
This is initial case. If my Class3 have both Class1 and Class2 again i need to call methods of Class1 and Class2.
Please help to find the correct solution in this case
Note: I added comments the issue area to understand
Well, just to fix this issue you need to manually call _class1Logic.GetClass1ItemAsync after saving. However this doesn't look good.
More elegant ways to fix it:
1) If you always need Class2.Class1 field to be filled use Include when you fetch data (in repository): dbContext.Set<Class2>().Include(c => c.class1).
2) Also you can turn on LazyLoading for EF - I assume it should work in your case.
3) Inject class1Repo to class2Logic and fix up class1 reference after saving - in case if you don't want to enable lazy loading or item was detached from context after save method
Thoughts about design:
I suggest you to look at Automapper or simular libraries instead of ModelFactory where you going to have all mapping logic
Edit: About generic repository: you can modify you GetAsync method
public async Task<T> GetAsync<T>(int id, params Expression<Func<T, object>>[] includes)
where T: class, IEntity
{
var query = context.Set<T>().AsQueryable();
if (includes.Length > 0)
{
query = includes.Aggregate(query,
(current, include) => current.Include(include));
}
return await query.FirstOrDefaultAsync(x => x.Id == id);
}
IEntity interface:
interface IEntity
{
int Id { get; }
}
With this implementation you can use
await _repo.GetAsync<Class2>(id, x => x.class1);
Related
I will plot an example to illustrate my behind the scene issues.
Let say I have this base generic class :
public abstract class ContainerBase<T>
{
Guid Id {get; init;}
IList<T> Items {get; set;}
bool IsLeaf {get; set;} = false;
/// omitted constructors and so
}
Then I have a whole bunch (undefined number) of concrete Container class that have another ContainerBase<...> as the T type argument :
public class RootContainer : ContainerBase<ChildContainer1>
{...}
public class ChildContainer1: ContainerBase<ChildContainer2>
{...}
public class ChildContainer2: ContainerBase<ChildContainer3>
{...}
...
public class ChildContainerNminus1: ContainerBase<ChildContainerN>
{...}
public class ChildContainerN: ContainerBase<int> // the recursion end here.
{
...
IsLeaf = true;
}
Now let say I have an AddContainer method from an Utility class and have access to the RootContainer object (a singleton for example) that is fully populated of recursive sub containers.
public static class ContainerUtility
{
// What is the Type of the recursive currentContainer ?
public static ContainerBase<T> FindContainer<T>(Guid id, ContainerBase<?> currentContainer)
{
if(currentContainer.Id == id)
return currentContainer;
if(currentContainer.IsLeaf) return default;
foreach(var item in currentContainer.Items)
{
var potential = FindContainer(id, item);
if(potential != default) return potential;
}
return default;
}
public static bool AddContainer<T>(ContainerBase<T> container, Guid parentId)
{
// potential should be of container's parent type (ContainerBase<"T-1">)
// but how to "bybass" an expected type parameter as I cannot know it ?
var potential = FindContainer<?>(parentId, RootContainer.Instance)
if(potential != default && potential is ContainerBase<?>)
{
potential.Items.Add(container)
return true;
}
return false;
}
}
You see, my issue is that I have a base type ContainerBase that is convenient for recursive search as all subClasses allow access to Items list to pursue recursion.
But at each step of the recursion it is a different actual type of ContainerBase<?>.
So I cannot perform cast on the method argument.
maybe use a top level interface that expose a List<object> Items ? Not sure that will end up good.
Bellow was my intermediate mid-solution on my issue.
I'll keep it for the record or erase it if you request it to clarify this response.
Ok I get something more interesting now. I would like to have your criticism of this solution I end up with :
Mainly I abstracted a way higher with a non generic interface to avoid my issue described in OP.
The Interface
public interface IContainer
{
int TAG { get; init; } // usefull for logging purpose
string Name { get; }
Guid Id { get; init; }
public bool IsLeaf { get;}
IList<IContainer>? GetContainers();
void SetContainers(List<IContainer> value);
}
The base class
public abstract class ContainerBase<T> : IContainer where T : IContainer
{
public int TAG { get; init; }
public Guid Id { get; init; }
private IList<IContainer>? _containers = new List<IContainer>();
public IList<T> Items { get; set; } = new List<T>();
public bool IsLeaf => this.GetType() == typeof(T);
public string Name => this.GetType().Name + "_" + TAG;
public ContainerBase(Guid id)
{
TAG = ContainerUtils.ContainerCount++;
Id=id;
}
public ContainerBase()
{
TAG = ContainerUtils.ContainerCount++;
Id = Guid.NewGuid();
}
public IList<IContainer>? GetContainers()
{
if(Items == null) return null;
if(_containers == null || !_containers.Any())
_containers = Items.Where(x => x!=null).Select(x => (IContainer)x!).ToList();
return _containers;
}
public void SetContainers(List<IContainer> value)
{
Items = new List<T>();
foreach(var item in value)
{
if (item is T)
Items.Add((T)item);
}
}
}
The concrete classes
internal class RootContainer : ContainerBase<Child1Container>
{
public RootContainer(Guid id) : base(id)
{
}
public RootContainer() : base()
{
}
}
The intermediate containers are the same only class name change (X = 1 to 3 in my test case)
internal class ChildXContainer : ContainerBase<ChildX+1Container>
{
public ChildXContainer(Guid id) : base(id)
{
}
public ChildXContainer() : base()
{
}
}
The leaf class (end point of my chained containers classes recursion).
internal class LeafContainer : ContainerBase<LeafContainer>
{
public int IntItem { get; set; }
public LeafContainer() : base()
{
}
public LeafContainer(Guid id) : base(id)
{
}
}
Do note I'm using a trick to detect if a ContainerBase<T> concrete implementation is a leaf or not :
If such classes are leaves then they have to derive from ContainerBase<> of themselves.
Kind like the CRTP syntax, but without its meaning.
So I'm not fully satisfied of this trick, but better than my previous attempt so far.
The Utility class
internal static class ContainerUtils
{
public static int ContainerCount = 0;
public static Guid IdToSearch {
get
{
if(!AllIds.Any())
return Guid.Empty;
return AllIds[new Random().Next(AllIds.Count - 1)];
}
//set { IdToSearch = value; }
}
public static List<Guid> AllIds { get; set; } = new();
private static RootContainer _root = BuildContainers();
public static RootContainer Root => _root;
private static RootContainer BuildContainers()
{
LeafContainer Leaf = new LeafContainer();
Child3Container Child3 = new Child3Container();
Child2Container Child2 = new Child2Container();
Child1Container Child1 = new Child1Container();
RootContainer Root = new RootContainer();
Root.Items.Add(Child1);
Child1.Items.Add(Child2);
Child2.Items.Add(Child3);
Child3.Items.Add(Leaf);
Leaf.IntItem = 12;
AllIds.Add(Root.Id);
AllIds.Add(Child1.Id);
AllIds.Add(Child2.Id);
AllIds.Add(Child3.Id);
AllIds.Add(Leaf.Id);
return Root;
}
private static IContainer? _GetSubContainer(this IContainer container, int index)
=> (container == null ||
container.GetContainers() == null ||
index >= container.GetContainers()!.Count) ? null : container.GetContainers()![index];
public static string ContainersToString()
=> ContainersToString(Root);
public static string ContainersToString(IContainer? fromContainer)
{
if (fromContainer == null) return string.Empty;
int i = 0;
string tab = " ";
string res = "";
while(fromContainer != null)
{
res += tab.Repeat(i) + "+" + fromContainer.Name??"NULL";
res += "\n";
i++;
fromContainer = _GetSubContainer(fromContainer, 0);
}
return res;
}
public static IContainer? SearchContainer(Guid id)
=> SearchContainer(id, Root);
public static IContainer? SearchContainer(Guid id, IContainer? fromContainer)
{
if (fromContainer == null) return null;
if (fromContainer.Id == id)
return fromContainer;
if (fromContainer.IsLeaf)
return null;
return SearchContainer(id, fromContainer._GetSubContainer(0));
}
public static bool SetItemToContainer(Guid id, IContainer newContainer)
{
var container = SearchContainer(id);
if(container == null) return false;
if (container._GetSubContainer(0) == null || (container.GetContainers()![0].GetType() != newContainer.GetType()))
return false;
container.GetContainers()![0] = newContainer;
return true;
}
}
The Program and its output
Console.WriteLine(ContainerUtils.ContainersToString());
IContainer newChild2 = new Child2Container();
Console.WriteLine("Child2's Name : " + ContainerUtils.SearchContainer(ContainerUtils.AllIds[2])?.Name ?? "NULL");
Console.WriteLine("New Child2's Name : " + newChild2.Name);
ContainerUtils.SetItemToContainer(ContainerUtils.AllIds[1], newChild2);
Console.WriteLine(ContainerUtils.ContainersToString());
Output
+RootContainer_4
+Child1Container_3
+Child2Container_2
+Child3Container_1
+LeafContainer_0
Child2's Name : Child2Container_2
New Child2's Name : Child2Container_5
+RootContainer_4
+Child1Container_3
+Child2Container_5
OLD answer
I created a new test project with a simpler version of my OP one.
Here is what I ended, and yep found using an interface as a "workish" solution (I'm not completely satisfied).
Let me know what you think of it please.
The interface without generic parameter :
public interface IContainer
{
string Name => this.GetType().Name;
public Guid Id { get; init; }
public IContainer? Item { get; set; }
public bool IsLeaf => Id == Guid.Empty;
}
The base abstract class with the generic parameter :
public abstract class ContainerBase<T> : IContainer where T : IContainer
{
public Guid Id { get; init; }
public T? Item { get; set; }
IContainer? IContainer.Item { get => Item; set => Item = (T)value; }
public ContainerBase(Guid id)
{
Id=id;
}
public ContainerBase()
{
Id = Guid.NewGuid();
}
}
The starting concrete Container class
internal class RootContainer : ContainerBase<Child1Container>
{
public RootContainer(Guid id) : base(id)
{
}
public RootContainer() : base()
{
}
}
The child Container concrete classes.
In my project there is Child1Container, Child2Container and Child3Container. I only display Child1Container here. The other are the same except class name.
internal class Child1Container : ContainerBase<Child2Container>
{
public Child1Container(Guid id) : base(id)
{
}
public Child1Container() : base()
{
}
}
The endind Container (noted Leaf here) :
This is where I found the code most ugly..
internal class LeafContainer : IContainer
{
public int IntItem { get; set; }
public Guid Id { get; init ; }
/// Meh, would be nice to avoid this.
public IContainer? Item { get => null; set => Item = null; }
public LeafContainer()
{
Id = Guid.Empty;
}
}
My utility class :
internal static class ContainerUtils
{
public static Guid IdToSearch { get; set; }
private static RootContainer _root = BuildContainers();
public static RootContainer Root => _root;
private static RootContainer BuildContainers()
{
LeafContainer Leaf = new LeafContainer();
Child3Container Child3 = new Child3Container();
Child2Container Child2 = new Child2Container();
Child1Container Child1 = new Child1Container();
RootContainer Root = new RootContainer();
Root.Item = Child1;
Child1.Item = Child2;
Child2.Item = Child3;
Child3.Item = Leaf;
Leaf.IntItem = 12;
IdToSearch = Root.Id;
return Root;
}
public static IContainer? SearchContainer(Guid id)
=> SearchContainer(id, Root);
public static IContainer? SearchContainer(Guid id, IContainer? fromContainer)
{
if (fromContainer == null) return null;
if(fromContainer.Id == id)
return fromContainer;
if(fromContainer.IsLeaf)
return null;
return SearchContainer(id, fromContainer.Item);
}
}
Finally my Program :
using TestRecursiveGenerics;
var res = ContainerUtils.SearchContainer(ContainerUtils.IdToSearch);
Console.WriteLine("Searching IContainer's Id, and we found : "+ res?.Name ?? "NULL");
I'm having an issue where I try to make one endpoint for all classes that derive the same class.
One of my Core Entities is called Cell and has many deriving types such as ImageCell, VideoCell and so on.
The project is implemented using Ardalis.Specification and Ardalis.Specification.EntityFrameworkCore.
For reference here is the base class Cell and two deriving classes.
public abstract class Cell : IAggregateRoot
namespace Core.Entities.Aggregates
{
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; }
}
}
namespace Core.Entities.Cells
{
public class ImageCell : Cell
{
public string Url { get; set; }
}
}
namespace Core.Entities.Cells
{
public class TextCell : Cell
{
public string Text { get; set; }
}
}
All classes have a corresponding 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; }
}
}
namespace API.DTOs.Cells
{
public class ImageCellDTO : CellDTO
{
public string ImageUrl { get; set; }
}
}
namespace API.DTOs.Cells
{
public class TextCellDTO : CellDTO
{
public string Text { get; set; }
}
}
The MappingProfile is set up according to the documentation:
namespace API
{
public class MappingProfile : Profile
{
public MappingProfile()
{
// Entity -> DTO
...
// Cells
// https://docs.automapper.org/en/stable/Mapping-inheritance.html
CreateMap<Cell, CellDTO>()
.IncludeAllDerived();
CreateMap<ImageCell, ImageCellDTO>();
CreateMap<AudioTextCell, AudioTextCellDTO>();
CreateMap<AudioCell, AudioCellDTO>();
CreateMap<GameCell, GameCellDTO>();
CreateMap<TextCell, TextCellDTO>();
CreateMap<VideoCell, VideoCellDTO>();
...
// DTO -> Enitity
...
// Cells
CreateMap<CellDTO, Cell>()
.IncludeAllDerived();
CreateMap<AudioTextCellDTO, AudioTextCell>();
CreateMap<AudioCellDTO, AudioCell>();
CreateMap<GameCellDTO, GameCell>();
CreateMap<TextCellDTO, TextCell>();
CreateMap<VideoCellDTO, VideoCell>();
CreateMap<ImageCellDTO, ImageCell>();
...
}
}
}
The Repository is set up like this:
using Ardalis.Specification;
namespace Core.Interfaces
{
public interface IRepository<T> : IRepositoryBase<T> where T : class, IAggregateRoot
{
}
}
using Ardalis.Specification;
namespace Core.Interfaces
{
public interface IReadRepository<T> : IReadRepositoryBase<T> where T : class, IAggregateRoot
{
}
}
namespace Infrastructure.Data
{
public class EfRepository<T> : RepositoryBase<T>, IReadRepository<T>, IRepository<T> where T : class, IAggregateRoot
{
public EfRepository(BookDesinerContext dbContext) : base(dbContext)
{
}
}
}
Service like this:
namespace Core.Interfaces
{
public interface IService<T> where T : class, IAggregateRoot
{
Task<bool> ExistsByIdAsync(int id);
Task<T> GetByIdAsync(int id);
Task<T> GetByIdAsyncWithSpec(Specification<T> spec);
Task<IEnumerable<T>> ListAsync();
Task<IEnumerable<T>> ListAsyncWithSpec(Specification<T> spec);
Task DeleteByIdAsync(int id);
Task DeleteRangeAsync(IEnumerable<T> range);
Task<T> AddAsync(T t);
Task UpdateAsyc(T t);
}
}
Now I created a default implementation:
using Ardalis.Specification;
using Core.Interfaces;
namespace Core.Services
{
public class GenericService<T> : IService<T> where T : class, IAggregateRoot
{
private readonly IRepository<T> _repository;
private readonly IAppLogger<GenericService<T>> _logger;
public GenericService(IRepository<T> repository, IAppLogger<GenericService<T>> logger)
{
_repository = repository;
_logger = logger;
}
public async Task<bool> ExistsByIdAsync(int id)
{
return await _repository.GetByIdAsync(id) != null;
}
public async Task<T> GetByIdAsync(int id)
{
var t = await _repository.GetByIdAsync(id);
if (t == null)
{
_logger.Error($"Element with id: {id} can not be found!");
throw new ArgumentException($"Element with id: {id} can not be found!");
}
return t;
}
public async Task<T> GetByIdAsyncWithSpec(Specification<T> spec)
{
if (!(spec is ISingleResultSpecification))
{
throw new ArgumentException("Specification does not implement marker interface.");
}
ISingleResultSpecification<T> specification = (ISingleResultSpecification<T>)spec;
var t = await _repository.GetBySpecAsync(specification);
if (t == null)
{
_logger.Error($"Element can not be found!");
throw new ArgumentException($"Element can not be found!");
}
return t;
}
public async Task<IEnumerable<T>> ListAsync()
{
return await _repository.ListAsync();
}
public async Task<IEnumerable<T>> ListAsyncWithSpec(Specification<T> spec)
{
return await _repository.ListAsync(spec);
}
public async Task DeleteByIdAsync(int id)
{
var t = await _repository.GetByIdAsync(id);
if (t == null)
{
_logger.Error($"Element with id: {id} can not be found!");
throw new ArgumentException($"Element with id: {id} can not be found!");
}
await _repository.DeleteAsync(t);
}
public async Task DeleteRangeAsync(IEnumerable<T> range)
{
await _repository.DeleteRangeAsync(range);
}
public async Task<T> AddAsync(T t)
{
return await _repository.AddAsync(t);
}
public async Task UpdateAsyc(T t)
{
await _repository.UpdateAsync(t);
}
}
}
I registered a Service for every single Subtype:
builder.Services.AddScoped<IService<Cell>, GenericService<Cell>>();
builder.Services.AddScoped<IService<ImageCell>, GenericService<ImageCell>>();
builder.Services.AddScoped<IService<TextCell>, GenericService<TextCell>>();
builder.Services.AddScoped<IService<AudioCell>, GenericService<AudioCell>>();
builder.Services.AddScoped<IService<AudioTextCell>, GenericService<AudioTextCell>>();
builder.Services.AddScoped<IService<VideoCell>, GenericService<VideoCell>>();
builder.Services.AddScoped<IService<GameCell>, GenericService<GameCell>>();
And for the final part the controller:
namespace API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class CellsController : BaseController<Cell, CellDTO>
{
private readonly IService<ImageCell> _imageCellService;
private readonly IService<TextCell> _textCellService;
private readonly IService<AudioCell> _audioCellService;
private readonly IService<AudioTextCell> _audioTextCellService;
private readonly IService<VideoCell> _videoCellService;
private readonly IService<GameCell> _gameCellService;
public CellsController(
IService<Cell> service,
IService<ImageCell> imageCellService,
IService<TextCell> textCellService,
IService<AudioCell> audioCellService,
IService<AudioTextCell> audioTextCellService,
IService<VideoCell> videoCellService,
IService<GameCell> gameCellService,
IMapper mapper) : base(service, mapper)
{
_imageCellService = imageCellService;
_textCellService = textCellService;
_audioCellService = audioCellService;
_audioTextCellService = audioTextCellService;
_videoCellService = videoCellService;
_gameCellService = gameCellService;
}
[HttpGet]
public override async Task<IActionResult> Get()
{
var result = new List<Object>();
// 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 Ok(result);
}
[HttpGet("Page/{pageId}")]
public async Task<IActionResult> GetByPageId(int pageId)
{
var result = new List<Object>();
// Add ImageCells
ICollection<ImageCell> imageCells = (ICollection<ImageCell>)await _imageCellService.ListAsync();
result.AddRange(_mapper.Map<ICollection<ImageCell>, ICollection<ImageCellDTO>>(imageCells.Where(c => c.PageId == pageId).ToList()));
// Add TextCells
ICollection<TextCell> textCells = (ICollection<TextCell>)await _textCellService.ListAsync();
result.AddRange(_mapper.Map<ICollection<TextCell>, ICollection<TextCellDTO>>(textCells.Where(c => c.PageId == pageId).ToList()));
...
return Ok(result);
}
[HttpGet("{id}")]
public override async Task<IActionResult> Get(int id)
{
if (await _imageCellService.ExistsByIdAsync(id))
{
var result = await _imageCellService.GetByIdAsync(id);
return Ok(_mapper.Map<ImageCell, ImageCellDTO>(result));
}
if (await _textCellService.ExistsByIdAsync(id))
{
var result = await _textCellService.GetByIdAsync(id);
return Ok(_mapper.Map<TextCell, TextCellDTO>(result));
}
...
return NotFound();
}
...
}
}
This is a highly inefficient implementation to my understanding.
Problems:
I can call /Cells to get all Cells the way it was intended with the List<Object>. List<CellDTO> always led to a downcast, which was unintended.
The same problem occures in a DTO that is not shown, that has a List<CellDTO> as a property. But I would need the concrete subtypes in this list.
My goals:
Remove redundant code in the controller
Only register one CellSerivce
Correct mapping Entity <=> DTO
Things I have considered, but I could not find information to back my thesis:
Writing a CellSpecification that includes all subtypes
Creating a DTO that covers all fields from the subtypes
Try the following:
var cells = (ICollection<Cell>)await _cellService.ListAsync();
result.AddRange(_mapper.Map<ICollection<Cell>, ICollection<CellDTO>>(cells));
Where _cellService is IService<Cell>
I need to merge data from tables in the database with data based on some logic from third-party sources. I implemented this logic via hashset, for which I overloaded the GetHashCode and Equals methods for entities. Now I don't understand how I can save the result of work in the database via DbSet, with subsequent data loading and subsequent merging (the task of merging/supplementing is periodic)
The directories are quite voluminous, so working through hashset speeds up the process.
class Program
{
private class DummyDbContext { public void SaveChangesAsync() { }}
static void Main(string[] args)
{
var dbContext = new DummyDbContext(); // TODO: Get from DI
// TODO: I don't know how to do it yet with HashSets
var currentFactories = LoadCurrentFactoriesFromDb(dbContext);
var currentProducts = LoadCurrentProductsFromDb(dbContext);
var thirdPartyData = GetThirdPartyData();
foreach (var data in thirdPartyData)
{
/*
In reality, the logic is more complicated, because some data transformation is required.
Some data may be missing. That is why comparing two objects is not quite easy (see the method Product.Equals)
*/
var factory = new Factory(data.otherFactory.Name);
var product = new Product(data.otherProduct.Property1, data.otherProduct.Property2, factory);
if (currentFactories.TryGetValue(factory, out var existedFactory))
factory = existedFactory;
else
currentFactories.Add(factory);
if (currentProducts.TryGetValue(product, out var existedProduct))
{
if (!existedProduct.Factory.Equals(factory))
throw new InvalidOperationException(); // TODO:
product = existedProduct;
factory.Products.Add(product); // TODO:
}
else
currentProducts.Add(product);
}
// **how to implement the saving of combined directories, in hashsets, in the database ?**
dbContext.SaveChangesAsync();
}
private static IEnumerable<(ThirdPartyFactory otherFactory, ThirdPartyProduct otherProduct)> GetThirdPartyData()
{
return new (ThirdPartyFactory otherFactory, ThirdPartyProduct otherProduct)[]
{
( new ThirdPartyFactory () {Name = "SomeFactory"}, new ThirdPartyProduct() {Property1 = "ProductName1"}),
( new ThirdPartyFactory () {Name = "SomeFactory"}, new ThirdPartyProduct() {Property1 = "ProductName2"}),
( new ThirdPartyFactory () {Name = "SomeFactory"}, new ThirdPartyProduct() {Property2 = "Property1"})
};
}
private static HashSet<Factory> LoadCurrentFactoriesFromDb(DummyDbContext context)
{
// DbContext.DbSet<Factory>.GetAll()
return new HashSet<Factory>();
}
private static HashSet<Product> LoadCurrentProductsFromDb(DummyDbContext context)
{
// DbContext.DbSet<Product>.GetAll()
return new HashSet<Product>();
}
}
public class Product
{
public Product(string property1, string property2, Factory factory)
{
Property1 = property1;
Property2 = property2;
Factory = factory;
}
public long Id { get; set; }
public string Property1 { get; }
public string Property2 { get; }
public Factory Factory { get; }
public override bool Equals(object? obj)
{
if (obj == null)
return false;
var product = (Product) obj;
return (string.IsNullOrWhiteSpace(Property1) && string.IsNullOrWhiteSpace(product.Property1)
|| string.CompareOrdinal(this.Property1, product.Property1) == 0)
&& (string.IsNullOrWhiteSpace(Property2) && string.IsNullOrWhiteSpace(product.Property2)
|| string.CompareOrdinal(this.Property2, product.Property2) == 0);
}
public override int GetHashCode()
{
return HashCode.Combine(Property1, Property2).GetHashCode();
}
}
public class Factory
{
public Factory(string name)
{
Name = name;
}
public long Id { get; set; }
public string Name { get; }
public HashSet<Product> Products { get; set; }
}
public class ThirdPartyProduct
{
public string Property1 { get; set; }
public string Property2 { get; set; }
}
public class ThirdPartyFactory
{
public string Name { get; set; }
}
Is it possible to implement this ? Or do I need to convert data from DbSet to HashSet and then back ? But won't I lose information about entities inside the context during such transformations ?
I have below code I am working on. I am working on data access adon.net layer. I have problem with my business layer class called UserBAL. The problem is that I am creating instance of dal and dbmanager inside constructor of UserBAL. How can I change this to be loosely coupled for UserBAL? Hope you get my point.
public interface IEntity
{
int Id { get; set; }
int DoSomething(string one, int two);
}
public class User : IEntity
{
public int Id { get; set; }
public int DoSomething(string one, int two)
{
throw new NotImplementedException();
}
public string Firstname { get; set; }
public string Lastname { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}
public class UserBal //busines logic
{
private readonly IRepositoryDal<User> _userRepositoryDal;
public UserBal()
{
_userRepositoryDal = new UserRepositoryDal(new DbManager("sqlserver?"));
}
public IEnumerable<User> SearchByName(string name)
{
return _userRepositoryDal.SearchByName(name);
}
}
interface IRepositoryDal<T> where T : IEntity
{
IEnumerable<T> SearchByName(string username);
T SearchById(string id);
void Update(T entity);
void Remove(T entity);
void Add(T entity);
}
public class UserRepositoryDal: IRepositoryDal<User>
{
private readonly IDbManager _dbManager;
public UserRepositoryDal(IDbManager dbManager)
{
//read from either singleton or configuration file !!
_dbManager = dbManager;
}
public IEnumerable<User> SearchByName(string username)
{
var parameters = new List<IDbDataParameter>
{
_dbManager.CreateParameter("#FirstName", 50, username, DbType.String),
};
var userDataTable = _dbManager.GetDataTable("storedpr2",
CommandType.StoredProcedure, parameters.ToArray());
foreach (DataRow dr in userDataTable.Rows)
{
var user = new User
{
Id = int.Parse(dr["Id"].ToString()),
Firstname = dr["Firstname"].ToString(),
Lastname = dr["LastName"].ToString(),
Email = dr["Email"].ToString()
};
yield return user;
}
}
public User SearchById(string id)
{
var parameters = new List<IDbDataParameter>
{
_dbManager.CreateParameter("#Id", 50, id, DbType.Int32),
};
var userDataTable = _dbManager.GetDataTable("storedpr2",
CommandType.StoredProcedure, parameters.ToArray());
return new User
{
Id = int.Parse(userDataTable.Rows[0]["Id"].ToString()),
Firstname = userDataTable.Rows[0]["Firstname"].ToString(),
Lastname = userDataTable.Rows[0]["LastName"].ToString(),
Email = userDataTable.Rows[0]["Email"].ToString()
};
}
public void Update(User entity)
{
throw new System.NotImplementedException();
}
public void Remove(User entity)
{
throw new System.NotImplementedException();
}
public void Add(User entity)
{
throw new System.NotImplementedException();
}
}
public partial class FrmLogin : Form
{
private readonly UserBal _userBal;
public FrmLogin()
{
InitializeComponent();
_userBal = new UserBal();
}
}
You should use dependency injection, and for required dependencies, you can use constructor injection, e.g:
public class UserBal
{
private readonly IRepositoryDal<User> _userRepositoryDal;
public UserBal(IRepositoryDal<User> userRepositoryDal)
{
_userRepositoryDal = userRepositoryDal
?? throw new ArgumentNullException(nameof(userRepositoryDal));
}
...
}
Dependency injection is the way to go. Here's a simplified example of your situation.
Given your classes could be like this:
public interface IEntity { }
public interface IRepositoryDal<T> where T : IEntity { }
public interface IDbManager { }
public class User : IEntity { }
public class UserBal //busines logic
{
[Injectivity.Attributes.Inject]
private IRepositoryDal<User> _userRepositoryDal;
}
public class UserRepositoryDal: IRepositoryDal<User>
{
[Injectivity.Attributes.Inject]
private IDbManager _dbManager;
}
public class DbManager : IDbManager
{
[Injectivity.Attributes.Construct()]
public DbManager([Injectivity.Attributes.Key("dbKey", typeof(string))] string x)
{
Console.WriteLine($"DbManager created with parameter \"{x}\"");
}
}
...then this code:
var context = Injectivity.Context.CreateRoot();
context.SetConfig<string>("dbKey", "sqlserver?");
context.SetFactory<IDbManager, DbManager>();
context.SetFactory<IRepositoryDal<User>, UserRepositoryDal>();
context.SetFactory<UserBal, UserBal>();
var user = context.Resolve<UserBal>();
...will produce this output:
DbManager created with parameter "sqlserver?"
Now, I've used a DI container that I wrote a number of years back. It's very flexible.
So the call to context.Resolve<UserBal>(); runs down the chain of seeing what needs to be injected to create all of the instances of all of the objects. Ultimately the creation of UserBal requires the DbManager to be created and hence the output.
You would normally not explicitly register each factory. Normally you would put attributes on all of the classes you want to register and then use context.Register(Assembly.LoadFrom("My.DLL")); or create an XML config file and call context.LoadConfig(XDocument.Load("config.xml"));.
You can even do things like this:
context.SetDecorator<IRepositoryDal<User>, UserRepositoryDalDecorator>();
This will cause all calls to context.Resolve<IRepositoryDal<User>>() or [Inject] attributes to automatically wrap the real instances in this decorator. Ideal to intercepting method calls for debugging purposes.
i having this error as title mention , what causes this anyone?? heres my code
i just want to display the files from SQL Database but it does have error any help? thanks!
namespace a.Models
public interface ICatRepository
{
IEnumerable<Category> GetAll();
Category Get(int id);
Category Add(Category item);
void Remove(int id);
bool Update(Category item);
}
another repository
namespace a.Models
public class CatRepository : ICatRepository
{
private istellarEntities db = new istellarEntities();
public CatRepository()
{
}
public IEnumerable<Category> GetAll()
{
return db.Categories;
}
public Category Get(int id)
{
return db.Categories.Find(id);
}
public Category Add(Category category)
{
db.Categories.Add(category);
db.SaveChanges();
return category;
}
public void Remove(int id)
{
Category category = db.Categories.Find(id);
db.Categories.Remove(category);
db.SaveChanges();
}
public bool Update(Category category)
{
db.Entry(category).State = EntityState.Modified;
db.SaveChanges();
return true;
}
}
controller
namespace a.Controllers
public class APICategoryController : ApiController
{
// static readonly ICatRepository repository = new CatRepository();
private readonly ICatRepository repository;
public APICategoryController(ICatRepository repository)
{
if (repository == null)
{
throw new ArgumentNullException("repository");
}
this.repository = repository;
}
public IEnumerable<Category> GetAllCategories()
{
return repository.GetAll();
}
Lastly my class file
namespace a.Models
using System;
using System.Collections.Generic;
public partial class Category
{
public Category()
{
this.IQuestions = new HashSet<IQuestion>();
}
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<IQuestion> IQuestions { get; set; }
}
You've got one of the more helpful error messages there - APICategoryController does not have a default constructor, ie: a parameterless constructor.
You either need to use some kind of Dependency Injector for your code to know how to instantiate a concrete class for ICatRepository, or provide a default constructor.
eg:
//a default constructor instantiating a concrete type. Simple, but no good for testing etc.
public APICategoryController()
{
ICatRepository repository = new ConcreteRepository;
this.repository = repository;
}
you need to add default constructor. you should add this to APICategoryController (this is default constructor)
public APICategoryController()
{
}