Get record by ID using Entity Framework - c#

How can I change the following action to get a single result by ID?
I have the following code to get All records from the database, I want to modifiy it so that I can get a single record.
namespace PDS.Core.App.Data.Action.Product
{
public class GetProducts_Action : BaseEFAction<GetProducts_Action_Request, GetProducts_Action_Response>
{
private CRMSContext Context { get; }
private IMapper Mapper { get; }
public GetProducts_Action(ILogger<GetProducts_Action> logger, CRMSContext context, ITransactionManager scope, IMapper mapper) : base(logger, context, scope)
{
Context = context.ValidateAndConsumeNonNullableArgument(nameof(context));
Mapper = mapper.ValidateAndConsumeNonNullableArgument(nameof(mapper));
}
protected override async Task<GetProducts_Action_Response> PerformActionAsync(GetProducts_Action_Request request)
{
var tb_Products = await Context.TB_Products
.ToListAsync();
var tb_ProductsDTOs = Mapper.Map<IList<TB_ProductDTO>>(tb_Products);
return new GetProducts_Action_Response { TB_Products = tb_ProductsDTOs };
}
}
public class GetProducts_Action_Request : BaseActionRequest
{
}
public class GetProducts_Action_Response : BaseActionResponse
{
public IList<TB_ProductDTO> TB_Products { get; set; }
}
}

You need to write the code a bit if you extend the code instead of modifying it.
First, you can create a new request class similar to GetProducts_Action_Request that has ProductId in order to use it for getting a single product item. For example, let's say GetProduct_Action_Request:
public class GetProduct_Action_Request : BaseActionRequest
{
public int ProductId {get; set;}
}
And response object that has single product:
public class GetProduct_Action_Response : BaseActionResponse
{
public TB_ProductDTO TB_Product { get; set; }
}
Then you need to create new action GetProduct_Action similar to GetProducts_Action. And you can use SingleOrDefaultAsync to return the only Product element that satisfies a Id condition:
public class GetProduct_Action : BaseEFAction<GetProduct_Action_Request, GetProduct_Action_Response>
{
private CRMSContext Context { get; }
private IMapper Mapper { get; }
public GetProduct_Action(ILogger<GetProduct_Action> logger, CRMSContext context, ITransactionManager scope, IMapper mapper) : base(logger, context, scope)
{
Context = context.ValidateAndConsumeNonNullableArgument(nameof(context));
Mapper = mapper.ValidateAndConsumeNonNullableArgument(nameof(mapper));
}
protected override async Task<GetProduct_Action_Response> PerformActionAsync(GetProduct_Action_Request request)
{
var tb_Product = await Context.TB_Products.SingleOrDefaultAsync(i=> i.Id == request.ProductId);
var tb_ProductDTO = Mapper.Map<IList<TB_ProductDTO>>(tb_Product);
return new GetProduct_Action_Response { TB_Product = tb_ProductDTO };
}
}
public class GetProduct_Action_Request : BaseActionRequest
{
public int ProductId {get; set;}
}
public class GetProduct_Action_Response : BaseActionResponse
{
public TB_ProductDTO TB_Product { get; set; }
}
Therefore you have 2 actions GetProduct_Action for a single product based on Id and GetProducts_Action for a list of all products.

Related

Mediatr - Generic Requests and Request Handlers

