I've created a base service which runs CRUD commands on a database using EF Core that I want other services to inherit. I'm trying to figure out if there is a way to infer the type being queried by the DbContext based on the types being passed to the service.
public class DbServiceBase<TDatabaseModel, TBusinessDisplayModel>
{
private readonly DbContext _context;
private readonly Mapper _mapper;
public DbServiceBase(DbContext context)
{
_context = context;
_mapper = new Mapper(MapperConfigurationGenerator.Invoke());
}
public async Task<List<TBusinessDisplayModel>> GetAll()
{
var dbResults = await _context.<TDatabaseModel>
.ToListAsync()
.ConfigureAwait(false);
return _mapper.Map<List<TBusinessDisplayModel>>(dbResults);
}
public async Task<TBusinessDisplayModel> GetById(long id)
{
var dbResult = await _context.<TDbModel>
.SingleAsync(x => x.Id == id)
.ConfigureAwait(false);
return _mapper.Map<TBusinessDisplayModel>(dbResult);
}
}
DbContext has a method called Set, that you can use to get a non-generic DbSet, such as:
dbSet = this.Set<SomeType>();
You should save this dbset in a private readonly field of you service and use this to query data.
The properties on the database context that are used to access the models in the database are usually implemented like this:
public DbSet<Post> Posts { get; set; }
EF will automatically populate those properties for your with database set objects for those entity types but you can also just create a database set from the context directly by calling DbContext.Set<T>. Since that method is already generic, you can use it to create the database set for the type you need in your implementation:
public async Task<List<TBusinessDisplayModel>> GetAll()
{
var dbResults = await _context.Set<TDatabaseModel>()
.ToListAsync()
.ConfigureAwait(false);
return _mapper.Map<List<TBusinessDisplayModel>>(dbResults);
}
Related
I have a Blazor App which uses EntityFrameworkCore and a Generic repository.
In my Services I query the repository, but when I try to call .ToListAsync(); - it requires that I add - using Microsoft.EntityFrameworkCore;
This means it will create a dependency to EF. I don't want to bind my service to EF.
.ToListAsync() is an extension method.
What is the best way to isolate the Service from it? I achieved it through an additional wrapper class - AsyncConverter. Is there a better way?
public class AsyncConverter : IAsyncConverter
{
public Task<List<TSource>> ConvertToListAsync<TSource>(IQueryable<TSource> source, CancellationToken cancellationToken = default)
{
return source.ToListAsync(cancellationToken);
}
}
public class EfRepository<TEntity> : IRepository<TEntity>
where TEntity : class
{
public EfRepository(ApplicationDbContext context)
{
this.Context = context ?? throw new ArgumentNullException(nameof(context));
this.DbSet = this.Context.Set<TEntity>();
}
protected DbSet<TEntity> DbSet { get; set; }
protected ApplicationDbContext Context { get; set; }
public virtual IQueryable<TEntity> All() => this.DbSet;
}
public class ItemsDataService : IItemsDataService
{
private readonly IRepository<Item> _itemsRepository;
private readonly IMapper _mapper;
private readonly IAsyncConverter _asyncConverter;
private readonly IStringLocalizer<Errors> _localizer;
public ItemsDataService(IRepository<Item> itemsRepository,
IMapper mapper,
IAsyncConverter asyncConverter,
IStringLocalizer<Errors> localizer = null)
{
_itemsRepository = itemsRepository;
_mapper = mapper;
_asyncConverter = asyncConverter;
_localizer = localizer;
}
public async Task<Response<IEnumerable<ItemNameDto>>> GetItemsNamesAsync(string searchWord, string userId)
{
try
{
searchWord.ThrowIfNull();
userId.ThrowIfNullOrEmpty();
var query = _itemsRepository.All()
.Where(x => x.UserId == userId);
var names = new List<ItemNameDto>();
if (!string.IsNullOrEmpty(searchWord))
{
query = query.Where(x => x.Name.ToLower().Contains(searchWord.ToLower()));
}
var queryProjection = _mapper.ProjectTo<ItemNameDto>(query); **/*.ToListAsync(); - This would add a dependency to EntityFrameworkCore. That it why I introduced AsyncConverter*/**
names = await _asyncConverter.ConvertToListAsync(queryProjection);
var response = ResponseBuilder.Success<IEnumerable<ItemNameDto>>(names);
return response;
}
catch (Exception ex)
{
var response = ResponseBuilder.Failure<IEnumerable<ItemNameDto>>("An error occured while getting items names.");
return response;
}
}
My usual approach is to put all the query-building code in my repositories, and have them return a materialized result. So I can mock the repository layer when unit-testing my services class.
Trying to unit-test the data access layer can be fraught, since it's inherently designed to be an integration layer. Queries that work one way when you are using in-memory objects may have different behavior when they're translated by Entity Framework.
If you prefer building your queries in the service layer, Ian Kemp's approach can work, basically putting your ConvertToListAsync method into the EfRepository class and its interface instead of having a whole new service to inject.
I am trying to build out a web application using a repository pattern. In doing so, I have noticed that my navigation properties are not loading. I was able to get my properties to load using EF Proxies, but when I went to use my DTOs, it really didn't like that. I have since removed all the Proxy bit of code and decided that I just need to provide a way to Eager Load the data I care about.
In my repository, I have an interface similar to this. Using DBSet, I am able to use the Find method to generically find the value and return it from the repo. Doing this, I am not able to pass in Includes to resolve the navigation properties.
public async Task<T> GetById(int Id)
{
var query = await this.Context.Set<T>().FindAsync(Id);
// Left split like this for clarity
return query;
}
The only way I got this working was by removing the generic piece and adding a new method called GetUserById into the UserRepository.
public class UserRepository : RepositoryBase<User>, IUserRepository
{
private readonly Context _context;
public UserRepository(Context context) : base(context) {
_context = context;
}
public User GetUserById(int Id)
{
var query = _context.Users.Include("Department").Where(x => x.Id == Id).FirstOrDefault();
return query;
}
}
Please tell me there is a better way.
Try this GetUserById implementation:
public User GetUserById(int Id, params Expression<Func<User, object>>[] includes)
{
var query = _context.Users.Where(x => x.Id == Id);
foreach (var include in includes)
{
query.Include(include);
}
return query.FirstOrDefault();
}
And this is how you would call it:
var repo = new UserRepository();
var user = repo.GetUserById(100, u => u.Department);
All,
I'm using Unit Of Work and Generic Repository patterns for data access in my ASP.NET MVC app with Entity Framework 5. My app worked fine when I only had to deal with 1 database and DbContext.
My problem is, I have a second not-very-closely-related database I need data access for, so I'm assuming I need a second DbContext. However, I don't know how to refactor my Generic Repository to accommodate more than one DbContext. Help!
Here's a simplified look at what I'm attempting so with comments explaining what's failing:
public class UnitOfWork
{
private Db1Entities db1Context = new Db1Entities();
private Db2Entities db2Context = new Db2Entities();
public GenericRepository<ObjectA> ObjectARepository { // ObjectA is sourced from database 1, so it uses a different DbContext than ObjectB.
get {
return new GenericRepository<ObjectA>(db1Context);
}
}
public GenericRepository<ObjectB> ObjectBRepository { // ObjectB is sourced from database 2, so it uses a different DbContext than ObjectA.
get {
return new GenericRepository<ObjectB>(db2Context);
}
}
}
public class GenericRepository<T> where T : class
{
internal DbContext context; // This line and the GenericRepository constructor are my problem. Type DbContext, being a base class, doesn't have the db objects that Db1Entities or Db2Entities have.
// When I used to only have to manage 1 DbContext, "context" was type Db1Entities and it worked fine.
internal DbSet<T> dbSet;
public GenericRepository(DbContext context) {
this.context = context;
this.dbSet = context.set<T>();
}
public virtual T GetByID (int id) {
// code to return a T object by id...
}
// Other repository methods...
}
FYI this is what I'm doing to get by for now. I don't consider it a solution because I'm duplicating "GenericRepository" by copying it as 2 generic repositories, each designed for a different DbContext. I think I ought to be able to use "GenericRepository" and be able to pass it a DbContext rather than making a GenericRepository class for each DbContext... I just don't know how to make it work. See below:
public class UnitOfWork
{
private Db1Entities db1Context = new Db1Entities();
private Db2Entities db2Context = new Db2Entities();
public GenericDb1Repository<ObjectA> ObjectARepository { // ObjectA is sourced from database 1, so it uses a different DbContext than ObjectB.
get {
return new GenericDb1Repository<ObjectA>(db1Context);
}
}
public GenericDb2Repository<ObjectB> ObjectBRepository { // ObjectB is sourced from database 2, so it uses a different DbContext than ObjectA.
get {
return new GenericDb2Repository<ObjectB>(db2Context);
}
}
}
public class GenericDb1Repository<T> where T : class
{
internal Db1Entities context;
internal DbSet<T> dbSet;
public GenericRepository(Db1Entities context) {
this.context = context;
this.dbSet = context.set<T>();
}
public virtual T GetByID (int id) {
// code to return a T object by id...
}
// Other repository methods...
}
public class GenericDb2Repository<T> where T : class
{
internal Db2Entities context;
internal DbSet<T> dbSet;
public GenericRepository(Db2Entities context) {
this.context = context;
this.dbSet = context.set<T>();
}
public virtual T GetByID (int id) {
// code to return a T object by id...
}
// Other repository methods...
}
public class GenericDb1Repository<T, TContext>
where T : class
where TContext : DbContext, new()
{
internal TContext context;
internal DbSet<T> dbSet;
public GenericRepository(TContext context) {
this.context = context;
this.dbSet = context.set<T>();
}
public virtual T GetByID (int id) {
// code to return a T object by id...
}
// Other repository methods...
}**
I want to create an anonymous method that uses AutoMapper to quickly convert any entity to one of its already mapped DTOs. Here's my code:
public static class Maps
{
public static IMapper Mapper;
public static void Init()
{
var config = new MapperConfiguration(cfg =>
cfg.CreateMap<Customer, DTO.Customer>());
Mapper = config.CreateMapper();
}
public static Dto ToDto<Entity, Dto>(this Entity entity)
{
return Mapper.Map<Dto>(entity);
}
}
This works, and I can use it like this in my service layer:
public DTO.Customer GetById(int Id)
{
return dbSet.FirstOrDefault(x => x.Id == Id).ToDto<Customer, DTO.Customer>();
}
However, since my FirstOrDefault() result is of Customer type, it seems redundant to have to put it in the call. How do I change the ToDto() method so that I don't need to include the Entity and simply call it using ToDto<DTO.Customer>()?
I want to create a simple DbContext class to avoid repetitive code in my repositories. The problem is that I don't know how to make it initialize/configure itself in constructor and then return itself. I only know how to assign additional property in that class and then call it.
I'm not sure how to properly articulate my problem so feel free to edit, however I think it will be very clear if you look at the code.
As you can see I'm calling .GetDatabase on every query, what I want is to remove .GetDatabase and make _db return what .GetDatabase is returning.
DbContext
public class DbContext
{
public DbContext()
{
var client = new MongoClient("mongodb://localhost");
var server = client.GetServer();
this.GetDatabase = server.GetDatabase("test");
}
public MongoDatabase GetDatabase { get; private set; }
}
Repository
public class AdministratorRepository
{
private readonly DbContext _db = new DbContext();
public Administrator GetByUsername(string username)
{
return _db.GetDatabase // I want to remove this .GetDatabase and make my _db itself return database
.GetCollection<Administrator>("administrators")
.FindOne(Query<Administrator>.EQ(x => x.Username, username));
}
}
You need to add one method to the DbContext class:
public ?? GetCollection<T>(string collection)
{
return _db.GetCollection<T>(collection);
}
I think the type is MongoCollection, but I'm not certain on that. Now you can do this:
return _db.GetCollection<Administrator>("administrators")
.FindOne(Query<Administrator>.EQ(x => x.Username, username));
Assuming your DbContext is only a wrapper you're using to initialize MongoDatabase, try this:
public class DbContext
{
public static MongoDatabase GetDatabase()
{
var client = new MongoClient("mongodb://localhost");
var server = client.GetServer();
return server.GetDatabase("test");
}
}
Then in client code:
private readonly MongoDatabase _db = DbContext.GetDatabase();
Also, you better pass the connection-string and database name as a parameter and avoid hard-coding it in the method.