Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature(orm): MasaDbContext supports multiple DbContexts #350

Merged
merged 5 commits into from
Nov 29, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ public interface IParserProvider
{
string Name { get; }

Task<bool> ResolveAsync(IServiceProvider serviceProvider, string key, Action<string> action);
Task<bool> ResolveAsync(HttpContext? httpContext, string key, Action<string> action);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ namespace Masa.BuildingBlocks.Isolation;

public interface IIsolationMiddleware
{
Task HandleAsync();
Task HandleAsync(HttpContext? httpContext);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ public class CookieParserProvider : IParserProvider
{
public string Name => "Cookie";

public Task<bool> ResolveAsync(IServiceProvider serviceProvider, string key, Action<string> action)
public Task<bool> ResolveAsync(HttpContext? httpContext, string key, Action<string> action)
{
var httpContext = serviceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;
if (httpContext?.Request.Cookies.ContainsKey(key) ?? false)
{
var value = httpContext.Request.Cookies[key];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ public class EnvironmentVariablesParserProvider : IParserProvider
{
public string Name { get; } = "EnvironmentVariables";

public Task<bool> ResolveAsync(IServiceProvider serviceProvider, string key, Action<string> action)
public Task<bool> ResolveAsync(HttpContext? httpContext, string key, Action<string> action)
{
string? value = System.Environment.GetEnvironmentVariable(key);
string? value = Environment.GetEnvironmentVariable(key);
if (!string.IsNullOrEmpty(value))
{
action.Invoke(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ public class FormParserProvider : IParserProvider
{
public string Name => "Form";

public Task<bool> ResolveAsync(IServiceProvider serviceProvider, string key, Action<string> action)
public Task<bool> ResolveAsync(HttpContext? httpContext, string key, Action<string> action)
{
var httpContext = serviceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;
if (!(httpContext?.Request.HasFormContentType ?? false))
return Task.FromResult(false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ public class HeaderParserProvider : IParserProvider
{
public string Name => "Header";

public Task<bool> ResolveAsync(IServiceProvider serviceProvider, string key, Action<string> action)
public Task<bool> ResolveAsync(HttpContext? httpContext, string key, Action<string> action)
{
var httpContext = serviceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;
if (httpContext?.Request.Headers.ContainsKey(key) ?? false)
{
var value = httpContext.Request.Headers[key].ToString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ public class HttpContextItemParserProvider : IParserProvider
{
public string Name => "Items";

public Task<bool> ResolveAsync(IServiceProvider serviceProvider, string key, Action<string> action)
public Task<bool> ResolveAsync(HttpContext? httpContext, string key, Action<string> action)
{
var httpContext = serviceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;
if (httpContext?.Items.ContainsKey(key) ?? false)
{
var value = httpContext.Items[key]?.ToString() ?? string.Empty;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ public class QueryStringParserProvider : IParserProvider
{
public string Name => "QueryString";

public Task<bool> ResolveAsync(IServiceProvider serviceProvider, string key, Action<string> action)
public Task<bool> ResolveAsync(HttpContext? httpContext, string key, Action<string> action)
{
var httpContext = serviceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;
if (httpContext?.Request.Query.ContainsKey(key) ?? false)
{
var value = httpContext.Request.Query[key].ToString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ public class RouteParserProvider : IParserProvider
{
public string Name => "Route";

public Task<bool> ResolveAsync(IServiceProvider serviceProvider, string key, Action<string> action)
public Task<bool> ResolveAsync(HttpContext? httpContext, string key, Action<string> action)
{
var httpContext = serviceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;
var value = httpContext?.GetRouteValue(key)?.ToString() ?? string.Empty;
if (!string.IsNullOrEmpty(value))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,17 @@ protected override void OnModelCreatingExecuting(ModelBuilder modelBuilder)
modelBuilder.Entity<Student>().OwnsMany(t => t.Hobbies);
}
}

public class CustomQueryDbContext : MasaDbContext
{
public CustomQueryDbContext(MasaDbContextOptions options) : base(options)
{
}

protected override void OnModelCreatingExecuting(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>();
modelBuilder.Entity<Student>().OwnsOne(x => x.Address);
modelBuilder.Entity<Student>().OwnsMany(t => t.Hobbies);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,83 @@ public async Task TestSoftDeleteAsync()
dbContext.Set<Student>().Remove(student);
await dbContext.SaveChangesAsync();

Assert.IsTrue(await dbContext.Set<Student>().CountAsync() == 0);
Assert.IsFalse(await dbContext.Set<Student>().AnyAsync());

var dataFilter = serviceProvider.GetRequiredService<IDataFilter>();
using (dataFilter.Disable<ISoftDelete>())
{
Assert.IsTrue(await dbContext.Set<Student>().CountAsync() == 1);

student = (await dbContext.Set<Student>().Include(s => s.Address).FirstOrDefaultAsync())!;
Assert.IsTrue(student.Id == 1);
Assert.IsTrue(student.Name == "Jim");
Assert.IsTrue(student.Age == 18);
Assert.IsTrue(student.IsDeleted);
Assert.IsTrue(student.Address.City == "ShangHai");
Assert.IsTrue(student.Address.Street == "PuDong");

Assert.IsTrue(student.Hobbies.Count == 2);
Assert.IsTrue(student.Hobbies.Any(h => h.Name == "Sing"));
Assert.IsTrue(student.Hobbies.Any(h => h.Name == "Game"));
}
}

[TestMethod]
public async Task TestAddMultiDbContextAsync()
{
var services = new ServiceCollection();
string connectionString = $"data source=test-{Guid.NewGuid()}";
string connectionStringByQuery = connectionString;
services.AddMasaDbContext<CustomQueryDbContext>(options => options.UseTestSqlite(connectionStringByQuery).UseFilter());
services.AddMasaDbContext<CustomDbContext>(options => options.UseTestSqlite(connectionString).UseFilter());
var serviceProvider = services.BuildServiceProvider();
var dbContext = serviceProvider.GetRequiredService<CustomDbContext>();
var queryDbContext = serviceProvider.GetRequiredService<CustomQueryDbContext>();
await dbContext.Database.EnsureCreatedAsync();
await queryDbContext.Database.EnsureCreatedAsync();

var student = new Student()
{
Id = 1,
Name = "Jim",
Age = 18,
Address = new Address()
{
City = "ShangHai",
Street = "PuDong",
},
Hobbies = new List<Hobby>()
{
new()
{
Name = "Sing",
Description = "loves singing"
},
new()
{
Name = "Game",
Description = "mobile game"
}
}
};
await dbContext.Set<Student>().AddAsync(student);
await dbContext.SaveChangesAsync();
Assert.IsTrue(await dbContext.Set<Student>().CountAsync() == 1);

Assert.IsTrue(await queryDbContext.Set<Student>().AnyAsync());

student = await dbContext.Set<Student>().Include(s => s.Address).Include(s => s.Hobbies).FirstAsync();
dbContext.Set<Student>().Remove(student);
await dbContext.SaveChangesAsync();

Assert.IsFalse(await dbContext.Set<Student>().AnyAsync());
Assert.IsFalse(await queryDbContext.Set<Student>().AnyAsync());

var dataFilter = serviceProvider.GetRequiredService<IDataFilter>();
using (dataFilter.Disable<ISoftDelete>())
{
Assert.IsTrue(await dbContext.Set<Student>().CountAsync() == 1);
Assert.IsTrue(await queryDbContext.Set<Student>().CountAsync() == 1);

student = (await dbContext.Set<Student>().Include(s => s.Address).FirstOrDefaultAsync())!;
Assert.IsTrue(student.Id == 1);
Expand Down Expand Up @@ -146,7 +217,7 @@ public void TestAddMultiMasaDbContextReturnSaveChangeFilterEqual1()
.AddMasaDbContext<CustomDbContext>();

var serviceProvider = services.BuildServiceProvider();
Assert.IsTrue(serviceProvider.GetServices<ISaveChangesFilter>().Count() == 2);
Assert.IsTrue(serviceProvider.GetServices<ISaveChangesFilter<CustomDbContext>>().Count() == 2);
}

[TestMethod]
Expand All @@ -160,7 +231,7 @@ public void TestAddMasaDbContextReturnSaveChangeFilterEqual2()

var serviceProvider = services.BuildServiceProvider();

var filters = serviceProvider.GetServices<ISaveChangesFilter>();
var filters = serviceProvider.GetServices<ISaveChangesFilter<CustomDbContext>>();
Assert.IsTrue(filters.Count() == 2);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,19 @@ protected CustomDbContext CreateDbContext(bool enableSoftDelete, out IServicePro
dbContext.Database.EnsureCreated();
return dbContext;
}

protected CustomQueryDbContext CreateQueryDbContext(bool enableSoftDelete, out IServiceProvider serviceProvider)
{
Services.AddMasaDbContext<CustomQueryDbContext>(options =>
{
if (enableSoftDelete)
options.UseFilter();

options.UseTestSqlite($"data source=test2-{Guid.NewGuid()}");
});
serviceProvider = Services.BuildServiceProvider();
var dbContext = serviceProvider.GetRequiredService<CustomQueryDbContext>();
dbContext.Database.EnsureCreated();
return dbContext;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

// ReSharper disable once CheckNamespace

namespace Microsoft.EntityFrameworkCore;

public interface ISaveChangesFilter<TDbContext> : ISaveChangesFilter
where TDbContext : MasaDbContext, IMasaDbContext
{

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

namespace Microsoft.EntityFrameworkCore;

public class SaveChangeFilter<TDbContext, TUserId> : ISaveChangesFilter
where TDbContext : DbContext
public class SaveChangeFilter<TDbContext, TUserId> : ISaveChangesFilter<TDbContext>
where TDbContext : MasaDbContext, IMasaDbContext
{
private readonly Type _userIdType;
private readonly IUserContext? _userContext;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

namespace Microsoft.EntityFrameworkCore;

public sealed class SoftDeleteSaveChangesFilter<TDbContext, TUserId> : ISaveChangesFilter
where TDbContext : DbContext, IMasaDbContext
public sealed class SoftDeleteSaveChangesFilter<TDbContext, TUserId> : ISaveChangesFilter<TDbContext>
where TDbContext : MasaDbContext, IMasaDbContext
where TUserId : IComparable
{
private readonly Type _userIdType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ protected virtual void OnModelCreatingExecuting(ModelBuilder modelBuilder)

protected virtual void OnModelCreatingConfigureGlobalFilters(ModelBuilder modelBuilder)
{
var methodInfo = typeof(MasaDbContext).GetMethod(nameof(ConfigureGlobalFilters), BindingFlags.NonPublic | BindingFlags.Instance);
var methodInfo = GetType().GetMethod(nameof(ConfigureGlobalFilters), BindingFlags.NonPublic | BindingFlags.Instance);

foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
Expand Down Expand Up @@ -221,20 +221,9 @@ public sealed override async Task<int> SaveChangesAsync(bool acceptAllChangesOnS
}

public abstract class MasaDbContext<TDbContext> : MasaDbContext
where TDbContext : DbContext, IMasaDbContext
where TDbContext : MasaDbContext, IMasaDbContext
{
protected MasaDbContext(MasaDbContextOptions<TDbContext> options) : base(options)
{
}

protected override void OnModelCreatingConfigureGlobalFilters(ModelBuilder modelBuilder)
{
var methodInfo =
typeof(MasaDbContext<TDbContext>).GetMethod(nameof(ConfigureGlobalFilters), BindingFlags.NonPublic | BindingFlags.Instance);

foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
methodInfo!.MakeGenericMethod(entityType.ClrType).Invoke(this, new object?[] { modelBuilder, entityType });
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

namespace Microsoft.EntityFrameworkCore;

public class MasaDbContextOptions<TContext> : MasaDbContextOptions
where TContext : DbContext, IMasaDbContext
public class MasaDbContextOptions<TDbContext> : MasaDbContextOptions
where TDbContext : MasaDbContext, IMasaDbContext
{
private readonly DbContextOptions _originOptions;

Expand All @@ -23,13 +23,13 @@ public MasaDbContextOptions(
public override IEnumerable<IModelCreatingProvider> ModelCreatingProviders
=> _modelCreatingProviders ??= ServiceProvider?.GetServices<IModelCreatingProvider>() ?? new List<IModelCreatingProvider>();

private IEnumerable<ISaveChangesFilter>? _saveChangesFilters;
private IEnumerable<ISaveChangesFilter<TDbContext>>? _saveChangesFilters;

/// <summary>
/// Can be used to intercept SaveChanges(Async) method
/// </summary>
public override IEnumerable<ISaveChangesFilter> SaveChangesFilters
=> _saveChangesFilters ??= ServiceProvider?.GetServices<ISaveChangesFilter>() ?? new List<ISaveChangesFilter>();
=> _saveChangesFilters ??= ServiceProvider?.GetServices<ISaveChangesFilter<TDbContext>>() ?? new List<ISaveChangesFilter<TDbContext>>();

/// <summary>
/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ private static IServiceCollection AddCoreServices<TDbContextImplementation, TUse
serviceProvider => serviceProvider.GetRequiredService<MasaDbContextOptions<TDbContextImplementation>>(),
optionsLifetime));

services.TryAddEnumerable(new ServiceDescriptor(typeof(ISaveChangesFilter),
services.TryAddEnumerable(new ServiceDescriptor(typeof(ISaveChangesFilter<TDbContextImplementation>),
typeof(SaveChangeFilter<TDbContextImplementation, TUserId>), optionsLifetime));
services.TryAddEnumerable(new ServiceDescriptor(typeof(ISaveChangesFilter),
services.TryAddEnumerable(new ServiceDescriptor(typeof(ISaveChangesFilter<TDbContextImplementation>),
typeof(SoftDeleteSaveChangesFilter<TDbContextImplementation, TUserId>), optionsLifetime));
return services;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ public static IEventBusBuilder UseUoW<TDbContext, TUserId>(
where TDbContext : MasaDbContext, IMasaDbContext
where TUserId : IComparable
{
eventBusBuilder.Services.UseUoW<TDbContext, TUserId>(nameof(eventBusBuilder.Services), optionsBuilder, disableRollbackOnFailure,
eventBusBuilder.Services.UseUoW<TDbContext, TUserId>(
nameof(eventBusBuilder.Services),
optionsBuilder,
disableRollbackOnFailure,
useTransaction);
return eventBusBuilder;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@
Provides the ability to parse and obtain Culture, and use it with [I18n](../Masa.Contrib.Globalization.I18n/README.md). Currently, there are three ways to switch languages:

* URL parameter method: ?culture=en-US, this method has the highest priority, the format is: culture=region code
* Cookies method: call L.SetCulture (region code) method to switch
* Client browser language automatic matching: If neither of the previous two methods are set, it supports automatic matching according to the client browser language.
* Cookies method: the cookie format is c=%LANGCODE%|uic=%LANGCODE%, where c is Culture and uic is UICulture, for example:

``` cookie
c=en-UK|uic=en-US
```

* Client browser language automatic matching: If the previous two methods are not set, it supports automatic matching according to the client browser language.

> Detailed [reference](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/localization?view=aspnetcore-7.0#localization-middleware)

## Masa.Contrib.Globalization.I18n.AspNetCore

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@
提供解析获取Culture的能力,配合[I18n](../Masa.Contrib.Globalization.I18n/README.zh-CN.md)来使用,目前支持三种方式进行切换语言:

* URL 参数 方式: ?culture=en-US,此方式优先级最高,格式为:culture=区域码
* Cookies 方式:调用 L.SetCulture(区域码) 方式切换
* Cookies 方式:cookie 格式为 c=%LANGCODE%|uic=%LANGCODE%,其中 c 是 Culture,uic 是 UICulture, 例如:

``` cookie
c=en-UK|uic=en-US
```

* 客户端浏览器语言自动匹配:如果前面两种方式都没有设置,支持自动根据客户端浏览器语言进行匹配。

> 详细[可参考](https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/localization?view=aspnetcore-7.0#localization-middleware)

## Masa.Contrib.Globalization.I18n.AspNetCore

用例:
Expand Down
Loading