Browse Source

Use `IsChangeTrackingEnabled` to replace `IsReadOnly`.

pull/17491/head
maliming 3 years ago
parent
commit
01d2cf3a8b
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 2
      framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/ServiceCollectionRepositoryExtensions.cs
  2. 8
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs
  3. 2
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs
  4. 20
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs
  5. 38
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs
  6. 2
      framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs
  7. 41
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/ReadOnlyRepository_Tests.cs

2
framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/ServiceCollectionRepositoryExtensions.cs

@ -92,7 +92,7 @@ public static class ServiceCollectionRepositoryExtensions
descriptor = ServiceDescriptor.Transient(serviceType, provider => descriptor = ServiceDescriptor.Transient(serviceType, provider =>
{ {
var repository = provider.GetRequiredService(implementationType); var repository = provider.GetRequiredService(implementationType);
ObjectHelper.TrySetProperty(repository.As<IRepository>(), x => x.IsReadOnly, _ => true); ObjectHelper.TrySetProperty(repository.As<IRepository>(), x => x.IsChangeTrackingEnabled, _ => false);
return repository; return repository;
}); });
} }

8
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs

@ -4,6 +4,8 @@ using System.Collections.Generic;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Data; using Volo.Abp.Data;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
@ -34,7 +36,11 @@ public abstract class BasicRepositoryBase<TEntity> :
public ICancellationTokenProvider CancellationTokenProvider => LazyServiceProvider.LazyGetService<ICancellationTokenProvider>(NullCancellationTokenProvider.Instance); public ICancellationTokenProvider CancellationTokenProvider => LazyServiceProvider.LazyGetService<ICancellationTokenProvider>(NullCancellationTokenProvider.Instance);
public bool IsReadOnly { get; protected set; } public ILoggerFactory? LoggerFactory => LazyServiceProvider.LazyGetService<ILoggerFactory>();
public ILogger Logger => LazyServiceProvider.LazyGetService<ILogger>(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance);
public bool IsChangeTrackingEnabled { get; protected set; } = true;
protected BasicRepositoryBase() protected BasicRepositoryBase()
{ {

2
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs

@ -12,7 +12,7 @@ namespace Volo.Abp.Domain.Repositories;
/// </summary> /// </summary>
public interface IRepository public interface IRepository
{ {
bool IsReadOnly { get; } bool IsChangeTrackingEnabled { get; }
} }
public interface IRepository<TEntity> : IReadOnlyRepository<TEntity>, IBasicRepository<TEntity> public interface IRepository<TEntity> : IReadOnlyRepository<TEntity>, IBasicRepository<TEntity>

20
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs

@ -145,6 +145,26 @@ public static class RepositoryExtensions
} }
} }
public static IDisposable DisableTracking(this IRepository repository)
{
return Tracking(repository, false);
}
public static IDisposable EnableTracking(this IRepository repository)
{
return Tracking(repository, true);
}
private static IDisposable Tracking(this IRepository repository, bool enabled)
{
var previous = repository.IsChangeTrackingEnabled;
ObjectHelper.TrySetProperty(ProxyHelper.UnProxy(repository).As<IRepository>(), x => x.IsChangeTrackingEnabled, _ => enabled);
return new DisposeAction<IRepository>(_ =>
{
ObjectHelper.TrySetProperty(ProxyHelper.UnProxy(repository).As<IRepository>(), x => x.IsChangeTrackingEnabled, _ => previous);
}, repository);
}
private static IUnitOfWorkManager GetUnitOfWorkManager<TEntity>( private static IUnitOfWorkManager GetUnitOfWorkManager<TEntity>(
this IBasicRepository<TEntity> repository, this IBasicRepository<TEntity> repository,
[CallerMemberName] string callingMethodName = nameof(GetUnitOfWorkManager) [CallerMemberName] string callingMethodName = nameof(GetUnitOfWorkManager)

38
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs

@ -9,6 +9,7 @@ using System.Linq.Expressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Logging;
using Volo.Abp.Data; using Volo.Abp.Data;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;
@ -107,7 +108,7 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
public async override Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default) public async override Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
{ {
CheckReadOnly(); CheckChangeTracking();
CheckAndSetId(entity); CheckAndSetId(entity);
var dbContext = await GetDbContextAsync(); var dbContext = await GetDbContextAsync();
@ -124,7 +125,7 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
public async override Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default) public async override Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{ {
CheckReadOnly(); CheckChangeTracking();
var entityArray = entities.ToArray(); var entityArray = entities.ToArray();
var dbContext = await GetDbContextAsync(); var dbContext = await GetDbContextAsync();
cancellationToken = GetCancellationToken(cancellationToken); cancellationToken = GetCancellationToken(cancellationToken);
@ -155,7 +156,7 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
public async override Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default) public async override Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
{ {
CheckReadOnly(); CheckChangeTracking();
var dbContext = await GetDbContextAsync(); var dbContext = await GetDbContextAsync();
dbContext.Attach(entity); dbContext.Attach(entity);
@ -172,7 +173,7 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
public async override Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default) public async override Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{ {
CheckReadOnly(); CheckChangeTracking();
cancellationToken = GetCancellationToken(cancellationToken); cancellationToken = GetCancellationToken(cancellationToken);
if (BulkOperationProvider != null) if (BulkOperationProvider != null)
@ -199,7 +200,7 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
public async override Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default) public async override Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
{ {
CheckReadOnly(); CheckChangeTracking();
var dbContext = await GetDbContextAsync(); var dbContext = await GetDbContextAsync();
dbContext.Set<TEntity>().Remove(entity); dbContext.Set<TEntity>().Remove(entity);
@ -212,7 +213,7 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
public async override Task DeleteManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default) public async override Task DeleteManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{ {
CheckReadOnly(); CheckChangeTracking();
cancellationToken = GetCancellationToken(cancellationToken); cancellationToken = GetCancellationToken(cancellationToken);
if (BulkOperationProvider != null) if (BulkOperationProvider != null)
@ -276,12 +277,12 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
[Obsolete("Use GetQueryableAsync method.")] [Obsolete("Use GetQueryableAsync method.")]
protected override IQueryable<TEntity> GetQueryable() protected override IQueryable<TEntity> GetQueryable()
{ {
return DbSet.AsQueryable().AsNoTrackingIf(IsReadOnly); return DbSet.AsQueryable().AsNoTrackingIf(!IsChangeTrackingEnabled);
} }
public async override Task<IQueryable<TEntity>> GetQueryableAsync() public async override Task<IQueryable<TEntity>> GetQueryableAsync()
{ {
return (await GetDbSetAsync()).AsQueryable().AsNoTrackingIf(IsReadOnly); return (await GetDbSetAsync()).AsQueryable().AsNoTrackingIf(!IsChangeTrackingEnabled);
} }
protected async override Task SaveChangesAsync(CancellationToken cancellationToken) protected async override Task SaveChangesAsync(CancellationToken cancellationToken)
@ -305,7 +306,7 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
public async override Task DeleteAsync(Expression<Func<TEntity, bool>> predicate, bool autoSave = false, CancellationToken cancellationToken = default) public async override Task DeleteAsync(Expression<Func<TEntity, bool>> predicate, bool autoSave = false, CancellationToken cancellationToken = default)
{ {
CheckReadOnly(); CheckChangeTracking();
var dbContext = await GetDbContextAsync(); var dbContext = await GetDbContextAsync();
var dbSet = dbContext.Set<TEntity>(); var dbSet = dbContext.Set<TEntity>();
@ -323,7 +324,6 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
public async override Task DeleteDirectAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) public async override Task DeleteDirectAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
{ {
CheckReadOnly();
var dbContext = await GetDbContextAsync(); var dbContext = await GetDbContextAsync();
var dbSet = dbContext.Set<TEntity>(); var dbSet = dbContext.Set<TEntity>();
await dbSet.Where(predicate).ExecuteDeleteAsync(GetCancellationToken(cancellationToken)); await dbSet.Where(predicate).ExecuteDeleteAsync(GetCancellationToken(cancellationToken));
@ -428,18 +428,12 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
); );
} }
protected virtual void CheckReadOnly()
protected virtual void CheckChangeTracking()
{ {
if (IsReadOnly) if (!IsChangeTrackingEnabled)
{ {
throw new AbpRepositoryIsReadOnlyException($"Can not call " + Logger.LogWarning("This repository has disabled change tracking. Your changes may not be saved!");
$"{nameof(InsertAsync)}, " +
$"{nameof(InsertManyAsync)}, " +
$"{nameof(UpdateAsync)}, " +
$"{nameof(UpdateManyAsync)}, " +
$"{nameof(DeleteAsync)}, " +
$"{nameof(DeleteManyAsync)}, " +
$"{nameof(DeleteDirectAsync)} methods on a read-only repository!");
} }
} }
} }
@ -473,14 +467,13 @@ public class EfCoreRepository<TDbContext, TEntity, TKey> : EfCoreRepository<TDbC
{ {
return includeDetails return includeDetails
? await (await WithDetailsAsync()).OrderBy(e => e.Id).FirstOrDefaultAsync(e => e.Id.Equals(id), GetCancellationToken(cancellationToken)) ? await (await WithDetailsAsync()).OrderBy(e => e.Id).FirstOrDefaultAsync(e => e.Id.Equals(id), GetCancellationToken(cancellationToken))
: IsReadOnly : !IsChangeTrackingEnabled
? await (await GetQueryableAsync()).OrderBy(e => e.Id).FirstOrDefaultAsync(e => e.Id.Equals(id), GetCancellationToken(cancellationToken)) ? await (await GetQueryableAsync()).OrderBy(e => e.Id).FirstOrDefaultAsync(e => e.Id.Equals(id), GetCancellationToken(cancellationToken))
: await (await GetDbSetAsync()).FindAsync(new object[] {id}, GetCancellationToken(cancellationToken)); : await (await GetDbSetAsync()).FindAsync(new object[] {id}, GetCancellationToken(cancellationToken));
} }
public virtual async Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default) public virtual async Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default)
{ {
CheckReadOnly();
var entity = await FindAsync(id, cancellationToken: cancellationToken); var entity = await FindAsync(id, cancellationToken: cancellationToken);
if (entity == null) if (entity == null)
{ {
@ -492,7 +485,6 @@ public class EfCoreRepository<TDbContext, TEntity, TKey> : EfCoreRepository<TDbC
public virtual async Task DeleteManyAsync(IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default) public virtual async Task DeleteManyAsync(IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
{ {
CheckReadOnly();
cancellationToken = GetCancellationToken(cancellationToken); cancellationToken = GetCancellationToken(cancellationToken);
var entities = await (await GetDbSetAsync()).Where(x => ids.Contains(x.Id)).ToListAsync(cancellationToken); var entities = await (await GetDbSetAsync()).Where(x => ids.Contains(x.Id)).ToListAsync(cancellationToken);

2
framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs

@ -407,7 +407,7 @@ public class RepositoryRegistration_Tests
public class MyTestAggregateRootWithDefaultPkEmptyRepository : IMyTestAggregateRootWithDefaultPkEmptyRepository public class MyTestAggregateRootWithDefaultPkEmptyRepository : IMyTestAggregateRootWithDefaultPkEmptyRepository
{ {
public bool IsReadOnly { get; set; } public bool IsChangeTrackingEnabled { get; set; }
} }
public class TestDbContextRegistrationOptions : AbpCommonDbContextRegistrationOptions public class TestDbContextRegistrationOptions : AbpCommonDbContextRegistrationOptions

41
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/ReadOnlyRepository_Tests.cs

@ -2,13 +2,9 @@ using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Shouldly; using Shouldly;
using Volo.Abp.Data;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.TestApp.Domain; using Volo.Abp.TestApp.Domain;
using Volo.Abp.TestApp.EntityFrameworkCore;
using Volo.Abp.TestApp.Testing; using Volo.Abp.TestApp.Testing;
using Volo.Abp.Uow; using Volo.Abp.Uow;
using Xunit; using Xunit;
@ -55,23 +51,42 @@ public class ReadOnlyRepository_Tests : TestAppTestBase<AbpEntityFrameworkCoreTe
} }
[Fact] [Fact]
public async Task ReadOnlyRepository_Should_Throw_AbpRepositoryIsReadOnlyException_When_Write_Method_Call() public async Task Repository_Should_Support_Tracking_Or_NoTracking()
{ {
var repository = GetRequiredService<IRepository<Person, Guid>>();
await WithUnitOfWorkAsync(async () => await WithUnitOfWorkAsync(async () =>
{ {
var repository = GetRequiredService<IRepository<Person, Guid>>(); await repository.InsertAsync(new Person(Guid.NewGuid(), "people1", 18));
await repository.ToEfCoreRepository().InsertAsync(new Person(Guid.NewGuid(), "test", 18)); await repository.InsertAsync(new Person(Guid.NewGuid(), "people2", 19));
var person = await repository.ToEfCoreRepository().FirstOrDefaultAsync(); await repository.InsertAsync(new Person(Guid.NewGuid(), "people3", 20));
person.ShouldNotBeNull(); await repository.InsertAsync(new Person(Guid.NewGuid(), "people4", 21));
}); });
await WithUnitOfWorkAsync(async () => await WithUnitOfWorkAsync(async () =>
{ {
await Assert.ThrowsAsync<AbpRepositoryIsReadOnlyException>(async () => var db = await repository.GetDbContextAsync();
db.ChangeTracker.Entries().Count().ShouldBe(0);
using (repository.DisableTracking())
{ {
var readonlyRepository = GetRequiredService<IReadOnlyRepository<Person, Guid>>(); var p1 = await repository.FindAsync(x => x.Name == "people1");
await readonlyRepository.ToEfCoreRepository().As<EfCoreRepository<TestAppDbContext, Person, Guid>>().InsertAsync(new Person(Guid.NewGuid(), "test readonly", 18)); p1.ShouldNotBeNull();
}); db.ChangeTracker.Entries().Count().ShouldBe(0);
}
var p2 = await repository.FindAsync(x => x.Name == "people2");
p2.ShouldNotBeNull();
db.ChangeTracker.Entries().Count().ShouldBe(1);
repository.DisableTracking();
var p3 = await repository.FindAsync(x => x.Name == "people3");
p3.ShouldNotBeNull();
db.ChangeTracker.Entries().Count().ShouldBe(1);
repository.EnableTracking();
var p4 = await repository.FindAsync(x => x.Name == "people4");
p4.ShouldNotBeNull();
db.ChangeTracker.Entries().Count().ShouldBe(2);
}); });
} }

Loading…
Cancel
Save