Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ indent_size = 4
insert_final_newline = true
charset = utf-8

# Use Upper Case for constant fields
dotnet_naming_style.upper_case_style.capitalization = all_upper
dotnet_naming_style.upper_case_style.word_separator = _
dotnet_naming_rule.constant_fields_should_be_upper_case.severity = warning
dotnet_naming_rule.constant_fields_should_be_upper_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_upper_case.style = upper_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
dotnet_naming_symbols.constant_fields.required_modifiers = const

# Xml project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2
Expand Down
143 changes: 132 additions & 11 deletions Masa.Contrib.sln

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/BuildingBlocks/MASA.BuildingBlocks
Submodule MASA.BuildingBlocks updated 26 files
+7 −0 Masa.BuildingBlocks.sln
+13 −0 src/Data/Masa.BuildingBlocks.Data.Contracts/DataFiltering/IDataFilter.cs
+9 −0 src/Data/Masa.BuildingBlocks.Data.Contracts/DataFiltering/ISoftDelete.cs
+4 −0 src/Data/Masa.BuildingBlocks.Data.Contracts/Masa.BuildingBlocks.Data.Contracts.csproj
+5 −0 src/Data/Masa.BuildingBlocks.Data.Contracts/_Imports.cs
+4 −0 src/Data/Masa.BuildingBlocks.Data.UoW/Masa.BuildingBlocks.Data.UoW.csproj
+1 −1 src/Data/Masa.BuildingBlocks.Data.UoW/_Imports.cs
+1 −1 src/Data/Masa.BuildingBlocks.Data/BaseDbConnectionStringProvider.cs
+24 −0 src/Data/Masa.BuildingBlocks.Data/ConnectionStringNameAttribute.cs
+29 −0 src/Data/Masa.BuildingBlocks.Data/ConnectionStrings.cs
+21 −0 src/Data/Masa.BuildingBlocks.Data/IConnectionStringProvider.cs
+1 −1 src/Data/Masa.BuildingBlocks.Data/IDbConnectionStringProvider.cs
+8 −0 src/Data/Masa.BuildingBlocks.Data/IMasaDbContext.cs
+9 −0 src/Data/Masa.BuildingBlocks.Data/Masa.BuildingBlocks.Data.csproj
+21 −0 src/Data/Masa.BuildingBlocks.Data/MasaDbConnectionOptions.cs
+1 −1 src/Data/Masa.BuildingBlocks.Data/Options/MasaDbContextConfigurationOptions.cs
+5 −0 src/Data/Masa.BuildingBlocks.Data/_Imports.cs
+4 −0 src/Ddd/Masa.BuildingBlocks.Ddd.Domain/Entities/Auditing/AuditEntity.cs
+1 −1 src/Ddd/Masa.BuildingBlocks.Ddd.Domain/Entities/Auditing/IAuditEntity.cs
+1 −0 src/Ddd/Masa.BuildingBlocks.Ddd.Domain/_Imports.cs
+0 −11 src/Isolation/Masa.BuildingBlocks.Isolation/IIsolationDbConnectionStringProvider.cs
+4 −0 src/Isolation/Masa.BuildingBlocks.Isolation/Masa.BuildingBlocks.Isolation.csproj
+0 −18 src/Isolation/Masa.BuildingBlocks.Isolation/Options/DbConnectionOptions.cs
+2 −4 src/Isolation/Masa.BuildingBlocks.Isolation/Options/IsolationDbConnectionOptions.cs
+11 −2 src/Isolation/Masa.BuildingBlocks.Isolation/Options/IsolationOptions.cs
+1 −0 src/Isolation/Masa.BuildingBlocks.Isolation/_Imports.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.Contrib.Data.Contracts.EF.DataFiltering;

public class DataFilter : IDataFilter
{
private readonly IServiceProvider _serviceProvider;
private readonly MemoryCache<Type, object> _cache;

public DataFilter(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_cache = new();
}

public IDisposable Enable<TFilter>() where TFilter : class
=> GetFilter<TFilter>().Enable();

public IDisposable Disable<TFilter>() where TFilter : class
=> GetFilter<TFilter>().Disable();

public bool IsEnabled<TFilter>() where TFilter : class
=> GetFilter<TFilter>().Enabled;

private DataFilter<TFilter> GetFilter<TFilter>()
where TFilter : class
{
return (_cache.GetOrAdd(
typeof(TFilter),
_ => _serviceProvider.GetRequiredService<DataFilter<TFilter>>()
) as DataFilter<TFilter>)!;
}
}

