using LANCommander.SDK.Extensions; using LANCommander.SDK; using LANCommander.Server.Data.Models; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System.Linq.Expressions; using System.Threading; using AutoMapper.QueryableExtensions; using AutoMapper; namespace LANCommander.Server.Data { public class Repository : IDisposable where T : class, IBaseModel { public readonly DatabaseContext Context; private readonly IMapper Mapper; private readonly IHttpContextAccessor HttpContextAccessor; private readonly ILogger Logger; private List>> IncludeExpressions { get; } = new(); private User User; private bool Tracking = true; public Repository( DatabaseContext context, IMapper mapper, IHttpContextAccessor httpContextAccessor, ILogger> logger) { Context = context; Mapper = mapper; HttpContextAccessor = httpContextAccessor; Logger = logger; Logger?.LogDebug("Opened up context {ContextId}", Context.ContextId); } private DbSet DbSet { get { return Context.Set(); } } private DbSet UserDbSet { get { return Context.Set(); } } private IQueryable Query(Expression> predicate) { using (var op = Logger.BeginOperation("Querying database")) { var queryable = DbSet.AsQueryable().Where(predicate); foreach (var includeExpression in IncludeExpressions) { queryable = queryable.Include(includeExpression); } op.Complete(); if (!Tracking) queryable = queryable.AsNoTracking(); return queryable; } } public Repository AsNoTracking() { Tracking = false; return this; } public Repository Include(Expression> includeExpression) { IncludeExpressions.Add(includeExpression); return this; } public async Task > GetAsync(Expression> predicate) { try { await Context.ContextMutex.WaitAsync(); return await Query(predicate).ToListAsync(); } finally { Tracking = true; IncludeExpressions.Clear(); Context.ContextMutex.Release(); } } public async Task> GetAsync(Expression> predicate) { try { await Context.ContextMutex.WaitAsync(); return await Query(predicate).ProjectTo(Mapper.ConfigurationProvider).ToListAsync(); } finally { Tracking = true; IncludeExpressions.Clear(); Context.ContextMutex.Release(); } } public async Task FirstAsync(Expression> predicate) { try { await Context.ContextMutex.WaitAsync(); return await Query(predicate).FirstAsync(); } finally { Tracking = true; IncludeExpressions.Clear(); Context.ContextMutex.Release(); } } public async Task FirstAsync(Expression> predicate) { try { await Context.ContextMutex.WaitAsync(); return await Query(predicate).ProjectTo(Mapper.ConfigurationProvider).FirstAsync(); } finally { Tracking = true; IncludeExpressions.Clear(); Context.ContextMutex.Release(); } } public async Task FirstAsync(Expression> predicate, Expression> orderKeySelector) { try { await Context.ContextMutex.WaitAsync(); return await Query(predicate).OrderByDescending(orderKeySelector).FirstAsync(); } finally { Tracking = true; IncludeExpressions.Clear(); Context.ContextMutex.Release(); } } public async Task FirstAsync(Expression> predicate, Expression> orderKeySelector) { try { await Context.ContextMutex.WaitAsync(); return await Query(predicate).ProjectTo(Mapper.ConfigurationProvider).OrderByDescending(orderKeySelector).FirstAsync(); } finally { Tracking = true; IncludeExpressions.Clear(); Context.ContextMutex.Release(); } } public async Task FindAsync(Guid id) { try { await Context.ContextMutex.WaitAsync(); using (var op = Logger.BeginOperation("Finding entity with ID {EntityId}", id)) { var entity = await Query(x => x.Id == id).FirstAsync(); op.Complete(); return entity; } } finally { Tracking = true; IncludeExpressions.Clear(); Context.ContextMutex.Release(); } } public async Task FindAsync(Guid id) { try { await Context.ContextMutex.WaitAsync(); using (var op = Logger.BeginOperation("Finding entity with ID {EntityId}", id)) { var entity = await Query(x => x.Id == id).ProjectTo(Mapper.ConfigurationProvider).FirstAsync(); op.Complete(); return entity; } } finally { Tracking = true; IncludeExpressions.Clear(); Context.ContextMutex.Release(); } } public async Task FirstOrDefaultAsync(Expression> predicate) { try { await Context.ContextMutex.WaitAsync(); using (var op = Logger.BeginOperation("Getting first or default of type {EntityType}", typeof(T).Name)) { var entity = await Query(predicate).FirstOrDefaultAsync(); op.Complete(); return entity; } } finally { Tracking = true; IncludeExpressions.Clear(); Context.ContextMutex.Release(); } } public async Task FirstOrDefaultAsync(Expression> predicate) { try { await Context.ContextMutex.WaitAsync(); using (var op = Logger.BeginOperation("Getting first or default of type {EntityType}", typeof(T).Name)) { var entity = await Query(predicate).ProjectTo(Mapper.ConfigurationProvider).FirstOrDefaultAsync(); op.Complete(); return entity; } } finally { Tracking = true; IncludeExpressions.Clear(); Context.ContextMutex.Release(); } } public async Task FirstOrDefaultAsync(Expression> predicate, Expression> orderKeySelector) { try { await Context.ContextMutex.WaitAsync(); return await Query(predicate).OrderByDescending(orderKeySelector).FirstOrDefaultAsync(); } finally { Tracking = true; IncludeExpressions.Clear(); Context.ContextMutex.Release(); } } public async Task FirstOrDefaultAsync(Expression> predicate, Expression> orderKeySelector) { try { await Context.ContextMutex.WaitAsync(); return await Query(predicate).ProjectTo(Mapper.ConfigurationProvider).OrderByDescending(orderKeySelector).FirstOrDefaultAsync(); } finally { Tracking = true; IncludeExpressions.Clear(); Context.ContextMutex.Release(); } } public async Task AddAsync(T entity) { try { var currentUser = await GetCurrentUserId(); await Context.ContextMutex.WaitAsync(); using (var op = Logger.BeginOperation("Adding entity of type {EntityType}", typeof(T).Name)) { entity.CreatedById = currentUser; entity.UpdatedById = currentUser; entity.CreatedOn = DateTime.UtcNow; entity.UpdatedOn = DateTime.UtcNow; await Context.AddAsync(entity); op.Complete(); return entity; } } finally { Tracking = true; Context.ContextMutex.Release(); } } public async Task UpdateAsync(T entity) { try { var currentUserId = await GetCurrentUserId(); var existing = await FindAsync(entity.Id); await Context.ContextMutex.WaitAsync(); using (var op = Logger.BeginOperation("Updating entity with ID {EntityId}", entity.Id)) { Context.Entry(existing).CurrentValues.SetValues(entity); entity.UpdatedById = currentUserId; entity.UpdatedOn = DateTime.UtcNow; Context.Update(entity); op.Complete(); return entity; } } finally { Tracking = true; Context.ContextMutex.Release(); } } public void Delete(T entity) { try { Context.ContextMutex.Wait(); using (var op = Logger.BeginOperation("Deleting entity with ID {EntityId}", entity.Id)) { Context.Remove(entity); op.Complete(); } } finally { Tracking = true; Context.ContextMutex.Release(); } } public async Task SaveChangesAsync() { try { await Context.ContextMutex.WaitAsync(); using (var op = Logger.BeginOperation("Saving changes!")) { await Context.SaveChangesAsync(); op.Complete(); } } finally { Tracking = true; Context.ContextMutex.Release(); } } private async Task GetUser(string username) { try { await Context.ContextMutex.WaitAsync(); return await UserDbSet.FirstOrDefaultAsync(u => u.UserName == username); } finally { Tracking = true; Context.ContextMutex.Release(); } } private async Task GetCurrentUserId() { if (HttpContextAccessor?.HttpContext?.User?.Identity?.IsAuthenticated == true) { if (User == null) User = await GetUser(HttpContextAccessor.HttpContext.User.Identity.Name); if (User == null) return null; else return User.Id; } else return null; } public void Dispose() { try { Context.ContextMutex.Release(); Logger?.LogDebug("Disposed context {ContextId}", Context.ContextId); } catch { Logger?.LogDebug("Could not dispose context {ContextId}", Context.ContextId); } } } }