2025-09-05 17:38:05 -05:00
|
|
|
using System.Linq;
|
2025-01-14 01:50:45 -06:00
|
|
|
using System.Linq.Expressions;
|
|
|
|
|
using LANCommander.Launcher.Data;
|
|
|
|
|
using LANCommander.Launcher.Data.Models;
|
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
|
|
|
|
|
|
namespace LANCommander.Launcher.Models;
|
|
|
|
|
|
|
|
|
|
public class BulkImportBuilder<TModel, TKeyedModel>
|
2025-09-05 17:38:05 -05:00
|
|
|
where TModel : BaseModel, new()
|
2025-01-14 01:50:45 -06:00
|
|
|
where TKeyedModel : SDK.Models.IKeyedModel
|
|
|
|
|
{
|
|
|
|
|
private readonly DatabaseContext _databaseContext;
|
|
|
|
|
|
|
|
|
|
private bool _remove = true;
|
2025-09-05 17:38:05 -05:00
|
|
|
private bool _batchMode = false;
|
|
|
|
|
private DbSet<TModel> _dbSet;
|
2025-01-14 01:50:45 -06:00
|
|
|
private ICollection<TModel> _target;
|
|
|
|
|
private IEnumerable<TKeyedModel> _source;
|
|
|
|
|
private TModel _existingTarget;
|
|
|
|
|
private List<Expression<Func<TModel, object>>> _includes = new();
|
|
|
|
|
private List<Action<TModel, TKeyedModel>> _assignActions = new();
|
2025-09-05 17:38:05 -05:00
|
|
|
private List<Action<TModel, TKeyedModel>> _relationshipActions = new();
|
2025-01-14 01:50:45 -06:00
|
|
|
|
|
|
|
|
public BulkImportBuilder(DatabaseContext databaseContext)
|
|
|
|
|
{
|
|
|
|
|
_databaseContext = databaseContext;
|
2025-09-05 17:38:05 -05:00
|
|
|
_dbSet = _databaseContext.Set<TModel>();
|
2025-01-14 01:50:45 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public BulkImportBuilder<TModel, TKeyedModel> SetTarget(ICollection<TModel> target)
|
|
|
|
|
{
|
|
|
|
|
_target = target;
|
|
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public BulkImportBuilder<TModel, TKeyedModel> UseSource(IEnumerable<TKeyedModel> source)
|
|
|
|
|
{
|
|
|
|
|
_source = source;
|
|
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public BulkImportBuilder<TModel, TKeyedModel> Include(Expression<Func<TModel, object>> expression)
|
|
|
|
|
{
|
|
|
|
|
_includes.Add(expression);
|
|
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public BulkImportBuilder<TModel, TKeyedModel> Assign(Action<TModel, TKeyedModel> assignAction)
|
|
|
|
|
{
|
|
|
|
|
_assignActions.Add(assignAction);
|
|
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-05 17:38:05 -05:00
|
|
|
public BulkImportBuilder<TModel, TKeyedModel> AssignRelationships(Action<TModel, TKeyedModel> relationshipAction)
|
|
|
|
|
{
|
|
|
|
|
_relationshipActions.Add(relationshipAction);
|
|
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-14 01:50:45 -06:00
|
|
|
public BulkImportBuilder<TModel, TKeyedModel> AsNoRemove()
|
|
|
|
|
{
|
|
|
|
|
_remove = false;
|
|
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-05 17:38:05 -05:00
|
|
|
public BulkImportBuilder<TModel, TKeyedModel> AsBatch()
|
2025-01-14 01:50:45 -06:00
|
|
|
{
|
2025-09-05 17:38:05 -05:00
|
|
|
_batchMode = true;
|
2025-01-14 01:50:45 -06:00
|
|
|
|
2025-09-05 17:38:05 -05:00
|
|
|
return this;
|
|
|
|
|
}
|
2025-01-14 01:50:45 -06:00
|
|
|
|
2025-09-05 17:38:05 -05:00
|
|
|
public async Task SaveChangesAsync()
|
|
|
|
|
{
|
|
|
|
|
await _databaseContext.SaveChangesAsync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<ICollection<TModel>> ImportAsync()
|
|
|
|
|
{
|
|
|
|
|
var entitiesToProcess = new List<(TModel entity, TKeyedModel source, bool isNew)>();
|
|
|
|
|
|
|
|
|
|
// First pass: prepare all entities and set basic properties
|
2025-01-14 01:50:45 -06:00
|
|
|
foreach (var source in _source)
|
|
|
|
|
{
|
2025-09-05 17:38:05 -05:00
|
|
|
// Check if entity is already being tracked first
|
|
|
|
|
var trackedEntity = _databaseContext.ChangeTracker.Entries<TModel>()
|
|
|
|
|
.FirstOrDefault(e => e.Entity.Id == source.Id);
|
|
|
|
|
|
|
|
|
|
TModel entity;
|
|
|
|
|
bool isNew = false;
|
|
|
|
|
|
|
|
|
|
if (trackedEntity != null)
|
2025-01-14 01:50:45 -06:00
|
|
|
{
|
2025-09-05 17:38:05 -05:00
|
|
|
// Use the already tracked entity
|
|
|
|
|
entity = trackedEntity.Entity;
|
2025-01-14 01:50:45 -06:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2025-09-05 17:38:05 -05:00
|
|
|
// Check if entity exists in database
|
|
|
|
|
var exists = await _dbSet.AsNoTracking().AnyAsync(e => e.Id == source.Id);
|
|
|
|
|
|
|
|
|
|
if (exists)
|
2025-01-14 01:50:45 -06:00
|
|
|
{
|
2025-09-05 17:38:05 -05:00
|
|
|
// Load the existing entity from database
|
|
|
|
|
entity = await _dbSet.FindAsync(source.Id);
|
|
|
|
|
if (entity == null)
|
|
|
|
|
{
|
|
|
|
|
// Fallback: create new entity if FindAsync returns null
|
|
|
|
|
entity = new TModel { Id = source.Id };
|
|
|
|
|
_databaseContext.Attach(entity);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Create new entity
|
|
|
|
|
entity = new TModel { Id = source.Id };
|
|
|
|
|
_databaseContext.Attach(entity);
|
|
|
|
|
_databaseContext.Entry(entity).State = EntityState.Added;
|
|
|
|
|
isNew = true;
|
2025-01-14 01:50:45 -06:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-05 17:38:05 -05:00
|
|
|
|
|
|
|
|
// Execute assign actions to set basic properties (but not relationships yet)
|
|
|
|
|
foreach (var assignAction in _assignActions)
|
|
|
|
|
assignAction(entity, source);
|
|
|
|
|
|
|
|
|
|
entitiesToProcess.Add((entity, source, isNew));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Save entities to ensure they exist in database for relationship establishment
|
|
|
|
|
// In batch mode, this will be the only save operation
|
|
|
|
|
await _databaseContext.SaveChangesAsync();
|
|
|
|
|
|
|
|
|
|
// Second pass: establish relationships and add to target collection
|
|
|
|
|
foreach (var (entity, source, isNew) in entitiesToProcess)
|
|
|
|
|
{
|
|
|
|
|
// Execute relationship actions after entities are saved
|
|
|
|
|
foreach (var relationshipAction in _relationshipActions)
|
|
|
|
|
relationshipAction(entity, source);
|
|
|
|
|
|
|
|
|
|
// Add to target collection if it's a new entity
|
|
|
|
|
if (isNew)
|
|
|
|
|
_target.Add(entity);
|
2025-01-14 01:50:45 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_remove)
|
|
|
|
|
{
|
|
|
|
|
var toRemove = _target.Where(x => !_source.Any(y => y.Id == x.Id)).ToList();
|
|
|
|
|
|
2025-09-05 17:38:05 -05:00
|
|
|
_databaseContext.RemoveRange(toRemove);
|
2025-01-14 01:50:45 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return _target;
|
|
|
|
|
}
|
|
|
|
|
}
|