2025-01-24 21:31:42 -06:00
|
|
|
|
using System.Collections;
|
|
|
|
|
|
using LANCommander.SDK.Extensions;
|
2024-10-21 20:06:52 -05:00
|
|
|
|
using LANCommander.SDK;
|
|
|
|
|
|
using LANCommander.Server.Data.Models;
|
2024-10-07 18:04:26 -05:00
|
|
|
|
using Microsoft.AspNetCore.Http;
|
2023-01-02 15:44:04 -06:00
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
2024-10-21 20:06:52 -05:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
2023-01-02 15:44:04 -06:00
|
|
|
|
using System.Linq.Expressions;
|
2025-01-24 21:31:42 -06:00
|
|
|
|
using System.Linq;
|
2024-10-30 18:15:02 -05:00
|
|
|
|
using System.Threading;
|
2024-11-11 18:18:59 -06:00
|
|
|
|
using AutoMapper.QueryableExtensions;
|
|
|
|
|
|
using AutoMapper;
|
2024-11-25 22:29:56 -06:00
|
|
|
|
using LANCommander.Server.Data.Enums;
|
2025-01-24 21:31:42 -06:00
|
|
|
|
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
2025-01-24 23:15:39 -06:00
|
|
|
|
using SharpCompress.Common;
|
2023-01-02 15:44:04 -06:00
|
|
|
|
|
2024-08-04 18:44:33 -05:00
|
|
|
|
namespace LANCommander.Server.Data
|
2023-01-02 15:44:04 -06:00
|
|
|
|
{
|
2024-10-15 23:48:16 -05:00
|
|
|
|
public class Repository<T> : IDisposable where T : class, IBaseModel
|
2023-01-02 15:44:04 -06:00
|
|
|
|
{
|
2024-10-21 20:06:52 -05:00
|
|
|
|
public readonly DatabaseContext Context;
|
2024-11-11 18:18:59 -06:00
|
|
|
|
private readonly IMapper Mapper;
|
2024-10-14 00:59:54 -05:00
|
|
|
|
private readonly IHttpContextAccessor HttpContextAccessor;
|
2024-10-21 20:06:52 -05:00
|
|
|
|
private readonly ILogger Logger;
|
2023-01-02 15:44:04 -06:00
|
|
|
|
|
2024-11-25 22:29:56 -06:00
|
|
|
|
private List<Func<IQueryable<T>, IQueryable<T>>> Modifiers = new List<Func<IQueryable<T>, IQueryable<T>>>();
|
|
|
|
|
|
|
2024-10-23 01:59:17 -05:00
|
|
|
|
private User User;
|
|
|
|
|
|
|
2024-11-11 18:18:59 -06:00
|
|
|
|
public Repository(
|
2024-11-25 22:29:56 -06:00
|
|
|
|
IDbContextFactory<DatabaseContext> contextFactory,
|
2024-11-11 18:18:59 -06:00
|
|
|
|
IMapper mapper,
|
|
|
|
|
|
IHttpContextAccessor httpContextAccessor,
|
2024-11-25 22:29:56 -06:00
|
|
|
|
ILogger logger)
|
2023-01-02 15:44:04 -06:00
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
Context = contextFactory.CreateDbContext();
|
2024-11-11 18:18:59 -06:00
|
|
|
|
Mapper = mapper;
|
2024-10-14 00:59:54 -05:00
|
|
|
|
HttpContextAccessor = httpContextAccessor;
|
2024-10-21 20:06:52 -05:00
|
|
|
|
Logger = logger;
|
|
|
|
|
|
|
|
|
|
|
|
Logger?.LogDebug("Opened up context {ContextId}", Context.ContextId);
|
2023-01-02 15:44:04 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-25 22:29:56 -06:00
|
|
|
|
public Repository(
|
|
|
|
|
|
IDbContextFactory<DatabaseContext> contextFactory,
|
|
|
|
|
|
IMapper mapper,
|
|
|
|
|
|
IHttpContextAccessor httpContextAccessor)
|
|
|
|
|
|
{
|
|
|
|
|
|
Context = contextFactory.CreateDbContext();
|
|
|
|
|
|
Mapper = mapper;
|
|
|
|
|
|
HttpContextAccessor = httpContextAccessor;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-02 15:44:04 -06:00
|
|
|
|
private DbSet<T> DbSet
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return Context.Set<T>(); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private DbSet<User> UserDbSet
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return Context.Set<User>(); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-25 22:29:56 -06:00
|
|
|
|
public Repository<T> Query(Func<IQueryable<T>, IQueryable<T>> modifier)
|
|
|
|
|
|
{
|
|
|
|
|
|
Modifiers.Add(modifier);
|
|
|
|
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-30 18:15:02 -05:00
|
|
|
|
private IQueryable<T> Query(Expression<Func<T, bool>> predicate)
|
2023-01-02 15:44:04 -06:00
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
var queryable = DbSet.AsQueryable().Where(predicate);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var modifier in Modifiers)
|
2024-10-21 20:06:52 -05:00
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
queryable = modifier.Invoke(queryable);
|
|
|
|
|
|
}
|
2024-10-21 20:06:52 -05:00
|
|
|
|
|
2024-11-27 01:52:06 -06:00
|
|
|
|
if (Modifiers.Any(m => m.Method.Name.StartsWith("<Include>")))
|
|
|
|
|
|
queryable = queryable.AsSplitQuery();
|
|
|
|
|
|
|
2024-11-25 22:29:56 -06:00
|
|
|
|
return queryable;
|
|
|
|
|
|
}
|
2024-11-07 01:40:05 -06:00
|
|
|
|
|
2024-11-25 22:29:56 -06:00
|
|
|
|
public IQueryable<T> Query()
|
|
|
|
|
|
{
|
|
|
|
|
|
return DbSet.AsQueryable();
|
|
|
|
|
|
}
|
2024-10-21 20:06:52 -05:00
|
|
|
|
|
2024-11-25 22:29:56 -06:00
|
|
|
|
public Repository<T> AsNoTracking()
|
|
|
|
|
|
{
|
|
|
|
|
|
return Query((queryable) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
return queryable.AsNoTracking();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Repository<T> Include(params Expression<Func<T, object>>[] expressions)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Query((queryable) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var expression in expressions)
|
|
|
|
|
|
{
|
|
|
|
|
|
queryable = queryable.Include(expression);
|
|
|
|
|
|
}
|
2024-11-11 18:18:59 -06:00
|
|
|
|
|
2024-10-21 20:06:52 -05:00
|
|
|
|
return queryable;
|
2024-11-25 22:29:56 -06:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Repository<T> SortBy(Expression<Func<T, object>> 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);
|
|
|
|
|
|
});
|
2024-10-21 20:06:52 -05:00
|
|
|
|
}
|
2023-01-02 15:44:04 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-25 22:29:56 -06:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// With the current query, get a paginated list of results. Optimizes the query by only getting the amount of records specified by page size.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="expression">The query to filter results by</param>
|
|
|
|
|
|
/// <param name="pageNumber">The current page number (indexed by 1)</param>
|
|
|
|
|
|
/// <param name="pageSize">The number of results to get per page</param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public async Task<PaginatedResults<T>> PaginateAsync(Expression<Func<T, bool>> expression, int pageNumber, int pageSize)
|
2024-11-11 18:18:59 -06:00
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var results = new PaginatedResults<T>();
|
2024-11-11 18:18:59 -06:00
|
|
|
|
|
2024-11-25 22:29:56 -06:00
|
|
|
|
results.Count = await Query(expression).CountAsync();
|
|
|
|
|
|
results.Results = await Query(expression).Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
return results;
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
Modifiers.Clear();
|
|
|
|
|
|
}
|
2024-11-11 18:18:59 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-25 22:29:56 -06:00
|
|
|
|
public async Task<ICollection<T>> GetAsync()
|
2024-11-07 01:40:05 -06:00
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
return await Query().ToListAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
Reset();
|
|
|
|
|
|
}
|
2024-11-07 01:40:05 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
public async Task <ICollection<T>> GetAsync(Expression<Func<T, bool>> predicate)
|
2023-01-02 15:44:04 -06:00
|
|
|
|
{
|
2024-10-30 18:15:02 -05:00
|
|
|
|
try
|
2024-10-21 20:06:52 -05:00
|
|
|
|
{
|
2024-10-30 18:15:02 -05:00
|
|
|
|
return await Query(predicate).ToListAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
Reset();
|
2024-11-11 18:18:59 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
public async Task<ICollection<U>> GetAsync<U>(Expression<Func<T, bool>> predicate)
|
2024-11-11 18:18:59 -06:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
return await Query(predicate).ProjectTo<U>(Mapper.ConfigurationProvider).ToListAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
Reset();
|
2024-10-30 18:15:02 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
public async Task<T> FirstAsync(Expression<Func<T, bool>> predicate)
|
2024-10-30 18:15:02 -05:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
return await Query(predicate).FirstAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
Reset();
|
2024-11-11 18:18:59 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
public async Task<U> FirstAsync<U>(Expression<Func<T, bool>> predicate)
|
2024-11-11 18:18:59 -06:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
return await Query(predicate).ProjectTo<U>(Mapper.ConfigurationProvider).FirstAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
Reset();
|
2024-10-30 18:15:02 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
public async Task<T> FirstAsync<TKey>(Expression<Func<T, bool>> predicate, Expression<Func<T, TKey>> orderKeySelector)
|
2024-10-30 18:15:02 -05:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
return await Query(predicate).OrderByDescending(orderKeySelector).FirstAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
Reset();
|
2024-11-11 18:18:59 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
public async Task<U> FirstAsync<U, TKey>(Expression<Func<T, bool>> predicate, Expression<Func<U, TKey>> orderKeySelector)
|
2024-11-11 18:18:59 -06:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
return await Query(predicate).ProjectTo<U>(Mapper.ConfigurationProvider).OrderByDescending(orderKeySelector).FirstAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
Reset();
|
2024-10-30 18:15:02 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
public async Task<T> FindAsync(Guid id)
|
2024-10-30 18:15:02 -05:00
|
|
|
|
{
|
|
|
|
|
|
try {
|
2024-11-25 22:29:56 -06:00
|
|
|
|
//using (var op = Logger.BeginOperation("Finding entity with ID {EntityId}", id))
|
|
|
|
|
|
//{
|
2025-01-28 17:31:02 -06:00
|
|
|
|
var entity = await Query(x => x.Id == id).FirstOrDefaultAsync();
|
2024-10-30 18:15:02 -05:00
|
|
|
|
|
2024-11-25 22:29:56 -06:00
|
|
|
|
/// op.Complete();
|
2024-10-21 20:06:52 -05:00
|
|
|
|
|
2024-10-30 18:15:02 -05:00
|
|
|
|
return entity;
|
2024-11-25 22:29:56 -06:00
|
|
|
|
//}
|
2024-10-30 18:15:02 -05:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
Reset();
|
2024-11-11 18:18:59 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
public async Task<U> FindAsync<U>(Guid id)
|
2024-11-11 18:18:59 -06:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
//using (var op = Logger.BeginOperation("Finding entity with ID {EntityId}", id))
|
|
|
|
|
|
//{
|
2025-01-28 17:31:02 -06:00
|
|
|
|
var entity = await Query(x => x.Id == id).ProjectTo<U>(Mapper.ConfigurationProvider).FirstOrDefaultAsync();
|
2024-11-11 18:18:59 -06:00
|
|
|
|
|
2024-11-25 22:29:56 -06:00
|
|
|
|
// op.Complete();
|
2024-11-11 18:18:59 -06:00
|
|
|
|
|
|
|
|
|
|
return entity;
|
2024-11-25 22:29:56 -06:00
|
|
|
|
//}
|
2024-11-11 18:18:59 -06:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
Reset();
|
2024-10-21 20:06:52 -05:00
|
|
|
|
}
|
2023-01-02 15:44:04 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
public async Task<T> FirstOrDefaultAsync(Expression<Func<T, bool>> predicate)
|
2023-01-02 15:44:04 -06:00
|
|
|
|
{
|
2024-10-30 18:15:02 -05:00
|
|
|
|
try
|
2024-10-21 20:06:52 -05:00
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
//using (var op = Logger.BeginOperation("Getting first or default of type {EntityType}", typeof(T).Name))
|
|
|
|
|
|
//{
|
2024-10-30 18:15:02 -05:00
|
|
|
|
var entity = await Query(predicate).FirstOrDefaultAsync();
|
|
|
|
|
|
|
2024-11-25 22:29:56 -06:00
|
|
|
|
// op.Complete();
|
2024-10-30 18:15:02 -05:00
|
|
|
|
|
|
|
|
|
|
return entity;
|
2024-11-25 22:29:56 -06:00
|
|
|
|
//}
|
2024-10-30 18:15:02 -05:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
Reset();
|
2024-11-11 18:18:59 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
public async Task<U> FirstOrDefaultAsync<U>(Expression<Func<T, bool>> predicate)
|
2024-11-11 18:18:59 -06:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
//using (var op = Logger.BeginOperation("Getting first or default of type {EntityType}", typeof(T).Name))
|
|
|
|
|
|
//{
|
2024-11-11 18:18:59 -06:00
|
|
|
|
var entity = await Query(predicate).ProjectTo<U>(Mapper.ConfigurationProvider).FirstOrDefaultAsync();
|
|
|
|
|
|
|
2024-11-25 22:29:56 -06:00
|
|
|
|
// op.Complete();
|
2024-11-11 18:18:59 -06:00
|
|
|
|
|
|
|
|
|
|
return entity;
|
2024-11-25 22:29:56 -06:00
|
|
|
|
//}
|
2024-11-11 18:18:59 -06:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
Reset();
|
2024-10-30 18:15:02 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-10-21 20:06:52 -05:00
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
public async Task<T> FirstOrDefaultAsync<TKey>(Expression<Func<T, bool>> predicate, Expression<Func<T, TKey>> orderKeySelector)
|
2024-10-30 18:15:02 -05:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
return await Query(predicate).OrderByDescending(orderKeySelector).FirstOrDefaultAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
Reset();
|
2024-11-11 18:18:59 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
public async Task<U> FirstOrDefaultAsync<U, TKey>(Expression<Func<T, bool>> predicate, Expression<Func<U, TKey>> orderKeySelector)
|
2024-11-11 18:18:59 -06:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
return await Query(predicate).ProjectTo<U>(Mapper.ConfigurationProvider).OrderByDescending(orderKeySelector).FirstOrDefaultAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
Reset();
|
2024-10-21 20:06:52 -05:00
|
|
|
|
}
|
2023-01-02 15:44:04 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
public async Task<T> AddAsync(T entity)
|
2023-01-02 15:44:04 -06:00
|
|
|
|
{
|
2024-10-30 18:15:02 -05:00
|
|
|
|
try
|
2024-10-21 20:06:52 -05:00
|
|
|
|
{
|
2024-11-11 18:18:59 -06:00
|
|
|
|
var currentUser = await GetCurrentUserId();
|
|
|
|
|
|
|
2024-11-25 22:29:56 -06:00
|
|
|
|
//using (var op = Logger.BeginOperation("Adding entity of type {EntityType}", typeof(T).Name))
|
|
|
|
|
|
//{
|
2024-11-11 18:18:59 -06:00
|
|
|
|
entity.CreatedById = currentUser;
|
|
|
|
|
|
entity.UpdatedById = currentUser;
|
2024-10-30 18:15:02 -05:00
|
|
|
|
entity.CreatedOn = DateTime.UtcNow;
|
|
|
|
|
|
entity.UpdatedOn = DateTime.UtcNow;
|
2023-01-02 15:44:04 -06:00
|
|
|
|
|
2024-10-30 18:15:02 -05:00
|
|
|
|
await Context.AddAsync(entity);
|
2024-10-21 20:06:52 -05:00
|
|
|
|
|
2024-11-25 22:29:56 -06:00
|
|
|
|
//op.Complete();
|
2024-10-30 18:15:02 -05:00
|
|
|
|
|
|
|
|
|
|
return entity;
|
2024-11-25 22:29:56 -06:00
|
|
|
|
//}
|
2024-10-30 18:15:02 -05:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
Reset();
|
2024-10-21 20:06:52 -05:00
|
|
|
|
}
|
2023-01-02 15:44:04 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
public async Task<T> UpdateAsync(T entity)
|
2023-01-02 15:44:04 -06:00
|
|
|
|
{
|
2024-10-30 18:15:02 -05:00
|
|
|
|
try
|
2024-10-21 20:06:52 -05:00
|
|
|
|
{
|
2024-11-11 18:18:59 -06:00
|
|
|
|
var currentUserId = await GetCurrentUserId();
|
|
|
|
|
|
|
2024-11-27 19:36:44 -06:00
|
|
|
|
entity.UpdatedById = currentUserId;
|
|
|
|
|
|
entity.UpdatedOn = DateTime.UtcNow;
|
2023-01-02 15:44:04 -06:00
|
|
|
|
|
2025-01-24 21:31:42 -06:00
|
|
|
|
SafeAttach(entity);
|
|
|
|
|
|
|
2024-11-27 19:36:44 -06:00
|
|
|
|
return entity;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
return entity;
|
2024-10-30 18:15:02 -05:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
Reset();
|
2024-10-21 20:06:52 -05:00
|
|
|
|
}
|
2023-01-02 15:44:04 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-04 00:36:49 -06:00
|
|
|
|
public void Delete(T entity)
|
|
|
|
|
|
{
|
2024-10-30 18:15:02 -05:00
|
|
|
|
try
|
2024-10-21 20:06:52 -05:00
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
//using (var op = Logger.BeginOperation("Deleting entity with ID {EntityId}", entity.Id))
|
|
|
|
|
|
//{
|
2024-10-30 18:15:02 -05:00
|
|
|
|
Context.Remove(entity);
|
|
|
|
|
|
|
2024-11-25 22:29:56 -06:00
|
|
|
|
// op.Complete();
|
|
|
|
|
|
//}
|
2024-10-30 18:15:02 -05:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
Reset();
|
2024-10-21 20:06:52 -05:00
|
|
|
|
}
|
2023-01-04 00:36:49 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
public async Task SaveChangesAsync()
|
2023-01-02 15:44:04 -06:00
|
|
|
|
{
|
2024-10-30 18:15:02 -05:00
|
|
|
|
try
|
2024-10-21 20:06:52 -05:00
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
//using (var op = Logger.BeginOperation("Saving changes!"))
|
|
|
|
|
|
//{
|
2024-10-30 18:15:02 -05:00
|
|
|
|
await Context.SaveChangesAsync();
|
|
|
|
|
|
|
2024-11-25 22:29:56 -06:00
|
|
|
|
// op.Complete();
|
|
|
|
|
|
//}
|
2024-10-30 18:15:02 -05:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
Reset();
|
2024-10-21 20:06:52 -05:00
|
|
|
|
}
|
2023-01-02 15:44:04 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-01-24 21:31:42 -06:00
|
|
|
|
protected void SafeAttach<TEntity>(TEntity entity)
|
|
|
|
|
|
where TEntity : IBaseModel
|
|
|
|
|
|
{
|
|
|
|
|
|
Context.ChangeTracker.TrackGraph(entity, node =>
|
|
|
|
|
|
{
|
2025-01-25 00:11:31 -06:00
|
|
|
|
var id = entity.Id;
|
2025-01-24 21:31:42 -06:00
|
|
|
|
var entityType = node.Entry.Metadata;
|
|
|
|
|
|
|
|
|
|
|
|
var existingEntry = node.Entry.Context.ChangeTracker.Entries()
|
|
|
|
|
|
.Where(e => e.Entity is IBaseModel)
|
|
|
|
|
|
.FirstOrDefault(e => (e.Entity as IBaseModel).Id == id);
|
|
|
|
|
|
|
|
|
|
|
|
if (existingEntry == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (id == Guid.Empty)
|
|
|
|
|
|
node.Entry.State = EntityState.Added;
|
2025-01-25 00:11:31 -06:00
|
|
|
|
|
|
|
|
|
|
SafeAttachChildren(node.Entry.Entity as IBaseModel);
|
2025-01-24 21:31:42 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//else
|
|
|
|
|
|
// Logger.LogDebug($"Discarding duplicate {entityType.DisplayName()} entity with ID {id}");
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-01-24 23:15:39 -06:00
|
|
|
|
private void SafeAttachChildren<TEntity>(TEntity entity)
|
2025-01-24 21:31:42 -06:00
|
|
|
|
where TEntity : class, IBaseModel
|
|
|
|
|
|
{
|
|
|
|
|
|
// This is possibly the worst way of handling this. This should be refactored in the future.
|
|
|
|
|
|
// Really all this does is make sure that any navigation collections are set correctly and
|
|
|
|
|
|
// populated from existing data without introducing duplicates in the change tracker.
|
|
|
|
|
|
|
|
|
|
|
|
var entry = Context.Entry(entity);
|
2025-01-24 23:15:39 -06:00
|
|
|
|
entry.State = EntityState.Detached;
|
|
|
|
|
|
|
|
|
|
|
|
// Get the actual state of the entity from the database
|
|
|
|
|
|
var existing = Context.Find(entity.GetType(), entity.Id);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var property in Context.Entry(entity).CurrentValues.Properties)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!property.IsPrimaryKey())
|
|
|
|
|
|
{
|
|
|
|
|
|
Context.Entry(existing).Property(property.Name).CurrentValue =
|
2025-01-25 00:11:31 -06:00
|
|
|
|
entry.Property(property.Name).CurrentValue;
|
2025-01-24 23:15:39 -06:00
|
|
|
|
Context.Entry(existing).Property(property.Name).IsModified =
|
|
|
|
|
|
true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-01-24 21:31:42 -06:00
|
|
|
|
|
|
|
|
|
|
foreach (var navigation in entry.Navigations)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (navigation.CurrentValue is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (navigation.CurrentValue is IBaseModel navigationEntity)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Not needed? Seems like _:1 relationships work fine.
|
|
|
|
|
|
|
|
|
|
|
|
/*if (navigationEntity.Id != Guid.Empty)
|
|
|
|
|
|
SafeAttach(navigationEntity);
|
|
|
|
|
|
else if (navigationEntity.Id == entity.Id)
|
|
|
|
|
|
continue; // Probably already attached because it was passed into this method*/
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (navigation is CollectionEntry && navigation.CurrentValue != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var collectionType = navigation.CurrentValue.GetType();
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof(IEnumerable<IBaseModel>).IsAssignableFrom(collectionType))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (existing != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Load the navigation collection by property name
|
|
|
|
|
|
Context.Entry(existing).Collection(navigation.Metadata.Name).Load();
|
|
|
|
|
|
|
|
|
|
|
|
var existingCollection = Context.Entry(existing)
|
|
|
|
|
|
.Collection(navigation.Metadata.Name)
|
|
|
|
|
|
.CurrentValue;
|
|
|
|
|
|
|
|
|
|
|
|
// The real jank. Make the most generic List possible without knowing what type is used
|
|
|
|
|
|
// for the collection's generic type.
|
|
|
|
|
|
var genericListType = typeof(List<>).MakeGenericType(collectionType.GetGenericArguments()[0]);
|
|
|
|
|
|
|
|
|
|
|
|
IList updatedList = Activator.CreateInstance(genericListType) as IList;
|
|
|
|
|
|
|
|
|
|
|
|
// We have no access to LINQ sexiness here. Manually iterate through the collection.
|
|
|
|
|
|
foreach (var item in existingCollection)
|
|
|
|
|
|
{
|
|
|
|
|
|
var existsInNew = false;
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var newItem in navigation.CurrentValue as IEnumerable)
|
|
|
|
|
|
{
|
2025-01-24 23:15:39 -06:00
|
|
|
|
if ((item as IBaseModel).Id == (newItem as IBaseModel).Id && !existsInNew)
|
2025-01-24 21:31:42 -06:00
|
|
|
|
{
|
2025-01-25 00:11:31 -06:00
|
|
|
|
foreach (var property in Context.Entry(newItem).CurrentValues.Properties)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!property.IsPrimaryKey())
|
|
|
|
|
|
{
|
|
|
|
|
|
Context.Entry(item).Property(property.Name).CurrentValue =
|
|
|
|
|
|
Context.Entry(newItem).Property(property.Name).CurrentValue;
|
|
|
|
|
|
Context.Entry(item).Property(property.Name).IsModified =
|
|
|
|
|
|
true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Context.Entry(newItem).State = EntityState.Detached;
|
2025-01-24 21:31:42 -06:00
|
|
|
|
existsInNew = true;
|
2025-01-25 00:11:31 -06:00
|
|
|
|
|
|
|
|
|
|
updatedList.Add(item);
|
2025-01-24 21:31:42 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-01-25 00:11:31 -06:00
|
|
|
|
|
|
|
|
|
|
// May not have iterated over any existing items in the collection, rerun through and
|
|
|
|
|
|
// check against updated list
|
|
|
|
|
|
foreach (var newItem in navigation.CurrentValue as IEnumerable)
|
2025-01-24 21:31:42 -06:00
|
|
|
|
{
|
2025-01-25 00:11:31 -06:00
|
|
|
|
var alreadyAdded = false;
|
2025-01-24 21:31:42 -06:00
|
|
|
|
|
2025-01-25 00:11:31 -06:00
|
|
|
|
foreach (var updatedListItem in updatedList)
|
2025-01-24 21:31:42 -06:00
|
|
|
|
{
|
2025-01-25 00:11:31 -06:00
|
|
|
|
if ((newItem as IBaseModel).Id == (updatedListItem as IBaseModel).Id &&
|
|
|
|
|
|
!alreadyAdded)
|
2025-01-24 23:15:39 -06:00
|
|
|
|
{
|
2025-01-25 00:11:31 -06:00
|
|
|
|
alreadyAdded = true;
|
2025-01-24 23:15:39 -06:00
|
|
|
|
}
|
2025-01-24 21:31:42 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-01-25 00:11:31 -06:00
|
|
|
|
if (!alreadyAdded)
|
|
|
|
|
|
updatedList.Add(newItem);
|
2025-01-24 21:31:42 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Context.Entry(existing).Collection(navigation.Metadata.Name).CurrentValue = updatedList;
|
|
|
|
|
|
Context.Entry(existing).Collection(navigation.Metadata.Name).IsModified = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Not sure if this is needed, introduces recursion pretty badly
|
|
|
|
|
|
|
|
|
|
|
|
/*foreach (var item in navigation.CurrentValue as List<IBaseModel>)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (item.Id != Guid.Empty && item.Id != entity.Id)
|
|
|
|
|
|
SafeAttach(item);
|
|
|
|
|
|
}*/
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-23 01:59:17 -05:00
|
|
|
|
private async Task<User> GetUser(string username)
|
2023-01-02 15:44:04 -06:00
|
|
|
|
{
|
2024-10-30 18:15:02 -05:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2024-11-30 02:09:10 -06:00
|
|
|
|
return await UserDbSet.AsNoTracking().FirstOrDefaultAsync(u => u.UserName == username);
|
2024-10-30 18:15:02 -05:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
Reset();
|
2024-10-30 18:15:02 -05:00
|
|
|
|
}
|
2023-01-02 15:44:04 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-23 01:59:17 -05:00
|
|
|
|
private async Task<Guid?> GetCurrentUserId()
|
2023-01-02 15:44:04 -06:00
|
|
|
|
{
|
2024-10-14 00:59:54 -05:00
|
|
|
|
if (HttpContextAccessor?.HttpContext?.User?.Identity?.IsAuthenticated == true)
|
2023-01-02 15:44:04 -06:00
|
|
|
|
{
|
2024-10-23 01:59:17 -05:00
|
|
|
|
if (User == null)
|
|
|
|
|
|
User = await GetUser(HttpContextAccessor.HttpContext.User.Identity.Name);
|
2023-01-02 15:44:04 -06:00
|
|
|
|
|
2024-10-23 01:59:17 -05:00
|
|
|
|
if (User == null)
|
2023-01-03 01:06:48 -06:00
|
|
|
|
return null;
|
2023-01-02 15:44:04 -06:00
|
|
|
|
else
|
2024-10-23 01:59:17 -05:00
|
|
|
|
return User.Id;
|
2023-01-02 15:44:04 -06:00
|
|
|
|
}
|
2024-10-14 00:59:54 -05:00
|
|
|
|
else
|
2023-01-03 01:06:48 -06:00
|
|
|
|
return null;
|
2023-01-02 15:44:04 -06:00
|
|
|
|
}
|
2024-10-21 20:06:52 -05:00
|
|
|
|
|
2024-11-25 22:29:56 -06:00
|
|
|
|
private void Reset()
|
|
|
|
|
|
{
|
|
|
|
|
|
Modifiers.Clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-21 20:06:52 -05:00
|
|
|
|
public void Dispose()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2024-11-25 22:29:56 -06:00
|
|
|
|
Context.Dispose();
|
2024-10-21 20:06:52 -05:00
|
|
|
|
}
|
2024-11-25 22:29:56 -06:00
|
|
|
|
catch { }
|
2024-10-21 20:06:52 -05:00
|
|
|
|
}
|
2023-01-02 15:44:04 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|