generic IRepository

This commit is contained in:
admin 2025-08-09 15:03:12 +02:00
parent 07531425f2
commit a784630322
15 changed files with 170 additions and 32 deletions

4
.gitignore vendored
View File

@ -4,10 +4,14 @@
ApplicationHub/bin ApplicationHub/bin
ApplicationHub/obj ApplicationHub/obj
ApplicationHub/*.db ApplicationHub/*.db
ApplicationHub/*.db-shm
ApplicationHub/*.db-wal
ApplicationHub.Data.EF/bin ApplicationHub.Data.EF/bin
ApplicationHub.Data.EF/obj ApplicationHub.Data.EF/obj
ApplicationHub.Data.EF/*.db ApplicationHub.Data.EF/*.db
ApplicationHub.Data.EF/*.db-shm
ApplicationHub.Data.EF/*.db-wal
ApplicationHub.Data.InMemory/bin ApplicationHub.Data.InMemory/bin
ApplicationHub.Data.InMemory/obj ApplicationHub.Data.InMemory/obj

View File

@ -21,4 +21,8 @@
<ProjectReference Include="..\ApplicationHub.Domain.Contracts\ApplicationHub.Domain.Contracts.csproj" /> <ProjectReference Include="..\ApplicationHub.Domain.Contracts\ApplicationHub.Domain.Contracts.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Authentication\ExpressionConverters\" />
</ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,8 @@
using ApplicationHub.Data.EF.Authentication.Entities;
using ApplicationHub.Data.EF.Utils;
using ApplicationHub.Domain.Contracts.Authentication.Models;
using AutoMapper;
namespace ApplicationHub.Data.EF.Authentication.Repositories;
public class GroupRepository(AuthenticationDataContext authenticationDataContext, IMapper mapper) : BaseRepository<Group, GroupEntity>(authenticationDataContext.Groups, mapper);

View File

@ -0,0 +1,8 @@
using ApplicationHub.Data.EF.Authentication.Entities;
using ApplicationHub.Data.EF.Utils;
using ApplicationHub.Domain.Contracts.Authentication.Models;
using AutoMapper;
namespace ApplicationHub.Data.EF.Authentication.Repositories;
public class RightRepository(AuthenticationDataContext authenticationDataContext, IMapper mapper) : BaseRepository<Right, RightEntity>(authenticationDataContext.Rights, mapper);

View File

@ -1,20 +1,8 @@
using ApplicationHub.Domain.Contracts.Authentication; using ApplicationHub.Data.EF.Authentication.Entities;
using ApplicationHub.Data.EF.Utils;
using ApplicationHub.Domain.Contracts.Authentication.Models; using ApplicationHub.Domain.Contracts.Authentication.Models;
using AutoMapper; using AutoMapper;
using Microsoft.EntityFrameworkCore;
namespace ApplicationHub.Data.EF.Authentication.Repositories; namespace ApplicationHub.Data.EF.Authentication.Repositories;
public class UserRepository(AuthenticationDataContext authenticationDataContext, IMapper mapper) : IUserRepository public class UserRepository(AuthenticationDataContext authenticationDataContext, IMapper mapper) : BaseRepository<User, UserEntity>(authenticationDataContext.Users, mapper);
{
public User? GetUserByUserName(string userName)
{
return mapper.Map<List<User>>(
authenticationDataContext.Users
.Include(x => x.Groups)
.Where(x => x.UserName == userName && x.Active)
.ToList()
)
.FirstOrDefault();
}
}

View File

@ -0,0 +1,16 @@
using System.Linq.Expressions;
using ApplicationHub.Domain.Contracts;
using AutoMapper;
using Microsoft.EntityFrameworkCore;
namespace ApplicationHub.Data.EF.Utils;
public class BaseRepository<TModel, TEntity>(DbSet<TEntity> dbSet, IMapper mapper) : IRepository<TModel> where TEntity : class
{
public IEnumerable<TModel> Find(Expression<Func<TModel, bool>>? where = null, int? limit = null, int? offset = null, List<KeyValuePair<Expression<Func<TModel, bool>>, OrderDirection>>? order = null)
{
var resolver = new QueryResolver<TModel, TEntity>();
var result = resolver.Find(dbSet, where, limit, offset, order).ToList();
return mapper.Map<List<TModel>>(result);
}
}

View File

@ -0,0 +1,57 @@
using System.Linq.Expressions;
using ApplicationHub.Domain.Contracts;
using ApplicationHub.Domain.Contracts.Linq;
using Microsoft.EntityFrameworkCore;
namespace ApplicationHub.Data.EF.Utils;
public class QueryResolver<TModel, TEntity> where TEntity : class
{
public IQueryable<TEntity> Find(DbSet<TEntity> dbSet, Expression<Func<TModel, bool>>? where = null, int? limit = null, int? offset = null, List<KeyValuePair<Expression<Func<TModel, bool>>, OrderDirection>>? order = null)
{
IQueryable<TEntity>? expression = null;
if (where != null)
{
var whereConverter = new EntityExpressionConverter<TModel, TEntity>(where.Parameters.Single());
expression = dbSet.Where(whereConverter.Convert(where));
}
if (offset.HasValue)
{
expression = expression == null
? dbSet.Skip(offset.Value)
: expression.Skip(offset.Value);
}
if (limit.HasValue)
{
expression = expression == null
? dbSet.Take(limit.Value)
: expression.Take(limit.Value);
}
if (order != null && order.Any())
{
foreach (var orderDescription in order)
{
var orderConverter = new EntityExpressionConverter<TModel, TEntity>(orderDescription.Key.Parameters.Single());
var convertedOrder = orderConverter.Convert(orderDescription.Key);
switch (orderDescription.Value)
{
case OrderDirection.Asc:
expression = expression == null
? dbSet.OrderBy(convertedOrder)
: expression.OrderBy(convertedOrder);
break;
case OrderDirection.Desc:
expression = expression == null
? dbSet.OrderByDescending(convertedOrder)
: expression.OrderByDescending(convertedOrder);
break;
}
}
}
return expression ?? dbSet.Where(x => true);
}
}

View File

@ -1,10 +1,11 @@
using ApplicationHub.Domain.Contracts.Authentication; using System.Linq.Expressions;
using ApplicationHub.Domain.Contracts;
using ApplicationHub.Domain.Contracts.Authentication.Models; using ApplicationHub.Domain.Contracts.Authentication.Models;
using AutoMapper; using AutoMapper;
namespace ApplicationHub.Data.InMemory.Authentication; namespace ApplicationHub.Data.InMemory.Authentication;
public class UserDataContext(IMapper mapper) : IUserRepository public class UserDataContext(IMapper mapper) : IRepository<User>
{ {
public User? GetUserByUserName(string userName) public User? GetUserByUserName(string userName)
{ {
@ -13,5 +14,10 @@ public class UserDataContext(IMapper mapper) : IUserRepository
.ToList(); .ToList();
return mapper.Map<List<User>>(result).FirstOrDefault(); return mapper.Map<List<User>>(result).FirstOrDefault();
} }
public IEnumerable<User> Find(Expression<Func<User, bool>>? where = null, int? limit = null, int? offset = null, List<KeyValuePair<Expression<Func<User, bool>>, OrderDirection>>? order = null)
{
throw new NotImplementedException();
}
} }

View File

@ -1,8 +0,0 @@
using ApplicationHub.Domain.Contracts.Authentication.Models;
namespace ApplicationHub.Domain.Contracts.Authentication;
public interface IUserRepository
{
public User? GetUserByUserName(string userName);
}

View File

@ -0,0 +1,8 @@
using System.Linq.Expressions;
namespace ApplicationHub.Domain.Contracts;
public interface IRepository<TModel>
{
IEnumerable<TModel> Find(Expression<Func<TModel, bool>>? where = null, int? limit = null, int? offset = null, List<KeyValuePair<Expression<Func<TModel, bool>>, OrderDirection>>? order = null);
}

View File

@ -0,0 +1,36 @@
using System.Linq.Expressions;
namespace ApplicationHub.Domain.Contracts.Linq;
public class EntityExpressionConverter<TModel, TEntity> : ExpressionVisitor
{
private readonly ParameterExpression _entityParameter;
private readonly ParameterExpression _modelParameter;
public EntityExpressionConverter(ParameterExpression modelParameter)
{
_modelParameter = modelParameter;
_entityParameter = Expression.Parameter(typeof(TEntity), "entity");
}
public Expression<Func<TEntity, bool>> Convert(Expression<Func<TModel, bool>> expression)
{
var body = Visit(expression.Body);
return Expression.Lambda<Func<TEntity, bool>>(body, _entityParameter);
}
protected override Expression VisitParameter(ParameterExpression node)
{
return node == _modelParameter ? _entityParameter : base.VisitParameter(node);
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression == _modelParameter)
{
return Expression.Property(_entityParameter, node.Member.Name);
}
return base.VisitMember(node);
}
}

View File

@ -0,0 +1,7 @@
namespace ApplicationHub.Domain.Contracts;
public enum OrderDirection
{
Asc,
Desc
}

View File

@ -1,12 +1,12 @@
using ApplicationHub.Domain.Contracts.Authentication; using ApplicationHub.Domain.Contracts;
using ApplicationHub.Domain.Contracts.Authentication.Models; using ApplicationHub.Domain.Contracts.Authentication.Models;
namespace Adapter.Authentication.Services; namespace Adapter.Authentication.Services;
public class AuthenticationService(IUserRepository userRepository) public class AuthenticationService(IRepository<User> userRepository)
{ {
public User? GetUserByUserName(string userName) public User? GetUserByUserName(string userName)
{ {
return userRepository.GetUserByUserName(userName); return userRepository.Find(x => x.UserName == userName && x.Active).FirstOrDefault();
} }
} }

View File

@ -1,8 +1,7 @@
using ApplicationHub.Data.EF.Authentication.Entities; using ApplicationHub.Data.EF.Authentication.Entities;
using ApplicationHub.Data.EF.Authentication.Repositories; using ApplicationHub.Data.EF.Authentication.Repositories;
using ApplicationHub.Data.InMemory.Authentication; using ApplicationHub.Domain.Contracts;
using ApplicationHub.Data.InMemory.Authentication.Entities; using ApplicationHub.Domain.Contracts.Authentication.Models;
using ApplicationHub.Domain.Contracts.Authentication;
namespace ApplicationHub.Configuration; namespace ApplicationHub.Configuration;
@ -16,7 +15,9 @@ public static class AdapterConfiguration
{ {
// use EF implementation // use EF implementation
builder.Services.AddAutoMapper(typeof(UserEntity)); builder.Services.AddAutoMapper(typeof(UserEntity));
builder.Services.AddScoped<IUserRepository, UserRepository>(); builder.Services.AddScoped<IRepository<User>, UserRepository>();
builder.Services.AddScoped<IRepository<Group>, GroupRepository>();
builder.Services.AddScoped<IRepository<Right>, RightRepository>();
// use InMemory implementation // use InMemory implementation
// builder.Services.AddScoped<IUserRepository, UserDataContext>(); // builder.Services.AddScoped<IUserRepository, UserDataContext>();

View File

@ -0,0 +1,3 @@
# Application Hub
A Web Application to manage multiple REST Applications and call it.