I'am working on a MVC solution, my DAL layer I could solve with a Repository classes, everything is working great.
But in my BLL layer I have repetitive code:
My Crud is the same, my fields and consructor are different.
I can also have some extra methodes.
Is there a way to solve this on a proper way?
Class 1
public class JobTypeLogic
{
#region Fields
public JobType JobType { get; set; }
private UnitOfWork unitOfWork = new UnitOfWork();
public Repository<JobType> JobTypeEngine { get; set; }
#endregion
#region Constructor
public JobTypeLogic()
{
JobType = new JobType();
JobTypeEngine = unitOfWork.Repository<JobType>();
}
#endregion
#region CRUD
public void Add()
{
JobTypeEngine.Add(JobType);
}
public JobType Get(long id)
{
return JobType = JobTypeEngine.Get(id);
}
public void Edit()
{
JobTypeEngine.Edit(JobType);
}
public void Delete()
{
JobTypeEngine.Delete(JobType);
}
public List<JobType> List()
{
return JobTypeEngine.List.ToList();
}
#endregion
}
Class 2
public class JobLogic
{
#region Fields
public Job Job { get; set; }
public IEnumerable<SelectListItem> JobTypeList { get; set; }
private UnitOfWork unitOfWork = new UnitOfWork();
public Repository<Job> JobEngine;
private Repository<JobType> JobTypeEngine;
#endregion
#region Constructor
public JobLogic()
{
Job = new Job();
JobEngine = unitOfWork.Repository<Job>();
JobTypeEngine = unitOfWork.Repository<JobType>();
JobTypeList = GetJobTypeList();
}
#endregion
#region CRUD
public void Add()
{
JobEngine.Add(Job);
}
public Job Get(long id)
{
return Job = JobEngine.Get(id);
}
public void Edit()
{
JobEngine.Edit(Job);
}
public void Delete()
{
JobEngine.Delete(Job);
}
public List<Job> List()
{
return JobEngine.List.ToList();
}
#endregion
#region Methode
private IEnumerable<SelectListItem> GetJobTypeList()
{
JobTypeEngine = unitOfWork.Repository<JobType>();
var jobs = JobTypeEngine.List
.Select(x =>
new SelectListItem
{
Value = x.ID.ToString(),
Text = x.Name
});
return new SelectList(jobs, "Value", "Text");
}
#endregion
}
You could create a generic base class
public class GenericJobLogic<T> where T : IJob
{
private Repository<T> engine;
public GenericJobLogic()
{
this.engine = unitOfWork.Repository<T>();
}
public virtual T Get(long id)
{
return this.engine.Get(id);
}
}
This assumes Job and JobType both implement IJob or some other base class JobBase. Or you could always just do where T : class.
Usage becomes
var jobBll = new GenericJobLogic<Job>();
Job job = jobBll.Get(1);
You can still override your base BLL class. Then override or extend only the necessary parts instead of writing a full implementation.
public class JobLogic : GenericJobLogic<Job>
{
public override Job Get(long id) { }
public IEnumerable<JobType> GetJobTypeList() { }
}
Thanks for the response.
I solved the problem by creating a generic base class and inherited from that class.
Base class
public class GenericLogic<T> where T : BaseEntity
{
private Repository<T> engine;
private UnitOfWork unitOfWork = new UnitOfWork();
public T Entity;
public GenericLogic()
{
this.engine = unitOfWork.Repository<T>();
}
#region CRUD
public void Add()
{
engine.Add(Entity);
}
public T Get(long id)
{}
public void Edit()
{}
public void Delete()
{}
public List<T> List()
{}
#endregion
}
The two BLL classes (and the rest of the BLL) become lighter and not repetitive.
BLL class
public class JobLogic : GenericLogic<Job>
{
#region Fields
public Job Job { get; set; }
public IEnumerable<SelectListItem> JobTypeList { get; set; }
#endregion
#region Constructor
public JobLogic()
{
Job = new Job();
JobTypeList = GetJobTypeList();
}
#endregion
#region Methode
private IEnumerable<SelectListItem> GetJobTypeList()
{
UnitOfWork unitOfWork = new UnitOfWork();
Repository<JobType> jobTypeEngine = unitOfWork.Repository<JobType>();
var jobs = jobTypeEngine.List
.Select(x =>
new SelectListItem
{
Value = x.ID.ToString(),
Text = x.Name
});
return new SelectList(jobs, "Value", "Text");
}
#endregion
}
DAL classes is
public class Repository<T> where T : BaseEntity
{
private readonly FlowContext context;
private IDbSet<T> entities;
string errorMessage = string.Empty;
public Repository(FlowContext context)
{
this.context = context;
}
public T Get(object id)
{}
public void Add(T entity)
{}
public void Edit(T entity)
{}
public void Delete(T entity)
{}
private IDbSet<T> Entities
{}
}
The only problem I have with this solution is that I can't use the Entity field from my base class in MVC. Therefore I created a Field Job. This has todo with the binding between the controller and the HTML page.
Hopefully is this code a great help for other people.
Related
I have a Category table in my project, I used Generic repository and Unit of Work pattern in my project. It is an API. It is working when I use mock of UnitOfWork lonely but It is not working when I want to check my controller result. My controller response is 404 always. I think it is because of my mistake in setup but I don't know where it is.
`
[DisplayName("Category Table")]
public class Category
{
public int Id { get; set; }
public int? Priority { get; set; }
public string Name { get; set; }
public string? Details { get; set; }
public bool Enabled { get; set; }
public int? ParentCategoryId { get; set; }
public virtual Category Parent { get; set; }
public virtual IList<Category> Children { get; set; }
}
public interface IGenericRepository<T> where T : class, new()
{
Task<T> GetByIdAsync(object id);
Task<IReadOnlyList<T>> ListAllAsync();
Task<T> GetEntityWithSpec(ISpecification<T> spec);
Task<IReadOnlyList<T>> ListWithSpecAsync(ISpecification<T> spec);
...
}
public class GenericRepository<T> : IGenericRepository<T> where T : class, new()
{
private readonly FactoryContext _context;
public GenericRepository(FactoryContext context)
{
_context = context;
}
public async Task<T> GetByIdAsync(object id)
{
return await _context.Set<T>().FindAsync(id);
}
public async Task<IReadOnlyList<T>> ListAllAsync()
{
return await _context.Set<T>().ToListAsync();
}
public async Task<T> GetEntityWithSpec(ISpecification<T> spec)
{
return await ApplySpecification(spec).FirstOrDefaultAsync();
}
public async Task<IReadOnlyList<T>> ListWithSpecAsync(ISpecification<T> spec)
{
return await ApplySpecification(spec).ToListAsync();
}
...
private IQueryable<T> ApplySpecification(ISpecification<T> spec)
{
return SpecificationEvaluator<T>.GetQuery(_context.Set<T>().AsQueryable(), spec);
}
}
public interface IUnitOfWork : IDisposable
{
IGenericRepository<TEntity> Repository<TEntity>() where TEntity : class, new();
Task<int> Complete();
}
public class UnitOfWork : IUnitOfWork
{
private readonly FactoryContext _context;
private Hashtable _repositories;
public UnitOfWork(FactoryContext context)
{
_context = context;
}
public async Task<int> Complete()
{
return await _context.SaveChangesAsync();
}
public void Dispose()
{
_context.Dispose();
}
public IGenericRepository<TEntity> Repository<TEntity>() where TEntity : class, new()
{
if (_repositories == null) _repositories = new Hashtable();
var type = typeof(TEntity).Name;
if (!_repositories.ContainsKey(type))
{
var repositoryType = typeof(GenericRepository<>);
var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(TEntity)), _context);
_repositories.Add(type, repositoryInstance);
}
return (IGenericRepository<TEntity>)_repositories[type];
}
}
public class CategoriesController : BaseApiController
{
private readonly IUnitOfWork _unitOfWork;
private readonly IMapper _mapper;
public CategoriesController(IUnitOfWork unitOfWork, IMapper mapper)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
}
[HttpGet("getcategorybyid/{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status400BadRequest)]
public async Task<ActionResult<CategoryToReturnDto>> GetCategoryById(int id)
{
try
{
var spec = new GetCategoriesWithParentsSpecification(id);
var category = await _unitOfWork.Repository<Category>().GetEntityWithSpec(spec);
if (category.Id == 0) return NotFound(new ApiResponse(404));
var returnCategories = _mapper.Map<Category, CategoryToReturnDto>(category);
return new OkObjectResult(new ApiResponse(200, "Ok", returnCategories));
}
catch (Exception ex)
{
return BadRequest(new ApiResponse(400, ex.Message));
}
}
}
public class CategoriesControllerTests
{
private readonly Mock<IMapper> _mapper;
private readonly Mock<IUnitOfWork> _unitOfWork;
public CategoriesControllerTests()
{
_mapper = new Mock<IMapper>();
_unitOfWork = new Mock<IUnitOfWork>();
}
[Fact]
public async Task Get_OK_ObjectResult_CategoryById()
{
//Arrange
Category newCategory = CreateTestCategory();
var spec = new GetCategoriesWithParentsSpecification(1);
_unitOfWork.Setup(x => x.Repository<Category>().GetEntityWithSpec(spec)).ReturnsAsync(newCategory)
.Verifiable();
//Act
// Here it is working and result2 has data.
var result2 = await _unitOfWork.Object.Repository<Category>().GetEntityWithSpec(spec);
var controller = new CategoriesController(_unitOfWork.Object, _mapper.Object);
var result = await controller.GetCategoryById(1);
// Assert
result.Value?.Id.ShouldBe(newCategory.Id);
result.Value?.Name.ShouldBeEquivalentTo(newCategory.Name);
result.Value?.Name.ShouldBeEquivalentTo("newCategory.Name");
// My problem here. My result is NotFoundObjectResult always.
result.Result.ShouldBeOfType<OkObjectResult>();
}
private Category CreateTestCategory()
{
return new Category()
{
Id = 1,
Priority = 1,
Name = "Test Category",
Enabled = true,
Details = "Testing category data"
};
}
}
`
The spec that you pass during the moq setup isn't the same as the spec that your repository receives inside the controller
In your test, you should change the setup in such a way that it checks the type of input instead and avoid passing a reference.
_unitOfWork.Setup(x => x.Repository<Category>()
.GetEntityWithSpec(It.IsAny<ISpecification<Category>>()))
.ReturnsAsync(newCategory)
.Verifiable();
Here we set up the moq in a way that as long as the input is ISpecification<Category>, it returns newCategory
The specificatio didn't use truly. Your setup should be like this:
_unitOfWork.Setup(x => x.Repository<Category>()
.GetEntityWithSpec(It.IsAny<ISpecification<Category>>()))
.ReturnsAsync(newCategory)
.Verifiable();
I am using ASP.NET Core Web API. I have these models:
public abstract class EntityBase
{
[Key]
public int Id { get; set; }
}
public class Mandate : EntityBase
{
public int? MerchantId { get; set; }
public DateTime DueDate { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
[ForeignKey("MerchantId")]
public virtual Merchant Merchant { get; set; }
}
Model: Mandate
ViewModel (Dto):
public class MandateGetDto
{
public int? MerchantId { get; set; }
public DateTime DueDate { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
IBaseRepository:
public interface IBaseRepository<T> where T : BaseEntity
{
Task<IEnumerable<T>> GetAll();
bool EntityExists(long id);
}
BaseRepository:
public class BaseRepository<T> : IBaseRepository<T> where T : AuditableBaseEntity
{
private readonly DDMDbContext _context;
private DbSet<T> _entities;
public BaseRepository(DDMDbContext context)
{
_context = context;
_entities = context.Set<T>();
}
public async Task<IEnumerable<T>> GetAll()
{
var list = await _entities.ToListAsync();
return list;
}
public bool EntityExists(long id)
{
return _entities.Any(x => x.Id == id);
}
}
EntityMapper:
public MandateGetDto FromMandateToMandateGetDto(Mandate mandate)
{
MandateGetDto mandateDto = new MandateGetDto();
mandateDto.MerchantId = mandate.MerchantId;
mandateDto.DueDate = mandate.DueDate;
mandateDto.StartDate = mandate.StartDate;
mandateDto.EndDate = mandate.EndDate;
return mandateDto;
}
UnitOfWork:
public class UnitOfWork : IUnitOfWork
{
private readonly DContext _context;
public UnitOfWork(DContext context)
{
_context = context;
}
#region Mandate
private readonly IBaseRepository<Mandate> _mandateRepository;
public IBaseRepository<Mandate> MandateRepository => _mandateRepository ?? new BaseRepository<Mandate>(_context);
# endregion
public void Dispose()
{
if (_context != null)
{
_context.Dispose();
}
}
}
Below is the code I have written for the Mandate service which retrieves all the records for the mandates.
MandateService:
public async Task<ResponsePagination<GenericPagination<MandateGetDto>>> GetAll(int page, int sizeByPage)
{
string nextRoute = null, previousRoute = null;
IEnumerable<Mandate> data = await _unitOfWork.MandateRepository.GetAll();
var mapper = new EntityMapper();
var mandatesDto = data.Select(m => mapper.FromMandateToMandateGetDto(m)).ToList();
GenericPagination<MandateGetDto> objGenericPagination = GenericPagination<MandateGetDto>.Create(mandatesDto, page, sizeByPage);
ResponsePagination<GenericPagination<MandateGetDto>> response = new ResponsePagination<GenericPagination<MandateGetDto>>(objGenericPagination);
response.CurrentPage = objGenericPagination.CurrentPage;
response.HasNextPage = objGenericPagination.HasNextPage;
response.HasPreviousPage = objGenericPagination.HasPreviousPage;
response.PageSize = objGenericPagination.PageSize;
response.TotalPages = objGenericPagination.TotalPages;
response.TotalRecords = objGenericPagination.TotalRecords;
response.Data = objGenericPagination;
if (response.HasNextPage)
{
nextRoute = $"/mandates?page={(page + 1)}";
response.NextPageUrl = _uriPaginationService.GetPaginationUri(page, nextRoute).ToString();
}
else
{
response.NextPageUrl = null;
}
if (response.HasPreviousPage)
{
previousRoute = $"/mandates?page={(page - 1)}";
response.PreviousPageUrl = _uriPaginationService.GetPaginationUri(page, previousRoute).ToString();
}
else
{
response.PreviousPageUrl = null;
}
return response;
}
public async Task<IEnumerable<Mandate>> GetMandates()
{
return await _unitOfWork.MandateRepository.GetAll();
}
startup.cs:
services.AddTransient<IMandateService, MandateService>();
Controller:
[Produces("application/json")]
[ApiController]
[ApiVersion("1.0")]
public class AdminController : ControllerBase
{
private readonly IMerchantService _merchantService;
private readonly DDMDbContext _context;
public AdminController(DDMDbContext context, IMerchantService merchantService)
{
_merchantService = merchantService;
_context = context;
}
[HttpGet("mandates")]
[Authorize]
public async Task<ResponsePagination<GenericPagination<MandateGetDto>>> GetAllMyMandates(int page, int sizeByPage)
{
return await _mandateService.GetAll(page, sizeByPage);
}
}
When I used POSTMAN for the Get Request, I got this response:
{
"current_page": 0,
"page_size": 0,
"total_pages": -2147483648,
"total_records": 1,
"has_next_page": false,
"has_previous_page": false,
"data": []
}
Data is blank while total record is 1.
How do I resolve this?
Thanks
You have not shown us the principal part of your code, which is the controller
Controller is the reason why your web API even works. So, if you have misconfigured something, it should be present in the controller first or then somewhere else.
Show us the code for the controller.
Edit
I am not sure if DbSet is appropriate for the usage, but using the context object works for me.
You should use
_context.MandateOrWhateverElseItIs.ToListAsync();
instead of using
_entities.ToListAsync();
Using the DbSet can be an issue, I recommend using the context. My application works fruitfully with context.
Edit
This is your code in BaseRepository
public class BaseRepository<T> : IBaseRepository<T> where T : AuditableBaseEntity
{
private readonly DDMDbContext _context;
private DbSet<T> _entities;
public BaseRepository(DDMDbContext context)
{
_context = context;
_entities = context.Set<T>();
}
public async Task<IEnumerable<T>> GetAll()
{
var list = await _entities.ToListAsync();
return list;
}
public bool EntityExists(long id)
{
return _entities.Any(x => x.Id == id);
}
}
What you should change it to is
public class BaseRepository<T> : IBaseRepository<T> where T : AuditableBaseEntity
{
private readonly DDMDbContext _context;
private DbSet<T> _entities;
public BaseRepository(DDMDbContext context)
{
_context = context;
_entities = context.Set<T>();
}
public async Task<IEnumerable<T>> GetAll()
{
//Changes are here
var list = await _context.EntitiesOrWhateverElseItIs.ToListAsync(); //I don't know what the option is, you can look it inside the autocomplete. Be sure to change EntitiesOrWhateverElseItIs with the option you see in autocomplete menu
return list;
}
public bool EntityExists(long id)
{
return _entities.Any(x => x.Id == id);
}
}
I have below OSM material data structure that is different than entities and i have methods inside entities which i am forming compatible OSM material using AddToOsm method
public class FenestrationMaterial : Material
{ }
public class StandardOpaqueMaterial : OpaqueMaterial
{ }
public class OpaqueMaterial : Material
{ }
public class SurfaceConstruction : IIdentity<Guid>
{
[Key]
public Guid Id { get; set; }
public Guid SurfaceTypeId { get; set; }
public IntendedSurfaceType SurfaceType { get; set; }
public List<Guid> LayerIds { get; set; }
public Construction AddToOsm(Model model, APIDbContext dbContext)
{
var construction = new Construction(model);
using var materials = new MaterialVector();
var fenestrationMaterialById = new Dictionary<Guid, FenestrationMaterial>();
var standardOpaqueMaterialById = new Dictionary<Guid, StandardOpaqueMaterial>();
var opaqueMaterialById = new Dictionary<Guid, OpaqueMaterial>();
foreach (var materialId in LayerIds.Where(i => i != default))
{
if (ProjectUtils.EntityById<OpaqueProjectMaterial>(dbContext, materialId) != default)
{
var OpaqueProjectMaterial = ProjectUtils.EntityById<OpaqueProjectMaterial>(dbContext, materialId);
materials.Add(
standardOpaqueMaterialById.GetOrCreate(OpaqueProjectMaterial.Id, () => OpaqueProjectMaterial.AddToOsm(model))
);
continue;
}
if (ProjectUtils.EntityById<AirGapMaterial>(dbContext, materialId) != default)
{
var airGapMaterial = ProjectUtils.EntityById<AirGapMaterial>(dbContext, materialId);
materials.Add(
opaqueMaterialById.GetOrCreate(airGapMaterial.Id, () => airGapMaterial.AddToOsm(model))
);
continue;
}
if (ProjectUtils.EntityById<GlazingMaterial>(dbContext, materialId) != default)
{
var glazingMaterial = ProjectUtils.EntityById<GlazingMaterial>(dbContext, materialId);
materials.Add(
fenestrationMaterialById.GetOrCreate(glazingMaterial.Id, () => glazingMaterial.AddToOsm(model))
);
continue;
}
}
construction.setLayers(materials);
return construction;
}
}
and then i do have entities for airGapmaterial, OpaqueProjectMaterial and GlazingMaterial
public class AirGapMaterial : ISourceOfData, IIdentity<Guid>
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
......
......
public OpaqueMaterial AddToOsm(Model model)
{
if (model is null)
{
throw new ArgumentNullException(nameof(model));
}
var airGapMaterial = new AirGap(model);
airGapMaterial.setName(this.Name);
.......
return airGapMaterial;
}
}
public class GlazingMaterial : ISourceOfData, IIdentity<Guid>
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
.......
......
public FenestrationMaterial AddToOsm(Model model)
{
if (model is null)
{
throw new ArgumentNullException(nameof(model));
}
var glazingMaterialComplexModel = new StandardGlazing(model);
glazingMaterialComplexModel.setName(this.Name);
........
return glazingMaterialComplexModel;
}
}
Is there any way I can use generic variable in-place of FenestrationMaterial and StandardOpaqueMaterial in the initialization of dictionaries (fenestrationMaterialById, standardOpaqueMaterialById) and extract the below common methods into single one?
I am looking kind of like this if possible
var fenestrationMaterialById = new Dictionary<Guid, T>();
That took a bit longer then I expected. Turns out the ProjectUtils bit really does throw a wrench into the works. In the following I trimmed things down to just the core concepts, added types missing from your post and did some refactoring to try and stream line things as much as possible. There is still plenty of room for improvement.
I also had to make a ton of assumptions about the underlying data and undocumented components. It looks to me like it would do what you're looking for but its certainly possible that I've made an assumption that is breaking on your end. I also tried to follow the pattern with the Util and DbContext usage, however, to me, those look pretty weak and should probably be reworked.
The core section you wanted to refactor is now:
public Construction AddToOsm(APIDbContext dbContext, Model model, IEnumerable<Guid> ids)
{
if(model is null) throw new ArgumentNullException(nameof(model));
var materialSources = ids.Where(i => i != default)
.Select(i => _projectUtils.EntityById<SourceData>(dbContext, i))
.Select(i => _projectUtils.ResolveToMaterialSource(i));
using var materialVector = new MaterialVector();
foreach (var materialSource in materialSources)
{
materialVector.Add(materialSource.AddToOsm(model));
}
return new Construction(model, materialVector);
}
And here's the rest, it should compile.
public class Model { }
public class Construction
{
public Construction(Model model, MaterialVector materials)
{
SetLayers(materials);
}
internal void SetLayers(MaterialVector materials)
{
}
}
public interface ISourceOfData
{
public string Name { get; set; }
}
public interface IIdentity<T>
{
[Key]
public Guid Id { get; set; }
}
public class IntendedSurfaceType { }
public class APIDbContext { }
public class KeyAttribute : Attribute { }
public class MaterialVector : IDisposable
{
public void Dispose()
{
}
internal void Add(IMaterial p)
{
}
}
public interface IMaterialTypeResolver
{
public IMaterialSource ResolveType(SourceData i);
}
public class MaterialTypeResolver : IMaterialTypeResolver
{
public IMaterialSource ResolveType(SourceData sourceData)
{
//Problem here - I don't know how your ProjectUtils.EntityById works internally
//So I'm guessing here that it uses a factory pattern
//this method needs to return an IMaterialSource...
//GlazingMaterialSource
//AirGapMaterialSource
//ect ect
//for a given set of database values
//essentially you need something coming from the database to tell the code
//which type to create, we pass that the data base values
//and it passes them later to the IMaterial in the AddToOsm call
//so the IMaterial implementation can do whatever custom work
return new OpaqueProjectMaterialSource(sourceData);
}
}
public class ProjectUtils
{
private IMaterialTypeResolver _MaterialTypeResolver;
public ProjectUtils(IMaterialTypeResolver materialTypeResolver)
{
_MaterialTypeResolver = materialTypeResolver;
}
public T EntityById<T>(APIDbContext dbContext, Guid materialId) where T : new()
//here you'd use the dbContext to populated this
//though this one at a time stuff is pretty inefficient
//it should be done as a single query if possible
=> new T();
public IMaterialSource ResolveToMaterialSource(SourceData i) => _MaterialTypeResolver.ResolveType(i);
}
public interface IMaterial
{
public string Name { get; set;}
}
public abstract class MaterialBase : IMaterial
{
public string Name {get;set;}
public MaterialBase(Model model, ISourceOfData source)
{
this.Name = source.Name;
}
}
public class AirGapMaterial : MaterialBase
{
public AirGapMaterial(Model model, ISourceOfData source) : base(model, source)
{
}
}
public class StandardGlazingMaterial : MaterialBase
{
public StandardGlazingMaterial(Model model, ISourceOfData source) : base(model, source)
{
}
}
public class FenestrationMaterial : MaterialBase
{
public FenestrationMaterial(Model model, ISourceOfData source) : base(model, source)
{
}
}
public class StandardOpaqueMaterial : OpaqueMaterial
{
public StandardOpaqueMaterial(Model model, ISourceOfData source) : base(model, source)
{
}
}
public class OpaqueMaterial : MaterialBase
{
public OpaqueMaterial(Model model, ISourceOfData source) : base(model, source)
{
}
}
public class SurfaceConstruction : IIdentity<Guid>
{
[Key]
public Guid Id { get; set; }
private ProjectUtils _projectUtils;
public SurfaceConstruction(ProjectUtils projectUtils)
{
_projectUtils = projectUtils;
}
public Construction AddToOsm(APIDbContext dbContext, Model model, IEnumerable<Guid> ids)
{
if(model is null) throw new ArgumentNullException(nameof(model));
var materialSources = ids.Where(i => i != default)
.Select(i => _projectUtils.EntityById<SourceData>(dbContext, i))
.Select(i => _projectUtils.ResolveToMaterialSource(i));
using var materialVector = new MaterialVector();
foreach (var materialSource in materialSources)
{
materialVector.Add(materialSource.AddToOsm(model));
}
return new Construction(model, materialVector);
}
}
public interface IMaterialSource
{
public IMaterial AddToOsm(Model model);
}
public class SourceData : ISourceOfData, IIdentity<Guid>
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
//add all the database loaded properties here
}
public abstract class MaterialSourceBase : IMaterialSource
{
protected SourceData SourceData {get;set;}
protected MaterialSourceBase(SourceData sourceData)
{
SourceData = sourceData;
}
public abstract IMaterial AddToOsm(Model model);
}
//So at this point you could generalize all of this down to a single
//factory pattern backed function as all we care about are
//given a set of database values, give me an IMaterial
public class OpaqueProjectMaterialSource : MaterialSourceBase
{
public OpaqueProjectMaterialSource(SourceData sourceData) : base(sourceData)
{
}
public override IMaterial AddToOsm(Model model) => new StandardOpaqueMaterial(model, base.SourceData);
}
public class AirGapMaterialSource : MaterialSourceBase
{
public AirGapMaterialSource(SourceData sourceData) : base(sourceData)
{
}
public override IMaterial AddToOsm(Model model) => new AirGapMaterial(model, base.SourceData);
}
public class GlazingMaterialSource : MaterialSourceBase
{
public GlazingMaterialSource(SourceData sourceData) : base(sourceData)
{
}
public override IMaterial AddToOsm(Model model) => new StandardGlazingMaterial(model, base.SourceData);
}
Is it possible that each class object has its own static data store?
I mean, just to perform actions like:
class Program
{
static void Main(string[] args)
{
var car1 = new Car();
car1.Save(); ////saves in its own storage
var own1 = new Owner();
own1.Save(); //saves in its own storage as well
}
}
In code I tried, I get such error
'System.InvalidCastException`
at this place
var repo = (Repository<IEntity>) CurrentRepository;
Whats wrong and how could I make it?
Whole code is here:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
//var car1 = new Car();
//car1.Save(); ////saves in its own storage
//var own1 = new Owner();
//own1.Save(); //saves in its own storage as well var car1 = new Car();
}
}
public interface IEntity
{
long Id { get; }
}
public class Owner : Entity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Car Car { get; set; }
public Owner(string firstName, string lastName, Car car) : base(new Owner())
{
FirstName = firstName;
LastName = lastName;
Car = car;
}
public Owner() : base()
{
}
}
public class Car : Entity
{
public string Name { get; set; }
public int Year { get; set; }
public Car() : base()
{
}
public Car(string name, int year)
{
Name = name;
Year = year;
}
}
public abstract class Entity : IEntity
{
public long Id { get; }
public static object CurrentRepository { get; set; }
public Entity(Entity ent)
{
Type entityType = ent.GetType();
var instance = Activator.CreateInstance(typeof(Repository<>).MakeGenericType(entityType));
CurrentRepository = instance;
}
public Entity()
{
}
public void Save()
{
var repo = (Repository<IEntity>)CurrentRepository;
repo.Save(this);
}
public void Delete()
{
var repo = (Repository<IEntity>)CurrentRepository;
repo.Delete(this);
}
}
public interface IRepository<T> where T : IEntity
{
void Save(T entity);
void Delete(T entity);
T Find(long id);
}
public class Repository<T> : IRepository<T> where T : class, IEntity
{
protected BaseStorage<T> CustomDataStorage;
public Repository()
{
CustomDataStorage = new BaseStorage<T>();
}
public void Save(T entity)
{
CustomDataStorage.Add(entity);
}
public void Delete(T entity)
{
CustomDataStorage.Remove(entity);
}
public T Find(long id)
{
throw new NotImplementedException();
}
}
public class BaseStorage<T> : IStorage<T>
{
List<T> data = new List<T>();
public void Add(T entity)
{
data.Add(entity);
}
public void Remove(T entity)
{
throw new NotImplementedException();
}
}
public interface IStorage<T>
{
void Add(T entity);
void Remove(T entity);
}
}
In code I tried, I get such error
'System.InvalidCastException`
at this place var repo = (Repository) CurrentRepository;
Whats wrong and how could I make it?
That's because you can't directly cast the CurrentRepository, a type of (Repository<Car> or Repository<Owner>) into a Repository<IEntity>.
See here for more information on this...
To achieve what you wanted here, you could try it this way:
Make the Entity class generic (Entity<T>) and constraint the generic type as IEntity
Make the CurrentRepository a type of Repository<Entity<T>>
Move the static initialization of CurrentRepository from instance constructor to the static constructor.
Make the Owner and Car object subclass of Entity<T> with T refers to its own type making it a self referencing generics
Code:
public class Owner : Entity<Owner>
{
...
}
public class Car : Entity<Car>
{
...
}
public abstract class Entity<T> : IEntity
where T: IEntity
{
public long Id { get; }
public static Repository<Entity<T>> CurrentRepository { get; set; }
static Entity()
{
CurrentRepository = new Repository<Entity<T>>();
}
public Entity()
{
}
public void Save()
{
CurrentRepository.Save(this);
}
public void Delete()
{
CurrentRepository.Delete(this);
}
}
One option to consider would be to change Entity as below.
The ConcurrentDictionary is to ensure that you get one repository per type.
public abstract class Entity : IEntity
{
public long Id { get; }
public static ConcurrentDictionary<Type, dynamic> CurrentRepository = new ConcurrentDictionary<Type, dynamic>();
public Entity(Entity ent)
{
GetRepository(ent);
}
private static dynamic GetRepository(Entity ent)
{
Type entityType = ent.GetType();
return CurrentRepository.GetOrAdd(entityType, type =>
{
var instance = Activator.CreateInstance(typeof(Repository<>).MakeGenericType(entityType));
return instance;
});
}
public Entity()
{
}
public void Save()
{
var repo = GetRepository(this);
repo.Save((dynamic)this);
}
public void Delete()
{
var repo = GetRepository(this);
repo.Delete((dynamic)this);
}
}
I just started to play around with MongoDB (C#) and tried to port a repository over from entity framework. I'm using the official C# driver 1.0. Now I did something like this:
internal class MongoContext
{
public MongoContext(string constring)
{
MongoServer server = MongoServer.Create(constring);
this.myDB = server.GetDatabase("MyDB");
BsonClassMap.RegisterClassMap<VoyageNumber>(cm =>
{ cm.MapField<string>(p => p.Id); });
BsonClassMap.RegisterClassMap<Schedule>(cm =>
{ cm.MapField<DateTime>(p => p.EndDate); cm.MapField<DateTime>(p => p.StartDate); });
BsonClassMap.RegisterClassMap<Voyage>(cm =>
{ cm.MapIdField<VoyageNumber>(p => p.VoyageNumber); cm.MapField<Schedule>(p => p.Schedule); });
}
private MongoDatabase myDB;
public MongoDatabase MyDB
{ get { return this.myDB; } }
}
I'd then go on and implement the Repository like this:
public class MongoVoyageRepository : IVoyageRepository
{
private readonly MongoContext context;
public MongoVoyageRepository(string constring)
{
this.context = new MongoContext(constring);
}
public void Store(Domain.Model.Voyages.Voyage voyage)
{
MongoCollection<Voyage> mongoVoyages = context.MyDB.GetCollection<Voyage>("Voyages");
//store logic...
}
}
Now I'd like to know if it is a good decision to instantiate a "context" like this in terms of performance. Does it make sense to put the BsonClass Maps in there?
Thank you for your input.
// entity base
public class MongoEntity {
public ObjectId _id { get; set; }
}
//user entity
public class Users : MongoEntity {
public string UserName { get; set; }
public string Password { get; set; }
}
// simple repository
public class Repository {
private MongoDatabase _db;
public MongoDatabase Database { get; private set; }
public Repository(string constr, string dbname) {
var server = MongoServer.Create(constr);
_db = server.GetDatabase(dbname);
Database = _db;
}
private MongoCollection<T> GetCollection<T>() where T : MongoEntity {
return _db.GetCollection<T>(typeof(T).Name);
}
public IEnumerable<T> List<T>() where T : MongoEntity {
return GetCollection<T>().FindAll();
}
public IEnumerable<T> List<T>(Expression<Func<T, bool>> exp) where T : MongoEntity {
return GetCollection<T>().AsQueryable<T>().Where(exp);
}
public T Single<T>(Expression<Func<T, bool>> exp) where T : MongoEntity {
return List<T>(exp).SingleOrDefault();
}
public void Insert<T>(T entity) where T : MongoEntity {
GetCollection<T>().Insert<T>(entity);
}
public void Insert<T>(ICollection<T> entities) where T : MongoEntity {
GetCollection<T>().InsertBatch(entities);
}
// Update, Delete method etc ...
}
// example
var repository = new Repository("mongodb://localhost", "test");
repository.Single<Users>(u => u.UserName == "myUserName");
I guess it does not make sense to register classes mapping each time when you create your repository class. Since MongoDB C# driver manages connections to the MongoDB internally, it seems to me that it's better to create MongoServer and register classes mapping only once, during application start and then use it.
I am using singleton in order to create MongoServer only once
public class MongoRead : MongoBase
{
public MongoRead(MongoServer server)
: base(server)
{
}
public override MongoDatabase Database
{
get { return Server.GetDatabase("myDb"); }
}
public MongoCollection Logs
{
get { return Database.GetCollection("logs"); }
}
private static MongoRead _instance = null;
public static MongoRead Instance
{
get
{
if (_instance == null)
{
_instance = RegisterMongoDb();
}
return _instance;
}
}
private static MongoRead RegisterMongoDb()
{
var readServer = MongoServer.Create(connectionString);
var read = new MongoRead(readServer);
var myConventions = new ConventionProfile();
myConventions.SetIdMemberConvention(new NoDefaultPropertyIdConvention());
BsonClassMap.RegisterConventions(myConventions, t => true);
return read;
}
}
So you also can use above class in your Repository:
public class MongoVoyageRepository : IVoyageRepository
{
private readonly MongoRead context
{
get { return MongoRead.Instance; }
};
public MongoVoyageRepository()
{
}
public void Store(Domain.Model.Voyages.Voyage voyage)
{
MongoCollection<Voyage> mongoVoyages =
context.Database.GetCollection<Voyage>("Voyages");
//store logic...
}
}
this is also interesting if you want to use repository pattern.