I'm trying to use generics to create an abstract query request handler for aggregate roots in a domain-driven design architecture.
I've got the following classes to setup a Mediatr request.
However, I get a compiler error when using ISender.Send because it thinks my response object is just an object instead a QueryResult<T>.
Do you know what I'm doing wrong?
public interface IQuery<T> : IRequest<T>
where T : class
{
Guid CorrelationId { get; }
}
public abstract class BaseResult
{
protected BaseResult(ResultStatus status, Dictionary<string, List<string>>? errors)
{
this.Status = status;
this.Errors = errors ?? new Dictionary<string, List<string>>();
this.IsSuccess = status == ResultStatus.Success;
}
public bool IsSuccess { get; private set; }
public ResultStatus Status { get; private set; }
public Dictionary<string, List<string>> Errors { get; private set; }
...
}
public class QueryResult<T> : BaseResult
where T : class
{
private QueryResult(T? data, ResultStatus status, Dictionary<string, List<string>>? errors)
: base(status, errors)
{
this.Data = data;
}
public T? Data { get; }
...
}
public class AggregateRootQuery<TAggregate, TDto>
: IRequest<QueryResult<IEnumerable<TDto>>>, IQuery<IEnumerable<TDto>>
where TAggregate : class, IAggregateRoot // IAggregate root is a marker interface to only allow queries to start at the aggregate root when using my EF core DB context wrapper
where TDto : class
{
public AggregateRootQuery(Guid correlationId)
{
this.CorrelationId = correlationId;
}
public Guid CorrelationId { get; }
}
public abstract class BaseAggregateRootQueryHandler<TAggregate, TDto> : IRequestHandler<AggregateRootQuery<TAggregate, TDto>, QueryResult<IEnumerable<TDto>>>
where TAggregate : class, IAggregateRoot
where TDto : class
{
protected BaseAggregateRootQueryHandler(IDbContext dbContext, IMapper mapper, ILogger logger)
{
this.DbContext = dbContext;
this.Mapper = mapper;
this.Logger = logger;
}
protected IDbContext DbContext { get; }
protected IMapper Mapper { get; }
protected ILogger Logger { get; }
public async Task<QueryResult<IEnumerable<TDto>>> Handle(AggregateRootQuery<TAggregate, TDto> request, CancellationToken cancellationToken)
{
var entities = await this.ApplyFilter(this.DbContext.AggregateRoot<TAggregate>())
.ToArrayAsync(cancellationToken);
var dtos = this.MapToDataTransferObjects(entities);
this.Logger.Debug("{Count} {EntityType} read from database", entities.Length, nameof(TAggregate));
return QueryResult<IEnumerable<TDto>>.Success(dtos);
}
protected abstract IQueryable<TAggregate> ApplyFilter(IQueryable<TAggregate> source);
protected virtual IEnumerable<TDto> MapToDataTransferObjects(IEnumerable<TAggregate> source)
{
return this.Mapper.Map<IEnumerable<TDto>>(source);
}
}
Usage
// Domain.Order implements IAggregateRoot
public class OrderQueryHandler : BaseAggregateRootQueryHandler<Domain.Order, OrderDto>
{
public OrderQueryHandler(IDbContext dbContext, IMapper mapper, ILogger logger)
:base(dbContext, mapper, logger)
{
}
protected override IQueryable<Domain.Order> ApplyFilter(IQueryable<Domain.Order> source)
{
return source.OrderBy(x => x.Name);
{
}
var result = await this.Mediator.Send(new AggregateRootQuery<Domain.Order, OrdrDto>(Guid.NewGuid()));
// `IsSuccess` and `Data` have compiler errors because it thinks `result` is an object and not a `QueryResult<IEnumerable<OrderDto>>`
if (!result.IsSuccess || result.Data == null)
{
// Error handing
}

EF Core: Implement a single endpoint for all Subtypes

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>

Object Reference Not Set to an Instance of an Object at Repository Layer

I am receiving null exception error on my framework. I have tried to apply Repository and Unit of Work design patterns in my application. What I am trying to do is simply retreiving user titles from my data base with GetAll() method.
Here is my repository class:
public class Repository<T> : IRepository<T> where T : class
{
protected readonly DbContext Context;
public Repository(DbContext context)
{
this.Context = context;
}
public T Get(int id)
{
return Context.Set<T>().Find(id);
}
public IEnumerable<T> GetAll()
{
return Context.Set<T>().ToList();
}
public IEnumerable<T> Find(Expression<Func<T, bool>> predicate)
{
return Context.Set<T>().Where(predicate);
}
public void Add(T entity)
{
Context.Set<T>().Add(entity);
}
public void AddRange(IEnumerable<T> entityList)
{
Context.Set<T>().AddRange(entityList);
}
public void Remove(T entity)
{
Context.Set<T>().Remove(entity);
}
public void RemoveRange(IEnumerable<T> entityList)
{
Context.Set<T>().RemoveRange(entityList);
}
}
This is IUserTitlesRepository:
public interface IUserTitlesRepository : IRepository<UserTitles>
{
}
And, the class where above interface implemented:
public UserTitlesRepository(XaPaDataContext context) : base(context)
{
}
public XaPaDataContext XaPaDataContext
{
get { return Context as XaPaDataContext; }
}
Before coming to Controller layer, I have two more layers, which are Operation and Manager layers. And, I think I have messed up on that part (on Base Manager class as shown below).
This is operation layer:
public class UserTitlesOperations
{
private readonly IUnitOfWork _uow;
public UserTitlesOperations(IUnitOfWork uow)
{
_uow = uow;
}
public List<UserTitles> GetAllUserTitles()
{
try
{
List<UserTitles> userTitleList = _uow.UserTitles.GetAll().ToList();
_uow.Complete();
return userTitleList;
}
catch (Exception ex)
{
throw new Exception(ex.ToString());
}
}
}
Below is the BaseManager class which gives inheritance to all manager classes.
public abstract class BaseManager
{
private IUnitOfWork _iUow;
private readonly XaPaDataContext _context;
public IUnitOfWork IUOW
{
get
{
if (_iUow == null)
{
_iUow = new XaPaUnitOfWork(_context);
}
return _iUow;
}
}
}
This is the manager class:
public class UserTitlesManager : BaseManager
{
private readonly UserTitlesOperations _userTitlesOperations;
public UserTitlesManager()
{
_userTitlesOperations = new UserTitlesOperations(base.IUOW);
}
public List<UserTitlesWM> GetAllUserTitles()
{
try
{
return UserTitlesMapping.MaptoWM(_userTitlesOperations.GetAllUserTitles());
}
catch (Exception ex)
{
throw new Exception(ex.ToString());
}
}
}
Finally, this is my API Controller:
[Route("api/LoginRequest")]
public class TitlesController : BaseController
{
UserTitlesManager _userTitlesManager;
public LoginController()
{
_userTitlesManager = new UserTitlesManager();
}
[Route("RetreiveTitles")]
public HttpResponseMessage GetTitles()
{
try
{
return Request.CreateResponse(HttpStatusCode.OK, _userTitlesManager.GetAllUserTitles());
}
catch (Exception ex)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex.ToString());
}
}
}
By the way BaseController is just another API controller which gives inheritance to all other API controllers, and houses a method which is used by all the other controllers.
So, I'm still trying to sharpen my self on this design patterns and would be glad if anyone could show my mistake on BaseManager class. As I said, I suppose the problem is caused by that private readonly XaPaDataContext _context; line. On the other hand,I can't figure out how to corrrect it as my operation classes' constructors are asking for IUnitOfWork.
Thank you in advance!
EDIT:
Just realized that I forgot to share my Unit of Work class:
public class XaPaUnitOfWork : IUnitOfWork
{
private readonly XaPaDataContext _context;
public XaPaUnitOfWork(XaPaDataContext context)
{
_context = context;
Categories = new CategoriesRepository(_context);
OrderDetails = new OrderDetailsRepository(_context);
Orders = new OrdersRepository(_context);
ProductImages = new ProductImagesRepository(_context);
Products = new ProductsRepository(_context);
Users = new UsersRepository(_context);
UserTitles = new UserTitlesRepository(_context);
UserTokens = new UserTokensRepository(_context);
}
public ICategoriesRepository Categories { get; private set; }
public IOrderDetailsRepository OrderDetails { get; private set; }
public IOrdersRepository Orders { get; private set; }
public IProductImagesRepository ProductImages { get; private set; }
public IProductsRepository Products { get; private set; }
public IUsersRepository Users { get; private set; }
public IUserTitlesRepository UserTitles { get; private set; }
public IUserTokensRepository UserTokens { get; private set; }
public int Complete()
{
return _context.SaveChanges();
}
public void Dispose()
{
_context.Dispose();
}
}
After I have changed my BaseManager class as below:
public abstract class BaseManager
{
private IUnitOfWork _iUow;
public IUnitOfWork IUOW
{
get
{
if (_iUow == null)
{
_iUow = new XaPaUnitOfWork(new XaPaDataContext());
}
return _iUow;
}
}
}
I have achived to receive HttpStatusCode.OK
But, honestly, I'm still unsure about the real reason. I make this correction mostly by heart.