public class DataFilter<TFilter> where TFilter : class
{
private readonly AsyncLocal<DataFilterState> _filter;

public DataFilter() => _filter = new AsyncLocal<DataFilterState>();

public bool Enabled
{
get
{
_filter.Value ??= new DataFilterState(true);

return _filter.Value!.Enabled;
}
}

public IDisposable Enable()
{
if (Enabled)
return NullDisposable.Instance;

_filter.Value!.Enabled = true;

return new DisposeAction(() => Disable());
}

public IDisposable Disable()
{
if (!Enabled)
return NullDisposable.Instance;

_filter.Value!.Enabled = false;

return new DisposeAction(() => Enable());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.Contrib.Data.Contracts.EF.DataFiltering;

public class SoftDeleteSaveChangesFilter<TDbContext> : ISaveChangesFilter where TDbContext : DbContext
{
private readonly TDbContext _context;
private readonly MasaDbContextOptions<TDbContext> _masaDbContextOptions;

public SoftDeleteSaveChangesFilter(MasaDbContextOptions<TDbContext> masaDbContextOptions, TDbContext dbContext)
{
_masaDbContextOptions = masaDbContextOptions;
_context = dbContext;
}

public void OnExecuting(ChangeTracker changeTracker)
{
if (!_masaDbContextOptions.EnableSoftDelete)
return;

changeTracker.DetectChanges();
foreach (var entity in changeTracker.Entries().Where(entry => entry.State == EntityState.Deleted))
{
if (entity.Entity is ISoftDelete)
{
HandleNavigationEntry(entity.Navigations.Where(n => !((IReadOnlyNavigation)n.Metadata).IsOnDependent));

entity.State = EntityState.Modified;
entity.CurrentValues[nameof(ISoftDelete.IsDeleted)] = true;
}
}
}

protected virtual void HandleNavigationEntry(IEnumerable<NavigationEntry> navigationEntries)
{
foreach (var navigationEntry in navigationEntries)
{
if (navigationEntry is CollectionEntry collectionEntry)
{
foreach (var dependentEntry in collectionEntry.CurrentValue ?? new List<object>())
{
HandleDependent(dependentEntry);
}
}
else
{
var dependentEntry = navigationEntry.CurrentValue;
if (dependentEntry != null)
{
HandleDependent(dependentEntry);
}
}
}
}

protected virtual void HandleDependent(object dependentEntry)
{
var entityEntry = _context.Entry(dependentEntry);
entityEntry.State = EntityState.Modified;

if (entityEntry.Entity is ISoftDelete)
entityEntry.CurrentValues[nameof(ISoftDelete.IsDeleted)] = true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.Contrib.Data.Contracts.EF.Internal;

internal class DataFilterState
{
public bool Enabled { get; set; }

public DataFilterState(bool enabled)
{
Enabled = enabled;
}
}
13 changes: 13 additions & 0 deletions src/Data/Masa.Contrib.Data.Contracts.EF/Internal/DisposeAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.Contrib.Data.Contracts.EF.Internal;

internal class DisposeAction : IDisposable
{
private readonly Action _action;

public DisposeAction(Action action) => _action = action;

public void Dispose() => _action.Invoke();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.Contrib.Data.Contracts.EF.Internal;

internal class InstanceBuilder
{
internal delegate object InvokeDelegate(params object[] parameters);

public static InvokeDelegate CreateInstanceDelegate(ConstructorInfo constructorInfo)
{
ParameterInfo[] parameters = constructorInfo.GetParameters();
var parameterParameterExpression = Expression.Parameter(typeof(object[]), "parameters");

var parameterCast = new List<Expression>(parameters.Length);
for (int i = 0; i < parameters.Length; i++)
{
var paramInfo = parameters[i];
var valueObj = Expression.ArrayIndex(parameterParameterExpression, Expression.Constant(i));
var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType);
parameterCast.Add(valueCast);
}
NewExpression newExp = Expression.New(constructorInfo, parameterCast);
var lambdaExp = Expression.Lambda<InvokeDelegate>(newExp, parameterParameterExpression);
return lambdaExp.Compile();
}
}
13 changes: 13 additions & 0 deletions src/Data/Masa.Contrib.Data.Contracts.EF/Internal/NullDisposable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.Contrib.Data.Contracts.EF.Internal;

internal class NullDisposable : IDisposable
{
public static NullDisposable Instance { get; } = new();

public void Dispose()
{
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
Expand All @@ -7,13 +7,13 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Masa.Utils.Data.EntityFrameworkCore" Version="0.4.0-preview.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0" />
<ProjectReference Include="..\..\BuildingBlocks\MASA.BuildingBlocks\src\Data\Masa.BuildingBlocks.Data.Contracts\Masa.BuildingBlocks.Data.Contracts.csproj" />
<ProjectReference Include="..\Masa.Contrib.Data.EntityFrameworkCore\Masa.Contrib.Data.EntityFrameworkCore.csproj" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\BuildingBlocks\MASA.BuildingBlocks\src\Data\Masa.BuildingBlocks.Data.Contracts\Masa.BuildingBlocks.Data.Contracts.csproj" />
<ProjectReference Include="..\..\BuildingBlocks\MASA.BuildingBlocks\src\Data\Masa.BuildingBlocks.Data.UoW\Masa.BuildingBlocks.Data.UoW.csproj" />
<PackageReference Include="Masa.Utils.Caching.Memory" Version="0.4.0-preview.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.Contrib.Data.Contracts.EF;

public static class MasaDbContextOptionsBuilderExtensions
{
private static readonly List<Type> _types = new();

public static MasaDbContextOptionsBuilder UseFilter(
this MasaDbContextOptionsBuilder masaDbContextOptionsBuilder,
Action<FilterOptions>? options = null)
=> masaDbContextOptionsBuilder.UseFilterCore(false, options);

public static MasaDbContextOptionsBuilder UseTestFilter(
this MasaDbContextOptionsBuilder masaDbContextOptionsBuilder,
Action<FilterOptions>? options = null)
=> masaDbContextOptionsBuilder.UseFilterCore(true, options);

private static MasaDbContextOptionsBuilder UseFilterCore(
this MasaDbContextOptionsBuilder masaDbContextOptionsBuilder,
bool isTest,
Action<FilterOptions>? options = null)
{
var filterOptions = new FilterOptions();
options?.Invoke(filterOptions);

masaDbContextOptionsBuilder.Services.TryAddScoped(typeof(DataFilter<>));
masaDbContextOptionsBuilder.Services.TryAddScoped<IDataFilter, DataFilter>();

if (filterOptions.EnableSoftDelete) masaDbContextOptionsBuilder.UseSoftDelete(isTest);

return masaDbContextOptionsBuilder;
}

private static void UseSoftDelete(this MasaDbContextOptionsBuilder masaDbContextOptionsBuilder, bool isTest = false)
{
if (!isTest)
{
if (_types.Any(type => masaDbContextOptionsBuilder.DbContextType == type))
return;

_types.Add(masaDbContextOptionsBuilder.DbContextType);
}

var masaDbContextOptionsType = typeof(MasaDbContextOptions<>).MakeGenericType(masaDbContextOptionsBuilder.DbContextType);
var softDeleteSaveChangesFilterType =
typeof(SoftDeleteSaveChangesFilter<>).MakeGenericType(masaDbContextOptionsBuilder.DbContextType);
var constructorInfo = softDeleteSaveChangesFilterType.GetConstructors().FirstOrDefault()!;
var invokeDelegate = InstanceBuilder.CreateInstanceDelegate(constructorInfo);

masaDbContextOptionsBuilder.Services.TryAdd(
new ServiceDescriptor(typeof(ISaveChangesFilter),
serviceProvider =>
{
var instance = invokeDelegate.Invoke(
serviceProvider.GetRequiredService(masaDbContextOptionsType),
serviceProvider.GetRequiredService(masaDbContextOptionsBuilder.DbContextType));
return instance;
},
ServiceLifetime.Scoped));

masaDbContextOptionsBuilder.EnableSoftDelete = true;
}
}
15 changes: 15 additions & 0 deletions src/Data/Masa.Contrib.Data.Contracts.EF/Options/FilterOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.Contrib.Data.Contracts.EF.Options;

public class FilterOptions
{
/// <summary>
/// enable soft delete
/// default: true
/// If you are sure that you do not need to use soft delete in the project, you can change to false
/// IDataFilter does not support ISoftDelete when soft delete is disabled
/// </summary>
public bool EnableSoftDelete { get; set; } = true;
}
3 changes: 0 additions & 3 deletions src/Data/Masa.Contrib.Data.Contracts.EF/README.md

This file was deleted.

3 changes: 0 additions & 3 deletions src/Data/Masa.Contrib.Data.Contracts.EF/README.zh-CN.md

This file was deleted.

17 changes: 17 additions & 0 deletions src/Data/Masa.Contrib.Data.Contracts.EF/_Imports.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

global using Masa.BuildingBlocks.Data.Contracts.DataFiltering;
global using Masa.Contrib.Data.Contracts.EF.DataFiltering;
global using Masa.Contrib.Data.Contracts.EF.Internal;
global using Masa.Contrib.Data.Contracts.EF.Options;
global using Masa.Contrib.Data.EntityFrameworkCore;
global using Masa.Contrib.Data.EntityFrameworkCore.Filters;
global using Masa.Utils.Caching.Memory;
global using Microsoft.EntityFrameworkCore;
global using Microsoft.EntityFrameworkCore.ChangeTracking;
global using Microsoft.EntityFrameworkCore.Metadata;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.DependencyInjection.Extensions;
global using System.Linq.Expressions;
global using System.Reflection;
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.Contrib.Data.EntityFrameworkCore.Cosmos.Internal;

internal static class Parser
{
public static Dictionary<string, string> ToDictionary(this string connectionString)
{
if (string.IsNullOrEmpty(connectionString))
throw new ArgumentException("Cosmos: empty database connection string", nameof(connectionString));

Dictionary<string, string> dictionary = new();
foreach (var item in connectionString.Split(';'))
{
if (string.IsNullOrEmpty(item))
continue;

if (item.Split('=').Length != 2)
throw new ArgumentException("Cosmos: Bad database connection string");

dictionary.Add(item.Split('=')[0], item.Split('=')[1]);
}
return dictionary;
}
}
Loading