using LANCommander.Server.Data; using LANCommander.Server.Data.Enums; using LANCommander.Server.Data.Models; using LANCommander.Server.Services.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System.Linq.Expressions; using AutoMapper; using AutoMapper.QueryableExtensions; using ZiggyCreatures.Caching.Fusion; namespace LANCommander.Server.Services { public abstract class BaseDatabaseService( ILogger logger, IFusionCache cache, IMapper mapper, IDbContextFactory dbContextFactory) : BaseService(logger), IBaseDatabaseService where T : class, IBaseModel { protected readonly List, IQueryable>> _modifiers = new(); public IBaseDatabaseService AsNoTracking() { return Query((queryable) => { return queryable.AsNoTracking(); }); return this; } public IBaseDatabaseService Query(Func, IQueryable> modifier) { _modifiers.Add(modifier); return this; } public IBaseDatabaseService Include(params Expression>[] expressions) { return Query((queryable) => { foreach (var expression in expressions) { queryable = queryable.Include(expression); } return queryable; }); return this; } public IBaseDatabaseService SortBy(Expression> expression, SortDirection direction = SortDirection.Ascending) { switch (direction) { case SortDirection.Descending: return Query((queryable) => { return queryable.OrderByDescending(expression); }); case SortDirection.Ascending: default: return Query((queryable) => { return queryable.OrderBy(expression); }); } } public virtual async Task> GetAsync() { return await GetAsync(x => true); } public virtual async Task> GetAsync() { return await GetAsync(x => true); } public virtual async Task GetAsync(Guid id) { return await FirstOrDefaultAsync(x => x.Id == id); } public virtual async Task GetAsync(Guid id) { return await FirstOrDefaultAsync(x => x.Id == id); } public virtual async Task> GetAsync(Expression> predicate) { try { using var context = await dbContextFactory.CreateDbContextAsync(); var queryable = context.Set().AsQueryable(); foreach (var modifier in _modifiers) queryable = modifier.Invoke(queryable); return await queryable.Where(predicate).ToListAsync(); } finally { Reset(); } } public virtual async Task> GetAsync(Expression> predicate) { try { using var context = await dbContextFactory.CreateDbContextAsync(); var queryable = context.Set().AsQueryable(); foreach (var modifier in _modifiers) queryable = modifier.Invoke(queryable); return await queryable.Where(predicate).ProjectTo(mapper.ConfigurationProvider).ToListAsync(); } finally { Reset(); } } public virtual async Task FirstAsync(Expression> predicate) { try { using var context = await dbContextFactory.CreateDbContextAsync(); var queryable = context.Set().AsQueryable(); foreach (var modifier in _modifiers) queryable = modifier.Invoke(queryable); return await queryable.FirstAsync(predicate); } finally { Reset(); } } public virtual async Task FirstAsync(Expression> predicate) { try { using var context = await dbContextFactory.CreateDbContextAsync(); var queryable = context.Set().AsQueryable(); foreach (var modifier in _modifiers) queryable = modifier.Invoke(queryable); return await queryable.Where(predicate).ProjectTo(mapper.ConfigurationProvider).FirstAsync(); } finally { Reset(); } } public virtual async Task FirstOrDefaultAsync(Expression> predicate) { try { using var context = await dbContextFactory.CreateDbContextAsync(); var queryable = context.Set().AsQueryable(); foreach (var modifier in _modifiers) queryable = modifier.Invoke(queryable); return await queryable.FirstOrDefaultAsync(predicate); } finally { Reset(); } } public virtual async Task FirstOrDefaultAsync(Expression> predicate) { try { using var context = await dbContextFactory.CreateDbContextAsync(); var queryable = context.Set().AsQueryable(); foreach (var modifier in _modifiers) queryable = modifier.Invoke(queryable); return await queryable.Where(predicate).ProjectTo(mapper.ConfigurationProvider).FirstOrDefaultAsync(); } finally { Reset(); } } public virtual async Task ExistsAsync(Guid id) { return (await FirstOrDefaultAsync(x => x.Id == id)) != null; } public virtual async Task ExistsAsync(Expression> predicate) { return (await GetAsync(predicate)) != null; } public virtual async Task AddAsync(T entity) { try { using var context = await dbContextFactory.CreateDbContextAsync(); context.Set().Add(entity); await context.SaveChangesAsync(); return entity; } finally { Reset(); } } /// /// Adds an entity to the database if it does exist as dictated by the predicate /// /// Qualifier expressoin /// Entity to add /// Newly created or existing entity public virtual async Task> AddMissingAsync(Expression> predicate, T entity) { var existing = await FirstOrDefaultAsync(predicate); if (existing == null) { await cache.ExpireAsync($"{typeof(T).FullName}:Get"); entity = await AddAsync(entity); return new ExistingEntityResult { Value = entity, Existing = false }; } else { return new ExistingEntityResult { Value = existing, Existing = true }; } } public abstract Task UpdateAsync(T entity); protected async Task UpdateAsync(T updatedEntity, Action> additionalMapping = null) { using var context = await dbContextFactory.CreateDbContextAsync(); var existingEntity = await context.Set().FirstOrDefaultAsync(e => e.Id == updatedEntity.Id); //context.Entry(existingEntity).CurrentValues.SetValues(entity); context.Entry(existingEntity).CurrentValues.SetValues(updatedEntity); if (additionalMapping != null) { var updateContext = new UpdateEntityContext(context, existingEntity, updatedEntity); additionalMapping?.Invoke(updateContext); } await context.SaveChangesAsync(); return updatedEntity; } public virtual async Task DeleteAsync(T entity) { try { await cache.ExpireAsync($"{typeof(T).FullName}:Get"); using var context = await dbContextFactory.CreateDbContextAsync(); context.Set().Remove(entity); await context.SaveChangesAsync(); } finally { Reset(); } } protected void Reset() { _modifiers.Clear(); } } }