Use `IsChangeTrackingEnabled` to replace `IsReadOnly`.

pull/17491/head
maliming 2 years ago
parent c17c4c261d
commit 01d2cf3a8b
No known key found for this signature in database
GPG Key ID: A646B9CB645ECEA4

@ -92,7 +92,7 @@ public static class ServiceCollectionRepositoryExtensions
descriptor = ServiceDescriptor.Transient(serviceType, provider =>
{
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;
});
}

@ -4,6 +4,8 @@ using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
@ -34,7 +36,11 @@ public abstract class BasicRepositoryBase<TEntity> :
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()
{

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

@ -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>(
this IBasicRepository<TEntity> repository,
[CallerMemberName] string callingMethodName = nameof(GetUnitOfWorkManager)

@ -9,6 +9,7 @@ using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Logging;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
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)
{
CheckReadOnly();
CheckChangeTracking();
CheckAndSetId(entity);
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)
{
CheckReadOnly();
CheckChangeTracking();
var entityArray = entities.ToArray();
var dbContext = await GetDbContextAsync();
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)
{
CheckReadOnly();
CheckChangeTracking();
var dbContext = await GetDbContextAsync();
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)
{
CheckReadOnly();
CheckChangeTracking();
cancellationToken = GetCancellationToken(cancellationToken);
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)
{
CheckReadOnly();
CheckChangeTracking();
var dbContext = await GetDbContextAsync();
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)
{
CheckReadOnly();
CheckChangeTracking();
cancellationToken = GetCancellationToken(cancellationToken);
if (BulkOperationProvider != null)
@ -276,12 +277,12 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
[Obsolete("Use GetQueryableAsync method.")]
protected override IQueryable<TEntity> GetQueryable()
{
return DbSet.AsQueryable().AsNoTrackingIf(IsReadOnly);
return DbSet.AsQueryable().AsNoTrackingIf(!IsChangeTrackingEnabled);
}
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)
@ -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)
{
CheckReadOnly();
CheckChangeTracking();
var dbContext = await GetDbContextAsync();
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)
{
CheckReadOnly();
var dbContext = await GetDbContextAsync();
var dbSet = dbContext.Set<TEntity>();
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 " +
$"{nameof(InsertAsync)}, " +
$"{nameof(InsertManyAsync)}, " +
$"{nameof(UpdateAsync)}, " +
$"{nameof(UpdateManyAsync)}, " +
$"{nameof(DeleteAsync)}, " +
$"{nameof(DeleteManyAsync)}, " +
$"{nameof(DeleteDirectAsync)} methods on a read-only repository!");
Logger.LogWarning("This repository has disabled change tracking. Your changes may not be saved!");
}
}
}
@ -473,14 +467,13 @@ public class EfCoreRepository<TDbContext, TEntity, TKey> : EfCoreRepository<TDbC
{
return includeDetails
? 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 GetDbSetAsync()).FindAsync(new object[] {id}, GetCancellationToken(cancellationToken));
}
public virtual async Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default)
{
CheckReadOnly();
var entity = await FindAsync(id, cancellationToken: cancellationToken);
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)
{
CheckReadOnly();
cancellationToken = GetCancellationToken(cancellationToken);
var entities = await (await GetDbSetAsync()).Where(x => ids.Contains(x.Id)).ToListAsync(cancellationToken);

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

@ -2,13 +2,9 @@ using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.Data;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.TestApp.EntityFrameworkCore;
using Volo.Abp.TestApp.Testing;
using Volo.Abp.Uow;
using Xunit;
@ -55,23 +51,42 @@ public class ReadOnlyRepository_Tests : TestAppTestBase<AbpEntityFrameworkCoreTe
}
[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 () =>
{
var repository = GetRequiredService<IRepository<Person, Guid>>();
await repository.ToEfCoreRepository().InsertAsync(new Person(Guid.NewGuid(), "test", 18));
var person = await repository.ToEfCoreRepository().FirstOrDefaultAsync();
person.ShouldNotBeNull();
await repository.InsertAsync(new Person(Guid.NewGuid(), "people1", 18));
await repository.InsertAsync(new Person(Guid.NewGuid(), "people2", 19));
await repository.InsertAsync(new Person(Guid.NewGuid(), "people3", 20));
await repository.InsertAsync(new Person(Guid.NewGuid(), "people4", 21));
});
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>>();
await readonlyRepository.ToEfCoreRepository().As<EfCoreRepository<TestAppDbContext, Person, Guid>>().InsertAsync(new Person(Guid.NewGuid(), "test readonly", 18));
});
var p1 = await repository.FindAsync(x => x.Name == "people1");
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