138 lines
No EOL
5.4 KiB
C#
138 lines
No EOL
5.4 KiB
C#
using System.Linq.Expressions;
|
|
using LANCommander.Server.Data.Models;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace LANCommander.Server.Data;
|
|
|
|
public class UpdateEntityContext<TEntity>
|
|
where TEntity : class, IBaseModel
|
|
{
|
|
private readonly DatabaseContext _context;
|
|
private readonly TEntity _entity;
|
|
private readonly TEntity _updatedEntity;
|
|
|
|
public UpdateEntityContext(DatabaseContext context, TEntity entity, TEntity updatedEntity)
|
|
{
|
|
_context = context;
|
|
_entity = entity;
|
|
_updatedEntity = updatedEntity;
|
|
}
|
|
|
|
public async Task UpdateRelationshipAsync<TRelatedEntity>(
|
|
Expression<Func<TEntity, TRelatedEntity?>> navigationPropertyPath)
|
|
where TRelatedEntity : class, IBaseModel
|
|
{
|
|
var compiledExpression = navigationPropertyPath.Compile();
|
|
|
|
// Get the updated entity from the new entity
|
|
var relatedEntity = compiledExpression.Invoke(_updatedEntity);
|
|
|
|
if (relatedEntity != null)
|
|
{
|
|
var existingEntityEntry = _context.Entry(_entity);
|
|
var navigation = existingEntityEntry.Reference(navigationPropertyPath);
|
|
|
|
// Ensure the existing entity is loaded if needed
|
|
if (!navigation.IsLoaded)
|
|
{
|
|
await navigation.LoadAsync();
|
|
}
|
|
|
|
// Check if the related entity is already being tracked
|
|
var trackedRelatedEntity = _context.Set<TRelatedEntity>().Local
|
|
.FirstOrDefault(e => e.Id == relatedEntity.Id);
|
|
|
|
if (trackedRelatedEntity != null)
|
|
{
|
|
// Use the already tracked instance to avoid duplicate tracking
|
|
navigation.CurrentValue = trackedRelatedEntity;
|
|
}
|
|
else
|
|
{
|
|
// Fetch the related entity from the database (ensuring EF Core tracks it)
|
|
var existingRelatedEntity = await _context.Set<TRelatedEntity>()
|
|
.FirstOrDefaultAsync(e => e.Id == relatedEntity.Id);
|
|
|
|
if (existingRelatedEntity != null)
|
|
{
|
|
navigation.CurrentValue = existingRelatedEntity; // Use the tracked instance
|
|
}
|
|
else
|
|
{
|
|
// If the entity is truly new and not in the DB, attach it
|
|
_context.Attach(relatedEntity);
|
|
navigation.CurrentValue = relatedEntity;
|
|
}
|
|
}
|
|
|
|
_context.Entry(_entity).State = EntityState.Modified;
|
|
}
|
|
}
|
|
|
|
public async Task UpdateRelationshipAsync<TRelatedEntity>(
|
|
Expression<Func<TEntity, IEnumerable<TRelatedEntity>>> navigationPropertyPath)
|
|
where TRelatedEntity : class, IBaseModel
|
|
{
|
|
var compiledExpression = navigationPropertyPath.Compile();
|
|
|
|
// Explicitly load the existing collection
|
|
var navigation = _context.Entry(_entity).Collection(navigationPropertyPath);
|
|
|
|
if (!navigation.IsLoaded)
|
|
await navigation.LoadAsync();
|
|
|
|
// Get the updated collection from the new entity
|
|
var updatedCollection = compiledExpression.Invoke(_updatedEntity);
|
|
|
|
if (updatedCollection == null)
|
|
return;
|
|
|
|
if (updatedCollection is IEnumerable<TRelatedEntity> updatedEntities)
|
|
{
|
|
var existingCollection = compiledExpression(_entity);
|
|
|
|
if (existingCollection is ICollection<TRelatedEntity> existingEntities)
|
|
{
|
|
// Get the list of tracked entities from the context
|
|
var trackedEntities = _context.Set<TRelatedEntity>().Local;
|
|
|
|
// Replace entities with tracked instances to avoid duplicate tracking.
|
|
// For entities that aren't already tracked locally, fetch the tracked
|
|
// instance from the database instead of attaching the detached incoming
|
|
// instance. Attaching a detached instance would pull in its nested
|
|
// navigation graph (e.g. Archive.StorageLocation), and two entities that
|
|
// share the same related key would trigger an identity-conflict.
|
|
var updatedTrackedEntities = new List<TRelatedEntity>();
|
|
|
|
foreach (var updatedEntity in updatedEntities)
|
|
{
|
|
var trackedEntity = trackedEntities.FirstOrDefault(t => t.Id == updatedEntity.Id)
|
|
?? await _context.Set<TRelatedEntity>().FirstOrDefaultAsync(t => t.Id == updatedEntity.Id)
|
|
?? updatedEntity;
|
|
|
|
updatedTrackedEntities.Add(trackedEntity);
|
|
}
|
|
|
|
// Update values for existing entities first
|
|
foreach (var existingEntity in existingEntities)
|
|
{
|
|
var updatedEntity = updatedEntities.FirstOrDefault(e => e.Id == existingEntity.Id);
|
|
if (updatedEntity != null)
|
|
{
|
|
_context.Entry(existingEntity).CurrentValues.SetValues(updatedEntity);
|
|
}
|
|
}
|
|
|
|
// Clear the existing collection and add all updated entities
|
|
existingEntities.Clear();
|
|
foreach (var entity in updatedTrackedEntities)
|
|
{
|
|
existingEntities.Add(entity);
|
|
}
|
|
|
|
_context.Entry(_entity).State = EntityState.Modified;
|
|
}
|
|
}
|
|
}
|
|
|
|
} |