Browse Source

Support `RecursiveDetectChanges`.

pull/19559/head
maliming 2 years ago
parent
commit
2604c817d5
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 4
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  2. 36
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs
  3. 3
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs
  4. 2
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs
  5. 14
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs
  6. 12
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs
  7. 18
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs
  8. 35
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/DomainEvents_Tests.cs

4
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs

@ -274,14 +274,14 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
protected virtual void ChangeTracker_Tracked(object? sender, EntityTrackedEventArgs e)
{
AbpEfCoreNavigationHelper.ChangeTracker_Tracked(ChangeTracker, sender, e);
AbpEfCoreNavigationHelper.ChangeTracker_Tracked(sender, e);
FillExtraPropertiesForTrackedEntities(e);
PublishEventsForTrackedEntity(e.Entry);
}
protected virtual void ChangeTracker_StateChanged(object? sender, EntityStateChangedEventArgs e)
{
AbpEfCoreNavigationHelper.ChangeTracker_StateChanged(ChangeTracker, sender, e);
AbpEfCoreNavigationHelper.ChangeTracker_StateChanged(sender, e);
PublishEventsForTrackedEntity(e.Entry);
}

36
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs

@ -1,12 +1,9 @@
using System;
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 Microsoft.EntityFrameworkCore.Metadata.Internal;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
@ -20,16 +17,16 @@ public class AbpEfCoreNavigationHelper : ITransientDependency
{
protected Dictionary<string, AbpEntityEntry> EntityEntries { get; } = new();
public virtual void ChangeTracker_Tracked(ChangeTracker changeTracker, object? sender, EntityTrackedEventArgs e)
public virtual void ChangeTracker_Tracked(object? sender, EntityTrackedEventArgs e)
{
EntityEntryTrackedOrStateChanged(e.Entry);
DetectChanges(changeTracker, e.Entry);
DetectChanges(e.Entry);
}
public virtual void ChangeTracker_StateChanged(ChangeTracker changeTracker, object? sender, EntityStateChangedEventArgs e)
public virtual void ChangeTracker_StateChanged(object? sender, EntityStateChangedEventArgs e)
{
EntityEntryTrackedOrStateChanged(e.Entry);
DetectChanges(changeTracker, e.Entry);
DetectChanges(e.Entry);
}
protected virtual void EntityEntryTrackedOrStateChanged(EntityEntry entityEntry)
@ -53,7 +50,7 @@ public class AbpEfCoreNavigationHelper : ITransientDependency
EntityEntries.Add(entryId, new AbpEntityEntry(entryId, entityEntry));
}
protected virtual void DetectChanges(ChangeTracker changeTracker, EntityEntry entityEntry)
protected virtual void DetectChanges(EntityEntry entityEntry)
{
if (entityEntry.State != EntityState.Added &&
entityEntry.State != EntityState.Deleted &&
@ -62,8 +59,13 @@ public class AbpEfCoreNavigationHelper : ITransientDependency
return;
}
#pragma warning disable EF1001
var stateManager = changeTracker.Context.GetDependencies().StateManager;
RecursiveDetectChanges(entityEntry);
}
protected virtual void RecursiveDetectChanges(EntityEntry entityEntry)
{
#pragma warning disable EF1001
var stateManager = entityEntry.Context.GetDependencies().StateManager;
var internalEntityEntityEntry = stateManager.Entries.FirstOrDefault(x => x.Entity == entityEntry.Entity);
if (internalEntityEntityEntry == null)
{
@ -85,7 +87,12 @@ public class AbpEfCoreNavigationHelper : ITransientDependency
continue;
}
abpEntityEntry.IsModified = true;
if (!abpEntityEntry.IsModified)
{
abpEntityEntry.IsModified = true;
RecursiveDetectChanges(abpEntityEntry.EntityEntry);
}
var navigationEntry = abpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is INavigation navigationMetadata && navigationMetadata.ForeignKey == foreignKey) ??
abpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is ISkipNavigation skipNavigationMetadata && skipNavigationMetadata.ForeignKey == foreignKey);
if (navigationEntry != null)
@ -119,7 +126,12 @@ public class AbpEfCoreNavigationHelper : ITransientDependency
continue;
}
abpEntityEntry.IsModified = true;
if (!abpEntityEntry.IsModified)
{
abpEntityEntry.IsModified = true;
RecursiveDetectChanges(abpEntityEntry.EntityEntry);
}
var navigationEntry = abpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is INavigation navigationMetadata && navigationMetadata.ForeignKey == inverseForeignKey) ??
abpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is ISkipNavigation skipNavigationMetadata && skipNavigationMetadata.ForeignKey == inverseForeignKey);
if (navigationEntry != null)

3
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
namespace Volo.Abp.EntityFrameworkCore.ChangeTrackers;
@ -17,7 +18,7 @@ public class AbpEntityEntry
{
get
{
return _isModified || NavigationEntries.Any(n => n.IsModified);
return _isModified || EntityEntry.State == EntityState.Modified || NavigationEntries.Any(n => n.IsModified);
}
set
{

2
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs

@ -51,7 +51,7 @@ public class AbpEntityFrameworkCoreTestModule : AbpModule
options.Entity<AppEntityWithNavigations>(opt =>
{
opt.DefaultWithDetailsFunc = q => q.Include(p => p.OneToOne).Include(p => p.OneToMany).Include(p => p.ManyToMany);
opt.DefaultWithDetailsFunc = q => q.Include(p => p.OneToOne).ThenInclude(x => x.OneToOne).Include(p => p.OneToMany).ThenInclude(x => x.OneToMany).Include(p => p.ManyToMany);
});
});

