using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System.Linq.Expressions; using System.Reflection; using LANCommander.Launcher.Data; using LANCommander.Launcher.Data.Models; using LANCommander.Launcher.Models; namespace LANCommander.Launcher.Services { public abstract class BaseDatabaseService : BaseService where T : BaseModel { protected DatabaseContext Context { get; set; } public BaseDatabaseService(DatabaseContext dbContext, ILogger logger) : base(logger) { Context = dbContext; } public virtual async Task> GetAsync() { return await Query(x => true).ToListAsync(); } public virtual async Task GetAsync(Guid id) { return await Context.Set().FindAsync(id); } public virtual async Task FirstOrDefaultAsync(Expression> predicate) { return await Context.Set().FirstOrDefaultAsync(predicate); } public virtual IQueryable Query(Expression> predicate) { return Context.Set().Where(predicate); } public virtual async Task ExistsAsync(Guid id) => await Context.Set().AnyAsync(x => x.Id == id); public virtual async Task ExistsAsync(Expression> predicate) => await Context.Set().AnyAsync(predicate); public virtual async Task AddAsync(T entity) { var result = await Context.Set().AddAsync(entity); entity = result.Entity; if (Context.Database.CurrentTransaction == null) await Context.SaveChangesAsync(); return entity; } /// /// 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 Query(predicate).FirstOrDefaultAsync(); if (existing == null) { entity = await AddAsync(entity); return new ExistingEntityResult { Value = entity, Existing = false, }; } else { return new ExistingEntityResult { Value = entity, Existing = true, }; } } public virtual async Task UpdateAsync(T entity) { var existing = await GetAsync(entity.Id); Context.Entry(existing).CurrentValues.SetValues(entity); entity = Context.Update(existing).Entity; if (Context.Database.CurrentTransaction == null) await Context.SaveChangesAsync(); return entity; } public virtual async Task SyncRelatedCollectionAsync( T entity, Expression>> navigationProperty, IEnumerable records, Func>> matchExpression) where TChild : class where T : class { Context.Attach(entity); var entry = Context.Entry(entity); var enumerableExpr = Expression.Lambda>>( navigationProperty.Body, navigationProperty.Parameters); var collectionEntry = entry.Collection(enumerableExpr); if (!collectionEntry.IsLoaded) await collectionEntry.LoadAsync(); var collection = navigationProperty.Compile().Invoke(entity); if (collection == null) { collection = new List(); if (navigationProperty.Body is not MemberExpression memberExpression || memberExpression.Member is not PropertyInfo propertyInfo) throw new InvalidOperationException($"Navigation expression '{navigationProperty}' must point to a property."); propertyInfo.SetValue(entity, collection); } var matchedChildren = new HashSet(); foreach (var record in records) { var matchPredicate = matchExpression(record); var existingChild = collection.FirstOrDefault(matchPredicate.Compile()); if (existingChild == null) { existingChild = await Context.Set() .FirstOrDefaultAsync(matchPredicate); } if (existingChild != null) { if (!collection.Contains(existingChild)) collection.Add(existingChild); matchedChildren.Add(existingChild); } } var toDelete = collection .Where(child => !matchedChildren.Contains(child)) .ToList(); foreach (var child in toDelete) { collection.Remove(child); } await Context.SaveChangesAsync(); } public virtual async Task SyncOwnedCollectionAsync( TEntity entity, Expression>> navigationProperty, IEnumerable incomingRecords, Func matchFunc, Action updateAction) where TChild : BaseModel where TEntity : class { Context.Attach(entity); var entry = Context.Entry(entity); var enumerableExpr = Expression.Lambda>>( navigationProperty.Body, navigationProperty.Parameters); var collectionEntry = entry.Collection(enumerableExpr); if (!collectionEntry.IsLoaded) await collectionEntry.LoadAsync(); var collection = navigationProperty.Compile().Invoke(entity); if (collection == null) { collection = new List(); if (navigationProperty.Body is not MemberExpression memberExpression || memberExpression.Member is not PropertyInfo propertyInfo) throw new InvalidOperationException($"Navigation expression '{navigationProperty}' must point to a property."); propertyInfo.SetValue(entity, collection); } var matched = new HashSet(); foreach (var incoming in incomingRecords) { var existing = collection.FirstOrDefault(c => matchFunc(c, incoming)); if (existing != null) { updateAction(existing, incoming); matched.Add(existing); } else { collection.Add(incoming); matched.Add(incoming); } } var toRemove = collection.Where(c => !matched.Contains(c)).ToList(); foreach (var child in toRemove) { collection.Remove(child); Context.Set().Remove(child); } await Context.SaveChangesAsync(); } public virtual async Task DeleteAsync(T entity) { Context.Set().Remove(entity); if (Context.Database.CurrentTransaction == null) await Context.SaveChangesAsync(); } } }