Skip to content

Commit

Permalink
Feature/entity framework core
Browse files Browse the repository at this point in the history
  • Loading branch information
zhenlei520 authored and blazor-component committed Oct 8, 2021
1 parent ebb9df8 commit af78655
Show file tree
Hide file tree
Showing 13 changed files with 388 additions and 15 deletions.
29 changes: 29 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[*.cs]

# top-most EditorConfig file
root = true

# Don't use tabs for indentation.
[*]
indent_style = space
trim_trailing_whitespace = true
guidelines = 140
max_line_length = 140

# Code files
[*.{cs,csx,vb,vbx}]
indent_size = 4
insert_final_newline = true
charset = utf-8

# Xml project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2

# Xml config files
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct,xml,stylecop}]
indent_size = 2

# JSON files
[*.json]
indent_size = 2
9 changes: 8 additions & 1 deletion MASA.Utils.sln
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{4F908878-0
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Utils.Data.DataAnnotations.Tests", "test\MASA.Utils.Data.DataAnnotations.Tests\MASA.Utils.Data.DataAnnotations.Tests.csproj", "{1D6302D8-DA31-4782-A076-AC586A286253}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MASA.Utils.Extensions.DependencyInjection", "src\Extensions\MASA.Utils.Extensions.DependencyInjection\MASA.Utils.Extensions.DependencyInjection.csproj", "{00E275C2-3B6A-4F48-AE0B-738AA669226D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Utils.Extensions.DependencyInjection", "src\Extensions\MASA.Utils.Extensions.DependencyInjection\MASA.Utils.Extensions.DependencyInjection.csproj", "{00E275C2-3B6A-4F48-AE0B-738AA669226D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Utils.Data.EntityFrameworkCore", "src\Data\MASA.Utils.Data.EntityFrameworkCore\MASA.Utils.Data.EntityFrameworkCore.csproj", "{66CE7FFE-E2DD-43EA-92DB-82A46D1B2215}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -71,6 +73,10 @@ Global
{00E275C2-3B6A-4F48-AE0B-738AA669226D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{00E275C2-3B6A-4F48-AE0B-738AA669226D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{00E275C2-3B6A-4F48-AE0B-738AA669226D}.Release|Any CPU.Build.0 = Release|Any CPU
{66CE7FFE-E2DD-43EA-92DB-82A46D1B2215}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{66CE7FFE-E2DD-43EA-92DB-82A46D1B2215}.Debug|Any CPU.Build.0 = Debug|Any CPU
{66CE7FFE-E2DD-43EA-92DB-82A46D1B2215}.Release|Any CPU.ActiveCfg = Release|Any CPU
{66CE7FFE-E2DD-43EA-92DB-82A46D1B2215}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -88,6 +94,7 @@ Global
{98E5FEB6-71FA-43A5-9411-BE7C95B59AE5} = {F844C2A1-C36D-400E-A0D8-7658EF9C3B93}
{1D6302D8-DA31-4782-A076-AC586A286253} = {4F908878-0EB8-43E4-96E4-8B1F32E9B635}
{00E275C2-3B6A-4F48-AE0B-738AA669226D} = {B2DA607D-4A39-4F0C-A9B3-DD9A061B0B4E}
{66CE7FFE-E2DD-43EA-92DB-82A46D1B2215} = {F844C2A1-C36D-400E-A0D8-7658EF9C3B93}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D7DAA0E6-098F-4B18-8775-64FDA96F1FF0}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace MASA.Utils.Data.EntityFrameworkCore;

public interface IQueryFilterProvider
{
LambdaExpression OnExecuting(IMutableEntityType entityType);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace MASA.Utils.Data.EntityFrameworkCore;

public interface ISaveChangesFilter
{
void OnExecuting(ChangeTracker changeTracker);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.0-preview.7.21378.4" />
</ItemGroup>

</Project>
118 changes: 118 additions & 0 deletions src/Data/MASA.Utils.Data.EntityFrameworkCore/MasaDbContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
namespace MASA.Utils.Data.EntityFrameworkCore;

public class MasaDbContext : DbContext
{
private readonly MasaDbContextOptions _options;

public MasaDbContext() : base() { }

public MasaDbContext(MasaDbContextOptions options)
: base(options)
{
_options = options;
}

/// <summary>
/// Automatic filter soft delete data.
/// When you override this method,you should call base.<see cref="OnModelCreating(ModelBuilder)"/>.
/// <inheritdoc/>
/// </summary>
/// <param name="modelBuilder"></param>
protected sealed override void OnModelCreating(ModelBuilder modelBuilder)
{
OnModelCreatingExecuting(modelBuilder);

// null when run dotnet ef cli
if (_options == null)
{
base.OnModelCreating(modelBuilder);
return;
}

foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
foreach (var provider in _options.QueryFilterProviders)
{
try
{
var lambda = provider.OnExecuting(entityType);
modelBuilder.Entity(entityType.ClrType).HasQueryFilter(lambda);
}
catch (Exception ex)
{
throw new Exception("An error occured when QueryFilterProvider executing", ex);
}
}
}
}

/// <summary>
/// Use this method instead of OnModelCreating
/// </summary>
/// <param name="modelBuilder"></param>
protected virtual void OnModelCreatingExecuting(ModelBuilder modelBuilder)
{

}

/// <summary>
/// Automatic soft delete.
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
public override int SaveChanges()
{
return SaveChanges(true);
}

/// <summary>
/// Automatic soft delete.
/// <inheritdoc/>
/// </summary>
/// <param name="acceptAllChangesOnSuccess"></param>
/// <returns></returns>
public sealed override int SaveChanges(bool acceptAllChangesOnSuccess)
{
OnFilterExecuting();
return base.SaveChanges(acceptAllChangesOnSuccess);
}

private void OnFilterExecuting()
{
foreach (var filter in _options.SaveChangesFilters)
{
try
{
filter.OnExecuting(ChangeTracker);
}
catch (Exception ex)
{
throw new Exception("An error occured when intercept SaveChanges(Async)", ex);
}
}
}

/// <summary>
/// Automatic soft delete.
/// <inheritdoc/>
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
return SaveChangesAsync(true, cancellationToken);
}

/// <summary>
/// Automatic soft delete.
/// <inheritdoc/>
/// </summary>
/// <param name="acceptAllChangesOnSuccess"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public sealed override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
OnFilterExecuting();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace MASA.Utils.Data.EntityFrameworkCore;

public abstract class MasaDbContextOptions : DbContextOptions
{
public MasaDbContextOptions()
: base(new Dictionary<Type, IDbContextOptionsExtension>())
{

}

public MasaDbContextOptions([NotNull] IReadOnlyDictionary<Type, IDbContextOptionsExtension> extensions)
: base(extensions)
{

}

/// <summary>
/// Can be used to filter data
/// </summary>
public abstract IEnumerable<IQueryFilterProvider> QueryFilterProviders { get; }

/// <summary>
/// Can be used to intercept SaveChanges(Async) method
/// </summary>
public abstract IEnumerable<ISaveChangesFilter> SaveChangesFilters { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace MASA.Utils.Data.EntityFrameworkCore;

public abstract class MasaDbContextOptionsBuilder : DbContextOptionsBuilder
{
public MasaDbContextOptionsBuilder(DbContextOptions options)
: base(options)
{

}

public abstract MasaDbContextOptionsBuilder UseQueryFilterProvider<TProvider>()
where TProvider : class, IQueryFilterProvider;

public abstract MasaDbContextOptionsBuilder UseSaveChangesFilter<TFilter>()
where TFilter : class, ISaveChangesFilter;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace MASA.Utils.Data.EntityFrameworkCore;

public class MasaDbContextOptionsBuilder<TContext> : MasaDbContextOptionsBuilder
where TContext : MasaDbContext
{
private readonly IServiceCollection _services;

public MasaDbContextOptionsBuilder(IServiceCollection services)
: base(new DbContextOptions<TContext>())
{
_services = services;
}

public override MasaDbContextOptionsBuilder UseQueryFilterProvider<TProvider>()
{
_services.AddScoped<IQueryFilterProvider, TProvider>();
return this;
}

public override MasaDbContextOptionsBuilder UseSaveChangesFilter<TFilter>()
{
_services.AddScoped<ISaveChangesFilter, TFilter>();
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
namespace MASA.Utils.Data.EntityFrameworkCore;

public class MasaDbContextOptions<TContext> : MasaDbContextOptions
where TContext : MasaDbContext
{
private readonly DbContextOptions _originOptions;

public MasaDbContextOptions(
DbContextOptions originOptions,
IEnumerable<IQueryFilterProvider> queryFilterProviders,
IEnumerable<ISaveChangesFilter> saveChangesFilters)
{
_originOptions = originOptions;
QueryFilterProviders = queryFilterProviders;
SaveChangesFilters = saveChangesFilters;
}

/// <summary>
/// Can be used to filter data
/// </summary>
public override IEnumerable<IQueryFilterProvider> QueryFilterProviders { get; }

/// <summary>
/// Can be used to intercept SaveChanges(Async) method
/// </summary>
public override IEnumerable<ISaveChangesFilter> SaveChangesFilters { get; }

/// <summary>
/// <inheritdoc/>
/// </summary>
public override Type ContextType => _originOptions.ContextType;

/// <summary>
/// <inheritdoc/>
/// </summary>
public override bool IsFrozen => _originOptions.IsFrozen;

/// <summary>
/// <inheritdoc/>
/// </summary>
public override IEnumerable<IDbContextOptionsExtension> Extensions => _originOptions.Extensions;

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <typeparam name="TExtension"></typeparam>
/// <param name="extension"></param>
/// <returns></returns>
public override DbContextOptions WithExtension<TExtension>(TExtension extension)
{
return _originOptions.WithExtension(extension);
}

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <typeparam name="TExtension"></typeparam>
/// <returns></returns>
public override TExtension FindExtension<TExtension>()
{
return _originOptions.GetExtension<TExtension>();
}

/// <summary>
/// <inheritdoc/>
/// </summary>
public override void Freeze()
{
_originOptions.Freeze();
}

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <typeparam name="TExtension"></typeparam>
/// <returns></returns>
public override TExtension GetExtension<TExtension>()
{
return _originOptions.GetExtension<TExtension>();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace MASA.Utils.Data.EntityFrameworkCore;

public static class ServiceCollectionExtensions
{
public static IServiceCollection AddMasaDbContext<TDbContext>(
this IServiceCollection services,
Action<MasaDbContextOptionsBuilder>? options = null)
where TDbContext : MasaDbContext
{
var builder = new MasaDbContextOptionsBuilder<TDbContext>(services);
options?.Invoke(builder);

services.AddDbContext<TDbContext>();

services.TryAddScoped(typeof(MasaDbContextOptions<TDbContext>), serviceProvider => CreateMasaDbContextOptions<TDbContext>(serviceProvider, builder.Options));
services.TryAddScoped<MasaDbContextOptions>(serviceProvider => serviceProvider.GetRequiredService<MasaDbContextOptions<TDbContext>>());

return services;
}

private static MasaDbContextOptions<TDbContext> CreateMasaDbContextOptions<TDbContext>(
IServiceProvider serviceProvider,
DbContextOptions options)
where TDbContext : MasaDbContext
{
var queryFilterProviders = serviceProvider.GetServices<IQueryFilterProvider>();
var saveChangesFilters = serviceProvider.GetServices<ISaveChangesFilter>();
return new MasaDbContextOptions<TDbContext>(options, queryFilterProviders, saveChangesFilters);
}
}
Loading

0 comments on commit af78655

Please sign in to comment.