Dependent Object Creation

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);

Multi-tenancy web application with filtered dbContext

I am new to ASP.Net MVC and multi-tenancy web application. I have done lots of reading, but being a beginner I just follow what I understand. So I managed to built a sample scenario web application and need to solve the ending part of it. Hope this scenario will be useful for some other beginners as well, but would welcome any other approach. Thanks in advance
1) Database in SQLServer 2008.
2) Data layer: C# class library project called MyApplication.Data
public class AppUser
{
[Key]
public virtual int AppUserID { get; set; }
[Required]
public virtual int TenantID { get; set; }
[Required]
public virtual int EmployeeID { get; set; }
[Required]
public virtual string Login { get; set; }
[Required]
public virtual string Password { get; set; }
}
public class Employee
{
[Key]
public virtual int EmployeeID { get; set; }
[Required]
public virtual int TenantID { get; set; }
[Required]
public virtual string FullName { get; set; }
}
public class Tenant_SYS
{
//this is an autonumber starting from 1
[Key]
public virtual int TenantID { get; set; }
[Required]
public virtual string TenantName { get; set; }
}
3). Business Layer: class library MyApplication.Business
Following FilteredDbSet Class courtesy: Zoran Maksimovic
public class FilteredDbSet<TEntity> : IDbSet<TEntity>, IOrderedQueryable<TEntity>, IOrderedQueryable, IQueryable<TEntity>, IQueryable, IEnumerable<TEntity>, IEnumerable, IListSource
where TEntity : class
{
private readonly DbSet<TEntity> _set;
private readonly Action<TEntity> _initializeEntity;
private readonly Expression<Func<TEntity, bool>> _filter;
public FilteredDbSet(DbContext context)
: this(context.Set<TEntity>(), i => true, null)
{
}
public FilteredDbSet(DbContext context, Expression<Func<TEntity, bool>> filter)
: this(context.Set<TEntity>(), filter, null)
{
}
public FilteredDbSet(DbContext context, Expression<Func<TEntity, bool>> filter, Action<TEntity> initializeEntity)
: this(context.Set<TEntity>(), filter, initializeEntity)
{
}
public Expression<Func<TEntity, bool>> Filter
{
get { return _filter; }
}
public IQueryable<TEntity> Include(string path)
{
return _set.Include(path).Where(_filter).AsQueryable();
}
private FilteredDbSet(DbSet<TEntity> set, Expression<Func<TEntity, bool>> filter, Action<TEntity> initializeEntity)
{
_set = set;
_filter = filter;
MatchesFilter = filter.Compile();
_initializeEntity = initializeEntity;
}
public Func<TEntity, bool> MatchesFilter
{
get;
private set;
}
public IQueryable<TEntity> Unfiltered()
{
return _set;
}
public void ThrowIfEntityDoesNotMatchFilter(TEntity entity)
{
if (!MatchesFilter(entity))
throw new ArgumentOutOfRangeException();
}
public TEntity Add(TEntity entity)
{
DoInitializeEntity(entity);
ThrowIfEntityDoesNotMatchFilter(entity);
return _set.Add(entity);
}
public TEntity Attach(TEntity entity)
{
ThrowIfEntityDoesNotMatchFilter(entity);
return _set.Attach(entity);
}
public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, TEntity
{
var entity = _set.Create<TDerivedEntity>();
DoInitializeEntity(entity);
return (TDerivedEntity)entity;
}
public TEntity Create()
{
var entity = _set.Create();
DoInitializeEntity(entity);
return entity;
}
public TEntity Find(params object[] keyValues)
{
var entity = _set.Find(keyValues);
if (entity == null)
return null;
// If the user queried an item outside the filter, then we throw an error.
// If IDbSet had a Detach method we would use it...sadly, we have to be ok with the item being in the Set.
ThrowIfEntityDoesNotMatchFilter(entity);
return entity;
}
public TEntity Remove(TEntity entity)
{
ThrowIfEntityDoesNotMatchFilter(entity);
return _set.Remove(entity);
}
/// <summary>
/// Returns the items in the local cache
/// </summary>
/// <remarks>
/// It is possible to add/remove entities via this property that do NOT match the filter.
/// Use the <see cref="ThrowIfEntityDoesNotMatchFilter"/> method before adding/removing an item from this collection.
/// </remarks>
public ObservableCollection<TEntity> Local
{
get { return _set.Local; }
}
IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator()
{
return _set.Where(_filter).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _set.Where(_filter).GetEnumerator();
}
Type IQueryable.ElementType
{
get { return typeof(TEntity); }
}
Expression IQueryable.Expression
{
get
{
return _set.Where(_filter).Expression;
}
}
IQueryProvider IQueryable.Provider
{
get
{
return _set.AsQueryable().Provider;
}
}
bool IListSource.ContainsListCollection
{
get { return false; }
}
IList IListSource.GetList()
{
throw new InvalidOperationException();
}
void DoInitializeEntity(TEntity entity)
{
if (_initializeEntity != null)
_initializeEntity(entity);
}
public DbSqlQuery<TEntity> SqlQuery(string sql, params object[] parameters)
{
return _set.SqlQuery(sql, parameters);
}
}
public class EFDbContext : DbContext
{
public IDbSet<AppUser> AppUser { get; set; }
public IDbSet<Tenant_SYS> Tenant { get; set; }
public IDbSet<Employee> Employee { get; set; }
///this makes sure the naming convention does not have to be plural
///tables can be anything we name them to be
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
public EFDbContext(int tenantID = 0) //Constructor of the class always expect a tenantID
{
//Here, the Dbset can expose the unfiltered data
AppUser = new FilteredDbSet<AppUser>(this);
Tenant = new FilteredDbSet<Tenant_SYS>(this);
//From here, add all the multitenant dbsets with filtered data
Employee = new FilteredDbSet<Employee>(this, d => d.TenantID == tenantID);
}
}
public interface IEmployeeRepository
{
IQueryable<Employee> Employees { get; }
void SaveEmployee(Employee Employee);
void DeleteEmployee(Employee Employee);
List<Employee> GetEmployeesSorted();
}
public class EFEmployeeRepository : IEmployeeRepository
{
private EFDbContext context;
public EFEmployeeRepository(int tenantID = 0)
{
context = new EFDbContext(tenantID);
}
IQueryable<Employee> IEmployeeRepository.Employees
{
get
{
return context.Employee;
}
}
public void SaveEmployee(Employee Employee)
{
if (Employee.EmployeeID == 0)
{
context.Employee.Add(Employee);
}
context.SaveChanges();
}
public void DeleteEmployee(Employee Employee)
{
context.Employee.Remove(Employee);
context.SaveChanges();
}
public List<Employee> GetEmployeesSorted()
{
//This is just a function to see the how the results are fetched.
return context.Employee.OrderBy(m => m.FullName)
.ToList();
//I haven't used where condition to filter the employees since it should be handled by the filtered context
}
}
4) WEB Layer: ASP.NET MVC 4 Internet Application with Ninject DI
public class NinjectControllerFactory : DefaultControllerFactory
{
private IKernel ninjectKernel;
public NinjectControllerFactory()
{
ninjectKernel = new StandardKernel();
AddBindings();
}
protected override IController GetControllerInstance(RequestContext requestContext,
Type controllerType)
{
return controllerType == null
? null
: (IController)ninjectKernel.Get(controllerType);
}
private void AddBindings()
{
ninjectKernel.Bind<IAppUserRepository>().To<EFAppUserRepository>();
ninjectKernel.Bind<IEmployeeRepository>().To<EFEmployeeRepository>();
}
}
5) Controller. Here is the Problem
public class HomeController : Controller
{
IEmployeeRepository repoEmployee;
public HomeController(IEmployeeRepository empRepository)
{
//How can I make sure that the employee is filtered globally by supplying a session variable of tenantID
//Please assume that session variable has been initialized from Login modules after authentication.
//There will be lots of Controllers like this in the application which need to use these globally filtered object
repoEmployee = empRepository;
}
public ActionResult Index()
{
//The list of employees fetched must belong to the tenantID supplied by session variable
//Why this is needed is to secure one tenant's data being exposed to another tenants accidently like, if programmer fails to put where condition
List<Employee> Employees = repoEmployee.Employees.ToList();
return View();
}
}
NInject DI can do the magic !! Provided you will have a login routine which creates the session variable "thisTenantID".
In the Web Layer:
private void AddBindings()
{
//Modified to inject session variable
ninjectKernel.Bind<EFDbContext>().ToMethod(c => new EFDbContext((int)HttpContext.Current.Session["thisTenantID"]));
ninjectKernel.Bind<IAppUserRepository>().To<EFAppUserRepository>();
ninjectKernel.Bind<IEmployeeRepository>().To<EFEmployeeRepository>().WithConstructorArgument("tenantID", c => (int)HttpContext.Current.Session["thisTenantID"]);
}
The way you have designed your repository follows a very clear design, but the parameter that you are passing in the constructor makes things a bit more complicated when using dependency injection.
What I propose here below, is perhaps not the best design, but it will allow you to progress without doing too much changes to your existing code.
The catch in this solution is that you have to call the "Initialise" method when creating the controller, which potentially you might not like, but it is quite effective.
Here are the steps:
Create a new method in your IEmployeeRepository
public interface IEmployeeRepository
{
//leave everything else as it is
void Initialise(int tenantId);
}
Implement that method in the EFEmployeeRepository
public class EFEmployeeRepository
{
//leave everything else as it is
public void Initialise(int tenantID = 0)
{
context = new EFDbContext(tenantID);
}
}
In the HomeController, you would need to call "Initialise" in the constructor
public HomeController(IEmployeeRepository empRepository)
{
repoEmployee = empRepository;
repoEmployee.Initialise(/* use your method to pass the Tenant ID here*/);
}
An alternative to this approach could be to create a RepositoryFactory that would return the Repository filled out with all the filters you need. In that case you will inject the Factory rather than the Repository to the Controller.

Categories