14
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs

@ -70,10 +70,22 @@ public class TestMigrationsDbContext : AbpDbContext<TestMigrationsDbContext>
modelBuilder.Entity<AppEntityWithNavigations>(b =>
{
b.ConfigureByConvention();
b.OwnsOne(v => v.AppEntityWithValueObjectAddress);
b.OwnsOne(x => x.AppEntityWithValueObjectAddress);
b.HasOne(x => x.OneToOne).WithOne().HasForeignKey<AppEntityWithNavigationChildOneToOne>(x => x.Id);
b.HasMany(x => x.OneToMany).WithOne().HasForeignKey(x => x.AppEntityWithNavigationId);
b.HasMany(x => x.ManyToMany).WithMany(x => x.ManyToMany).UsingEntity<AppEntityWithNavigationsAndAppEntityWithNavigationChildManyToMany>();
});
modelBuilder.Entity<AppEntityWithNavigationChildOneToOne>(b =>
{
b.ConfigureByConvention();
b.HasOne(x => x.OneToOne).WithOne().HasForeignKey<AppEntityWithNavigationChildOneToOneAndOneToOne>(x => x.Id);
});
modelBuilder.Entity<AppEntityWithNavigationChildOneToMany>(b =>
{
b.ConfigureByConvention();
b.HasMany(x => x.OneToMany).WithOne().HasForeignKey(x => x.AppEntityWithNavigationChildOneToManyId);
});
}
}

12
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs

@ -103,6 +103,18 @@ public class TestAppDbContext : AbpDbContext<TestAppDbContext>, IThirdDbContext,
b.HasMany(x => x.ManyToMany).WithMany(x => x.ManyToMany).UsingEntity<AppEntityWithNavigationsAndAppEntityWithNavigationChildManyToMany>();
});
modelBuilder.Entity<AppEntityWithNavigationChildOneToOne>(b =>
{
b.ConfigureByConvention();
b.HasOne(x => x.OneToOne).WithOne().HasForeignKey<AppEntityWithNavigationChildOneToOneAndOneToOne>(x => x.Id);
});
modelBuilder.Entity<AppEntityWithNavigationChildOneToMany>(b =>
{
b.ConfigureByConvention();
b.HasMany(x => x.OneToMany).WithOne().HasForeignKey(x => x.AppEntityWithNavigationChildOneToManyId);
});
modelBuilder.TryConfigureObjectExtensions<TestAppDbContext>();
}
}

18
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs

@ -36,7 +36,6 @@ public class AppEntityWithValueObjectAddress : ValueObject
{
public AppEntityWithValueObjectAddress(string country)
{
Country = country;
}
@ -48,8 +47,14 @@ public class AppEntityWithValueObjectAddress : ValueObject
}
}
public class AppEntityWithNavigationChildOneToOne : Entity<Guid>
{
public string ChildName { get; set; }
public virtual AppEntityWithNavigationChildOneToOneAndOneToOne OneToOne { get; set; }
}
public class AppEntityWithNavigationChildOneToOneAndOneToOne : Entity<Guid>
{
public string ChildName { get; set; }
}
@ -59,6 +64,15 @@ public class AppEntityWithNavigationChildOneToMany : Entity<Guid>
public Guid AppEntityWithNavigationId { get; set; }
public string ChildName { get; set; }
public virtual List<AppEntityWithNavigationChildOneToManyAndOneToMany> OneToMany { get; set; }
}
public class AppEntityWithNavigationChildOneToManyAndOneToMany : Entity<Guid>
{
public Guid AppEntityWithNavigationChildOneToManyId { get; set; }
public string ChildName { get; set; }
}
public class AppEntityWithNavigationChildManyToMany : AggregateRoot<Guid>

35
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/DomainEvents_Tests.cs

@ -252,7 +252,11 @@ public abstract class DomainEvents_Tests<TStartupModule> : TestAppTestBase<TStar
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.OneToOne = new AppEntityWithNavigationChildOneToOne
{
ChildName = "ChildName"
ChildName = "ChildName",
OneToOne = new AppEntityWithNavigationChildOneToOneAndOneToOne
{
ChildName = "OneToOne-ChildName"
}
};
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
@ -269,6 +273,16 @@ public abstract class DomainEvents_Tests<TStartupModule> : TestAppTestBase<TStar
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.OneToOne.OneToOne.ChildName = "OneToOne-ChildName2";
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
LocalEventBus.Subscribe<EntityUpdatedEventData<AppEntityWithNavigationChildOneToOne>>(data =>
{
throw new Exception("Should not trigger this event");
@ -294,7 +308,14 @@ public abstract class DomainEvents_Tests<TStartupModule> : TestAppTestBase<TStar
new AppEntityWithNavigationChildOneToMany
{
AppEntityWithNavigationId = entity.Id,
ChildName = "ChildName1"
ChildName = "ChildName1",
OneToMany = new List<AppEntityWithNavigationChildOneToManyAndOneToMany>()
{
new AppEntityWithNavigationChildOneToManyAndOneToMany()
{
ChildName = "OneToMany-ChildName1"
}
}
}
};
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
@ -312,6 +333,16 @@ public abstract class DomainEvents_Tests<TStartupModule> : TestAppTestBase<TStar
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.OneToMany[0].OneToMany[0].ChildName = "OneToMany-ChildName2";
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
LocalEventBus.Subscribe<EntityUpdatedEventData<AppEntityWithNavigationChildOneToMany>>(data =>
{
throw new Exception("Should not trigger this event");

Loading…
Cancel
Save