hi
Please use IRemoteStreamContent to replace IFormFile in your DTO and regenerate the Angular proxy.
https://abp.io/docs/latest/framework/architecture/domain-driven-design/application-services#working-with-streams
Thanks.
hi
The new version
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
namespace Volo.Abp.EntityFrameworkCore.ChangeTrackers;
public class MyAbpEntityEntry
{
public string Id { get; set; }
public EntityEntry EntityEntry { get; set; }
public List<MyAbpNavigationEntry> NavigationEntries { get; set; }
private bool _isModified;
public bool IsModified
{
get
{
return _isModified || EntityEntry.State == EntityState.Modified || NavigationEntries.Any(n => n.IsModified);
}
set => _isModified = value;
}
public MyAbpEntityEntry(string id, EntityEntry entityEntry)
{
Id = id;
EntityEntry = entityEntry;
NavigationEntries = EntityEntry.Navigations.Select(x => new MyAbpNavigationEntry(x, x.Metadata.Name)).ToList();
}
public void UpdateNavigation(EntityEntry entityEntry, MyAbpNavigationEntry navigationEntry)
{
if (IsModified ||
EntityEntry.State == EntityState.Modified ||
navigationEntry.IsModified)
{
return;
}
var currentValue = navigationEntry.NavigationEntry.CurrentValue;
if (currentValue == null)
{
return;
}
if (navigationEntry.NavigationEntry is CollectionEntry)
{
navigationEntry.OriginalValue!.As<List<object>>().Add(entityEntry.Entity);
}
else
{
navigationEntry.OriginalValue = currentValue;
}
}
}
public class MyAbpNavigationEntry
{
public NavigationEntry NavigationEntry { get; set; }
public string Name { get; set; }
public bool IsModified { get; set; }
public object? OriginalValue { get; set; }
public object? CurrentValue => NavigationEntry.CurrentValue;
public MyAbpNavigationEntry(NavigationEntry navigationEntry, string name)
{
NavigationEntry = navigationEntry;
Name = name;
if (navigationEntry.CurrentValue != null )
{
OriginalValue = navigationEntry is CollectionEntry ? new List<object>() : navigationEntry.CurrentValue;
}
}
}
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(AbpEfCoreNavigationHelper))]
public class MyAbpEfCoreNavigationHelper : AbpEfCoreNavigationHelper
{
protected Dictionary<string, MyAbpEntityEntry> MyEntityEntries { get; } = new();
public override void ChangeTracker_Tracked(object? sender, EntityTrackedEventArgs e)
{
EntityEntryTrackedOrStateChanged(e.Entry);
DetectChanges(e.Entry);
}
public override void ChangeTracker_StateChanged(object? sender, EntityStateChangedEventArgs e)
{
EntityEntryTrackedOrStateChanged(e.Entry);
DetectChanges(e.Entry);
}
protected override void EntityEntryTrackedOrStateChanged(EntityEntry entityEntry)
{
if (entityEntry.State != EntityState.Unchanged)
{
return;
}
var entryId = GetEntityEntryIdentity(entityEntry);
if (entryId == null)
{
return;
}
if (MyEntityEntries.ContainsKey(entryId))
{
return;
}
MyEntityEntries.Add(entryId, new MyAbpEntityEntry(entryId, entityEntry));
}
protected override void DetectChanges(EntityEntry entityEntry, bool checkEntityEntryState = true)
{
#pragma warning disable EF1001
var stateManager = entityEntry.Context.GetDependencies().StateManager;
var internalEntityEntityEntry = stateManager.TryGetEntry(entityEntry.Entity, throwOnNonUniqueness: false);
if (internalEntityEntityEntry == null)
{
return;
}
var foreignKeys = entityEntry.Metadata.GetForeignKeys().ToList();
foreach (var foreignKey in foreignKeys)
{
var principal = stateManager.FindPrincipal(internalEntityEntityEntry, foreignKey);
if (principal == null)
{
continue;
}
var entryId = GetEntityEntryIdentity(principal.ToEntityEntry());
if (entryId == null || !MyEntityEntries.TryGetValue(entryId, out var myAbpEntityEntry))
{
continue;
}
var navigationEntry = myAbpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is INavigation navigationMetadata && navigationMetadata.ForeignKey == foreignKey) ??
myAbpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is ISkipNavigation skipNavigationMetadata && skipNavigationMetadata.ForeignKey == foreignKey);
if (navigationEntry != null && checkEntityEntryState && entityEntry.State == EntityState.Unchanged)
{
myAbpEntityEntry.UpdateNavigation(entityEntry, navigationEntry);
}
if (!myAbpEntityEntry.IsModified && (!checkEntityEntryState || IsEntityEntryChanged(entityEntry)))
{
myAbpEntityEntry.IsModified = true;
DetectChanges(myAbpEntityEntry.EntityEntry, false);
}
if (navigationEntry != null && IsEntityEntryChanged(entityEntry))
{
navigationEntry.IsModified = true;
}
}
var skipNavigations = entityEntry.Metadata.GetSkipNavigations().ToList();
foreach (var skipNavigation in skipNavigations)
{
var joinEntityType = skipNavigation.JoinEntityType;
var foreignKey = skipNavigation.ForeignKey;
var inverseForeignKey = skipNavigation.Inverse.ForeignKey;
foreach (var joinEntry in stateManager.Entries)
{
if (joinEntry.EntityType != joinEntityType || stateManager.FindPrincipal(joinEntry, foreignKey) != internalEntityEntityEntry)
{
continue;
}
var principal = stateManager.FindPrincipal(joinEntry, inverseForeignKey);
if (principal == null)
{
continue;
}
var entryId = GetEntityEntryIdentity(principal.ToEntityEntry());
if (entryId == null || !MyEntityEntries.TryGetValue(entryId, out var myAbpEntityEntry))
{
continue;
}
var navigationEntry = myAbpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is INavigation navigationMetadata && navigationMetadata.ForeignKey == inverseForeignKey) ??
myAbpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is ISkipNavigation skipNavigationMetadata && skipNavigationMetadata.ForeignKey == inverseForeignKey);
if (navigationEntry != null && checkEntityEntryState && entityEntry.State == EntityState.Unchanged)
{
myAbpEntityEntry.UpdateNavigation(entityEntry, navigationEntry);
}
if (!myAbpEntityEntry.IsModified && (!checkEntityEntryState || IsEntityEntryChanged(entityEntry)))
{
myAbpEntityEntry.IsModified = true;
DetectChanges(myAbpEntityEntry.EntityEntry, false);
}
if (navigationEntry != null && (!checkEntityEntryState || IsEntityEntryChanged(entityEntry)))
{
navigationEntry.IsModified = true;
}
}
}
#pragma warning restore EF1001
}
protected override bool IsEntityEntryChanged(EntityEntry entityEntry)
{
return entityEntry.State == EntityState.Added ||
entityEntry.State == EntityState.Deleted ||
entityEntry.State == EntityState.Modified;
}
public override List<EntityEntry> GetChangedEntityEntries()
{
return MyEntityEntries
.Where(x => x.Value.IsModified)
.Select(x => x.Value.EntityEntry)
.ToList();
}
public override bool IsEntityEntryModified(EntityEntry entityEntry)
{
if (entityEntry.State == EntityState.Modified)
{
return true;
}
var entryId = GetEntityEntryIdentity(entityEntry);
if (entryId == null)
{
return false;
}
return MyEntityEntries.TryGetValue(entryId, out var myAbpEntityEntry) && myAbpEntityEntry.IsModified;
}
public override bool IsNavigationEntryModified(EntityEntry entityEntry, int? navigationEntryIndex = null)
{
var entryId = GetEntityEntryIdentity(entityEntry);
if (entryId == null)
{
return false;
}
if (!MyEntityEntries.TryGetValue(entryId, out var myAbpEntityEntry))
{
return false;
}
if (navigationEntryIndex == null)
{
return myAbpEntityEntry.NavigationEntries.Any(x => x.IsModified);
}
var navigationEntryProperty = myAbpEntityEntry.NavigationEntries.ElementAtOrDefault(navigationEntryIndex.Value);
return navigationEntryProperty != null && navigationEntryProperty.IsModified;
}
public override AbpNavigationEntry? GetNavigationEntry(EntityEntry entityEntry, int navigationEntryIndex)
{
var entryId = GetEntityEntryIdentity(entityEntry);
if (entryId == null)
{
return null;
}
if (!MyEntityEntries.TryGetValue(entryId, out var myAbpEntityEntry))
{
return null;
}
var element = myAbpEntityEntry.NavigationEntries.ElementAtOrDefault(navigationEntryIndex);
if (element == null)
{
return null;
}
return new AbpNavigationEntry(element.NavigationEntry, element.Name)
{
IsModified = element.IsModified,
OriginalValue = element.OriginalValue
};
}
protected override string? GetEntityEntryIdentity(EntityEntry entityEntry)
{
if (entityEntry.Entity is IEntity entryEntity && entryEntity.GetKeys().Length == 1)
{
return $"{entityEntry.Metadata.ClrType.FullName}:{entryEntity.GetKeys().FirstOrDefault()}";
}
return null;
}
public override void RemoveChangedEntityEntries()
{
MyEntityEntries.RemoveAll(x => x.Value.IsModified);
}
public override void Clear()
{
MyEntityEntries.Clear();
}
}
Hi
I will create a test project and check the results if you don’t have time to prepare the demo.
Thanks
Hi
I will check if we can refactor this function.
Thanks
Hi
I will check this case with your project.
Thanks
Hi
If you can share a minimal project I can check it, maybe we can improve more performance.
Thanks
hi
Can you try to override the AbpEfCoreNavigationHelper in your project?
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
namespace Volo.Abp.EntityFrameworkCore.ChangeTrackers;
public class MyAbpEntityEntry
{
public string Id { get; set; }
public EntityEntry EntityEntry { get; set; }
public List<AbpNavigationEntry> NavigationEntries { get; set; }
private bool _isModified;
public bool IsModified
{
get
{
return _isModified || EntityEntry.State == EntityState.Modified || NavigationEntries.Any(n => n.IsModified);
}
set => _isModified = value;
}
public MyAbpEntityEntry(string id, EntityEntry entityEntry)
{
Id = id;
EntityEntry = entityEntry;
NavigationEntries = EntityEntry.Navigations.Select(x => new AbpNavigationEntry(x, x.Metadata.Name)).ToList();
}
public void UpdateNavigationEntries()
{
foreach (var navigationEntry in NavigationEntries)
{
if (IsModified ||
EntityEntry.State == EntityState.Modified ||
navigationEntry.IsModified ||
navigationEntry.NavigationEntry.IsModified)
{
continue;
}
var currentValue = AbpNavigationEntry.GetOriginalValue(navigationEntry.NavigationEntry.CurrentValue);
if (currentValue == null)
{
continue;
}
switch (navigationEntry.OriginalValue)
{
case null:
navigationEntry.OriginalValue = currentValue;
break;
case IEnumerable originalValueCollection when currentValue is IEnumerable currentValueCollection:
{
var existingList = originalValueCollection.Cast<object?>().ToList();
var newList = currentValueCollection.Cast<object?>().ToList();
if (newList.Count > existingList.Count)
{
navigationEntry.OriginalValue = currentValue;
}
break;
}
default:
navigationEntry.OriginalValue = currentValue;
break;
}
}
}
}
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(AbpEfCoreNavigationHelper))]
public class MyAbpEfCoreNavigationHelper : AbpEfCoreNavigationHelper
{
protected Dictionary<string, MyAbpEntityEntry> MyEntityEntries { get; } = new();
public override void ChangeTracker_Tracked(object? sender, EntityTrackedEventArgs e)
{
EntityEntryTrackedOrStateChanged(e.Entry);
DetectChanges(e.Entry);
}
public override void ChangeTracker_StateChanged(object? sender, EntityStateChangedEventArgs e)
{
EntityEntryTrackedOrStateChanged(e.Entry);
DetectChanges(e.Entry);
}
protected override void EntityEntryTrackedOrStateChanged(EntityEntry entityEntry)
{
if (entityEntry.State != EntityState.Unchanged)
{
return;
}
var entryId = GetEntityEntryIdentity(entityEntry);
if (entryId == null)
{
return;
}
if (MyEntityEntries.ContainsKey(entryId))
{
return;
}
MyEntityEntries.Add(entryId, new MyAbpEntityEntry(entryId, entityEntry));
}
protected override void DetectChanges(EntityEntry entityEntry, bool checkEntityEntryState = true)
{
#pragma warning disable EF1001
var stateManager = entityEntry.Context.GetDependencies().StateManager;
var internalEntityEntityEntry = stateManager.TryGetEntry(entityEntry.Entity, throwOnNonUniqueness: false);
if (internalEntityEntityEntry == null)
{
return;
}
var foreignKeys = entityEntry.Metadata.GetForeignKeys().ToList();
foreach (var foreignKey in foreignKeys)
{
var principal = stateManager.FindPrincipal(internalEntityEntityEntry, foreignKey);
if (principal == null)
{
continue;
}
var entryId = GetEntityEntryIdentity(principal.ToEntityEntry());
if (entryId == null || !MyEntityEntries.TryGetValue(entryId, out var myAbpEntityEntry))
{
continue;
}
myAbpEntityEntry.UpdateNavigationEntries();
if (!myAbpEntityEntry.IsModified && (!checkEntityEntryState || IsEntityEntryChanged(entityEntry)))
{
myAbpEntityEntry.IsModified = true;
DetectChanges(myAbpEntityEntry.EntityEntry, false);
}
var navigationEntry = myAbpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is INavigation navigationMetadata && navigationMetadata.ForeignKey == foreignKey) ??
myAbpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is ISkipNavigation skipNavigationMetadata && skipNavigationMetadata.ForeignKey == foreignKey);
if (navigationEntry != null && IsEntityEntryChanged(entityEntry))
{
navigationEntry.IsModified = true;
}
}
var skipNavigations = entityEntry.Metadata.GetSkipNavigations().ToList();
foreach (var skipNavigation in skipNavigations)
{
var joinEntityType = skipNavigation.JoinEntityType;
var foreignKey = skipNavigation.ForeignKey;
var inverseForeignKey = skipNavigation.Inverse.ForeignKey;
foreach (var joinEntry in stateManager.Entries)
{
if (joinEntry.EntityType != joinEntityType || stateManager.FindPrincipal(joinEntry, foreignKey) != internalEntityEntityEntry)
{
continue;
}
var principal = stateManager.FindPrincipal(joinEntry, inverseForeignKey);
if (principal == null)
{
continue;
}
var entryId = GetEntityEntryIdentity(principal.ToEntityEntry());
if (entryId == null || !MyEntityEntries.TryGetValue(entryId, out var myAbpEntityEntry))
{
continue;
}
myAbpEntityEntry.UpdateNavigationEntries();
if (!myAbpEntityEntry.IsModified && (!checkEntityEntryState || IsEntityEntryChanged(entityEntry)))
{
myAbpEntityEntry.IsModified = true;
DetectChanges(myAbpEntityEntry.EntityEntry, false);
}
var navigationEntry = myAbpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is INavigation navigationMetadata && navigationMetadata.ForeignKey == inverseForeignKey) ??
myAbpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is ISkipNavigation skipNavigationMetadata && skipNavigationMetadata.ForeignKey == inverseForeignKey);
if (navigationEntry != null && (!checkEntityEntryState || IsEntityEntryChanged(entityEntry)))
{
navigationEntry.IsModified = true;
}
}
}
#pragma warning restore EF1001
}
protected override bool IsEntityEntryChanged(EntityEntry entityEntry)
{
return entityEntry.State == EntityState.Added ||
entityEntry.State == EntityState.Deleted ||
entityEntry.State == EntityState.Modified;
}
public override List<EntityEntry> GetChangedEntityEntries()
{
return MyEntityEntries
.Where(x => x.Value.IsModified)
.Select(x => x.Value.EntityEntry)
.ToList();
}
public override bool IsEntityEntryModified(EntityEntry entityEntry)
{
if (entityEntry.State == EntityState.Modified)
{
return true;
}
var entryId = GetEntityEntryIdentity(entityEntry);
if (entryId == null)
{
return false;
}
return MyEntityEntries.TryGetValue(entryId, out var myAbpEntityEntry) && myAbpEntityEntry.IsModified;
}
public override bool IsNavigationEntryModified(EntityEntry entityEntry, int? navigationEntryIndex = null)
{
var entryId = GetEntityEntryIdentity(entityEntry);
if (entryId == null)
{
return false;
}
if (!MyEntityEntries.TryGetValue(entryId, out var myAbpEntityEntry))
{
return false;
}
if (navigationEntryIndex == null)
{
return myAbpEntityEntry.NavigationEntries.Any(x => x.IsModified);
}
var navigationEntryProperty = myAbpEntityEntry.NavigationEntries.ElementAtOrDefault(navigationEntryIndex.Value);
return navigationEntryProperty != null && navigationEntryProperty.IsModified;
}
public override AbpNavigationEntry? GetNavigationEntry(EntityEntry entityEntry, int navigationEntryIndex)
{
var entryId = GetEntityEntryIdentity(entityEntry);
if (entryId == null)
{
return null;
}
if (!MyEntityEntries.TryGetValue(entryId, out var myAbpEntityEntry))
{
return null;
}
return myAbpEntityEntry.NavigationEntries.ElementAtOrDefault(navigationEntryIndex);
}
protected override string? GetEntityEntryIdentity(EntityEntry entityEntry)
{
if (entityEntry.Entity is IEntity entryEntity && entryEntity.GetKeys().Length == 1)
{
return $"{entityEntry.Metadata.ClrType.FullName}:{entryEntity.GetKeys().FirstOrDefault()}";
}
return null;
}
public override void RemoveChangedEntityEntries()
{
MyEntityEntries.RemoveAll(x => x.Value.IsModified);
}
public override void Clear()
{
MyEntityEntries.Clear();
}
}
hi
If you can use it in an ASP.NET Core application, you can also use it in an ABP application.
You just need to check its documentation. If you run into any issues, feel free to provide feedback
Thanks.