This reverts commit ea216ffb37
.
This commit is contained in:
Родитель
ea216ffb37
Коммит
0e164a8fec
109
docs/EFCore.md
109
docs/EFCore.md
|
@ -54,28 +54,19 @@ null or mismatched tenants.
|
|||
|
||||
Finbuckle.MultiTenant provides two different ways to utilize this behavior in a database context class:
|
||||
|
||||
1. Implement `IMultiTenantDbContext` and use the provided helper methods as
|
||||
1. Implement `IMultiTenantDbContext` and used the helper methods as
|
||||
[described below](#adding-multitenant-functionality-to-an-existing-dbcontext), or
|
||||
2. Derive from `MultiTenantDbContext` which handles the details for
|
||||
you, [also described below](#deriving-from-multitenantdbcontext).
|
||||
2. Derive from `MultiTenantDbContext` which handles the details for you.
|
||||
|
||||
The first option is more complex, but provides enhanced flexibility and allows existing database context classes (which
|
||||
may derive from a base class) to utilize per-tenant data isolation. The second option is easier, but provides less
|
||||
flexibility. These approaches are both explained in detail further below.
|
||||
flexibility. These approaches are both explained further below.
|
||||
|
||||
## Hybrid Per-tenant and Shared Databases
|
||||
|
||||
When using a shared database context based on `IMultiTenantDbContext` it is simple extend into a hybrid approach simply
|
||||
by assigning some tenants to a separate shared database (or its own completely isolated database) via a tenant info
|
||||
connection string property as [described above](#separate-databases).
|
||||
|
||||
## Configuring and Using a Shared Database
|
||||
|
||||
Whether implementing `IMultiTenantDbContext` directly or deriving from `MultiTenantDbContext`, the context will need to
|
||||
know which entity types should be treated as multi-tenant (i.e. which entity types are to be isolated per tenant) When
|
||||
the database context is initialized, a shadow property named `TenantId` is added to the data model for designated entity
|
||||
types. This property is used internally to filter all requests and commands. If there already is a defined string
|
||||
property named `TenantId` then it will be used.
|
||||
Regardless of how the database context is configured, the context will need to know which entity types should be treated
|
||||
as multi-tenant (i.e. which entity types are to be isolated per tenant) When the database context is initialized, a
|
||||
shadow property named `TenantId` is added to the data model for designated entity types. This property is used
|
||||
internally to filter all requests and commands. If there already is a defined string property named `TenantId` then it
|
||||
will be used.
|
||||
|
||||
There are two ways to designate an entity type as multi-tenant:
|
||||
|
||||
|
@ -199,14 +190,14 @@ default values.
|
|||
> injection, but this was removed in v7.0.0 for consistency. Instead, inject the `IMultiTenantContextAccessor` and use
|
||||
> it to set the `TenantInfo` property in the database context constructor.
|
||||
|
||||
Finally, call the library extension methods as described below. This requires overriding the `OnModelCreating`,
|
||||
`SaveChanges`, and `SaveChangesAsync` methods.
|
||||
Finally, call the library extension methods as described below. This requires overriding
|
||||
the `OnModelCreating`, `SaveChanges`, and `SaveChangesAsync` methods.
|
||||
|
||||
In `OnModelCreating` use the `EntityTypeBuilder` fluent API extension method `IsMultiTenant` to designate entity types
|
||||
as multi-tenant. Call `ConfigureMultiTenant` on the `ModelBuilder` to configure each entity type marked with the
|
||||
`[MultiTenant]` data attribute. This is only needed if using the attribute and internally uses the `IsMultiTenant`
|
||||
fluent API. Make sure to call the base class `OnModelCreating` method if necessary, such as if inheriting from
|
||||
`IdentityDbContext`.
|
||||
as multi-tenant. Call `ConfigureMultiTenant` on the `ModelBuilder` to configure each entity type marked with
|
||||
the `[MultiTenant]` data attribute. This is only needed if using the attribute and internally uses the `IsMultiTenant`
|
||||
fluent API. Make sure to call the base class `OnModelCreating` method if necessary, such as if inheriting
|
||||
from `IdentityDbContext`.
|
||||
|
||||
```csharp
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
|
@ -242,7 +233,7 @@ public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess,
|
|||
}
|
||||
```
|
||||
|
||||
Now whenever this database context is used it will only set and query records for the current tenant.
|
||||
Now, whenever this database context is used it will only set and query records for the current tenant.
|
||||
|
||||
## Deriving from `MultiTenantDbContext`
|
||||
|
||||
|
@ -258,6 +249,8 @@ dotnet add package Finbuckle.MultiTenant.EntityFrameworkCore
|
|||
|
||||
The `MultiTenantDbContext` has two constructors which should be called from any derived database context. Make sure to
|
||||
forward the `IMultiTenatContextAccessor` and, if applicable the `DbContextOptions<T>` into the base constructor.
|
||||
Variants of these constructors that pass `ITenantInfo` to the base constructor are also available, but these will not be
|
||||
used for dependency injection.
|
||||
|
||||
```csharp
|
||||
public class BloggingDbContext : MultiTenantDbContext
|
||||
|
@ -304,61 +297,31 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|||
|
||||
Now, whenever this database context is used it will only set and query records for the current tenant.
|
||||
|
||||
## Dependency Injection
|
||||
## Hybrid Per-tenant and Shared Databases
|
||||
|
||||
For many cases, such as typical ASP.NET Core apps, the normal dependency injection registration of a database context is
|
||||
sufficient. The `AddDbContext` will register the context as a service and provide the necessary dependencies. Injected
|
||||
instances will automatically be associated with the current tenant.
|
||||
|
||||
When registering the database context as a service for use with dependency injection it is important to take into
|
||||
account whether the connection string and/or provider will vary per-tenant. If so, it is recommended to set the
|
||||
connection string and provider in the `OnConfiguring` database context method as described above rather than in the
|
||||
`AddDbContext`
|
||||
service registration method.
|
||||
|
||||
## Factory Instantiation
|
||||
|
||||
In some cases it may be necessary to create a database context instance without dependency injection, such as in code
|
||||
that loops through tenants. In this case, the `MultiTenantDbContext.Create` factory method can be used to create a
|
||||
database context instance for a specific tenant.
|
||||
|
||||
```csharp
|
||||
// create or otherwise obtain a tenant info instance
|
||||
using var tenantInfo = new MyTenantInfo(...);
|
||||
|
||||
// create a database context instance for the tenant
|
||||
using var tenantDbContext = MultiTenantDbContext.Create<AppMultiTenantDbContext, AppTenantInfo>(tenantInfo);
|
||||
|
||||
// create a database context instance for the tenant with an instance of DbOptions<AppMultiTenantDbContext>
|
||||
var tenantDbContextWithOptions = MultiTenantDbContext.Create<AppMultiTenantDbContext, AppTenantInfo>(tenantInfo,
|
||||
dbOptions);
|
||||
|
||||
// loop through a bunch of tenant instances
|
||||
foreach (var tenant in tenants)
|
||||
{
|
||||
using var tenantDbContext = MultiTenantDbContext.Create<AppMultiTenantDbContext, AppTenantInfo>(tenant);
|
||||
// do something with the database context
|
||||
}
|
||||
```
|
||||
|
||||
Make sure to dispose of the database context instance when it is no longer needed, or better yet use a `using` block or
|
||||
variable. This method will work for any database context class expecting a `IMultiTenantContextAccessor` in its
|
||||
constructor and an options DbContextOptions<T> in its constructor.
|
||||
When using a shared database context based on `IMultiTenantDbContext` it is simple extend into a hybrid approach simply
|
||||
by assigning some tenants to a separate shared database (or its own completely isolated database) via the tenant info
|
||||
connection string property.
|
||||
|
||||
## Design Time Instantiation
|
||||
|
||||
Given that a multi-tenant database context usually requires a tenant to function, design time instantiation can be
|
||||
challenging. By default, for things like migrations and command line tools Entity Framework core attempts to create an
|
||||
instance of the context using dependency injection, however usually no valid tenant exists in these cases and DI fails.
|
||||
For this reason it is recommended to use
|
||||
a [design time factory](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/dbcontext-creation#from-a-design-time-factory)
|
||||
wherein a dummy `ITenantInfo` with the desired connection string and passed to the database context creation factory
|
||||
described above.
|
||||
For this reason it is recommended to use a [design time factory](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/dbcontext-creation#from-a-design-time-factory) wherein a dummy `ITenantInfo` is
|
||||
constructed with the desired connection string and passed to the database context constructor.
|
||||
|
||||
## Registering with ASP.NET Core
|
||||
|
||||
When registering the database context as a service in ASP.NET Core it is important to take into account whether the
|
||||
connection string and/or provider will vary per-tenant. If so, it is recommended to set the connection string and
|
||||
provider in the `OnConfiguring` database context method as described above rather than in the `AddDbContext` service
|
||||
registration method.
|
||||
|
||||
## Adding Data
|
||||
|
||||
Added entities are automatically associated with the current `TenantInfo`. If an entity is associated with a different
|
||||
`TenantInfo` then a `MultiTenantException` is thrown in `SaveChanges` or `SaveChangesAsync`.
|
||||
Added entities are automatically associated with the current `TenantInfo`. If an entity is associated with a
|
||||
different `TenantInfo` then a `MultiTenantException` is thrown in `SaveChanges` or `SaveChangesAsync`.
|
||||
|
||||
```csharp
|
||||
// Add a blog for a tenant.
|
||||
|
@ -454,8 +417,8 @@ property on the database context:
|
|||
|
||||
* `TenantMismatchMode.Throw` - A `MultiTenantException` is thrown (default).
|
||||
* `TenantMismatchMode.Ignore` - The entity is added or updated without modifying its `TenantId`.
|
||||
* `TenantMismatchMode.Overwrite` - The entity's `TenantId` is overwritten to match the database context's current
|
||||
`TenantInfo`.
|
||||
* `TenantMismatchMode.Overwrite` - The entity's `TenantId` is overwritten to match the database context's
|
||||
current `TenantInfo`.
|
||||
|
||||
## Tenant Not Set Mode
|
||||
|
||||
|
@ -465,5 +428,5 @@ or `SaveChangesAsync`. This behavior can be changed by setting the `TenantNotSet
|
|||
|
||||
* `TenantNotSetMode.Throw` - For added entities the null `TenantId` will be overwritten to match the database context's
|
||||
current `TenantInfo`. For updated entities a `MultiTenantException` is thrown (default).
|
||||
* `TenantNotSetMode.Overwrite` - The entity's `TenantId` is overwritten to match the database context's current
|
||||
`TenantInfo`.
|
||||
* `TenantNotSetMode.Overwrite` - The entity's `TenantId` is overwritten to match the database context's
|
||||
current `TenantInfo`.
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
// Copyright Finbuckle LLC, Andrew White, and Contributors.
|
||||
// Refer to the solution LICENSE file for more information.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Finbuckle.MultiTenant.Abstractions;
|
||||
using Finbuckle.MultiTenant.Internal;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Finbuckle.MultiTenant.EntityFrameworkCore;
|
||||
|
@ -21,33 +22,9 @@ public abstract class MultiTenantDbContext : DbContext, IMultiTenantDbContext
|
|||
/// <inheritdoc />
|
||||
public TenantNotSetMode TenantNotSetMode { get; set; } = TenantNotSetMode.Throw;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of a multitenant context that accepts a IMultiTenantContextAccessor instance and an optional DbContextOptions instance.
|
||||
/// </summary>
|
||||
/// <param name="tenantInfo">The tenant information to bind to the context.</param>
|
||||
/// <param name="options">The database options instance.</param>
|
||||
/// <typeparam name="TContext">The TContext implementation type.</typeparam>
|
||||
/// <typeparam name="TTenantInfo">The ITenantInfo implementation type.</typeparam>
|
||||
/// <returns></returns>
|
||||
public static TContext Create<TContext, TTenantInfo>(TTenantInfo? tenantInfo, DbContextOptions? options = null)
|
||||
where TContext : DbContext
|
||||
where TTenantInfo : class, ITenantInfo, new()
|
||||
protected MultiTenantDbContext(ITenantInfo? tenantInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
var mca = new StaticMultiTenantContextAccessor<TTenantInfo>(tenantInfo);
|
||||
var context = options switch
|
||||
{
|
||||
null => (TContext)Activator.CreateInstance(typeof(TContext), mca)!,
|
||||
not null => (TContext)Activator.CreateInstance(typeof(TContext), mca, options)!
|
||||
};
|
||||
|
||||
return context;
|
||||
}
|
||||
catch (MissingMethodException)
|
||||
{
|
||||
throw new ArgumentException("The provided DbContext type does not have a constructor that accepts the required parameters.");
|
||||
}
|
||||
TenantInfo = tenantInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -59,13 +36,17 @@ public abstract class MultiTenantDbContext : DbContext, IMultiTenantDbContext
|
|||
TenantInfo = multiTenantContextAccessor.MultiTenantContext.TenantInfo;
|
||||
}
|
||||
|
||||
protected MultiTenantDbContext(ITenantInfo? tenantInfo, DbContextOptions options) : base(options)
|
||||
{
|
||||
TenantInfo = tenantInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the database context instance and binds to the current tenant.
|
||||
/// </summary>
|
||||
/// <param name="multiTenantContextAccessor">The MultiTenantContextAccessor instance used to bind the context instance to a tenant.</param>
|
||||
/// <param name="options">The database options instance.</param>
|
||||
protected MultiTenantDbContext(IMultiTenantContextAccessor multiTenantContextAccessor, DbContextOptions options) :
|
||||
base(options)
|
||||
protected MultiTenantDbContext(IMultiTenantContextAccessor multiTenantContextAccessor, DbContextOptions options) : base(options)
|
||||
{
|
||||
TenantInfo = multiTenantContextAccessor.MultiTenantContext.TenantInfo;
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
using Finbuckle.MultiTenant.Abstractions;
|
||||
|
||||
namespace Finbuckle.MultiTenant.Internal;
|
||||
|
||||
internal class StaticMultiTenantContextAccessor<TTenantInfo>(TTenantInfo? tenantInfo)
|
||||
: IMultiTenantContextAccessor<TTenantInfo>
|
||||
where TTenantInfo : class, ITenantInfo, new()
|
||||
{
|
||||
IMultiTenantContext IMultiTenantContextAccessor.MultiTenantContext => MultiTenantContext;
|
||||
|
||||
public IMultiTenantContext<TTenantInfo> MultiTenantContext { get; } =
|
||||
new MultiTenantContext<TTenantInfo> { TenantInfo = tenantInfo };
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
// Copyright Finbuckle LLC, Andrew White, and Contributors.
|
||||
// Refer to the solution LICENSE file for more information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Finbuckle.MultiTenant.Abstractions;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
@ -14,115 +17,115 @@ public class EntityTypeBuilderExtensionsShould : IDisposable
|
|||
|
||||
public EntityTypeBuilderExtensionsShould()
|
||||
{
|
||||
_connection = new SqliteConnection("DataSource=:memory:");
|
||||
_connection.Open();
|
||||
}
|
||||
_connection = new SqliteConnection("DataSource=:memory:");
|
||||
_connection.Open();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_connection?.Dispose();
|
||||
}
|
||||
_connection?.Dispose();
|
||||
}
|
||||
|
||||
private TestDbContext GetDbContext(Action<ModelBuilder>? config = null, TenantInfo? tenant = null)
|
||||
private TestDbContext GetDbContext(Action<ModelBuilder>? config = null, ITenantInfo? tenant = null)
|
||||
{
|
||||
var options = new DbContextOptionsBuilder()
|
||||
.ReplaceService<IModelCacheKeyFactory, DynamicModelCacheKeyFactory>() // needed for testing only
|
||||
.UseSqlite(_connection)
|
||||
.Options;
|
||||
return new TestDbContext(config, tenant ?? new TenantInfo(), options);
|
||||
}
|
||||
var options = new DbContextOptionsBuilder()
|
||||
.ReplaceService<IModelCacheKeyFactory, DynamicModelCacheKeyFactory>() // needed for testing only
|
||||
.UseSqlite(_connection)
|
||||
.Options;
|
||||
return new TestDbContext(config, tenant ?? new TenantInfo(), options);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetMultiTenantAnnotation()
|
||||
{
|
||||
using var db = GetDbContext();
|
||||
var annotation = db.Model.FindEntityType(typeof(MyMultiTenantThing))?
|
||||
.FindAnnotation(Constants.MultiTenantAnnotationName);
|
||||
using var db = GetDbContext();
|
||||
var annotation = db.Model.FindEntityType(typeof(MyMultiTenantThing))?
|
||||
.FindAnnotation(Constants.MultiTenantAnnotationName);
|
||||
|
||||
Assert.True((bool)annotation!.Value!);
|
||||
}
|
||||
Assert.True((bool)annotation!.Value!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTenantIdStringShadowProperty()
|
||||
{
|
||||
using var db = GetDbContext();
|
||||
var prop = db.Model.FindEntityType(typeof(MyMultiTenantThing))?.FindProperty("TenantId");
|
||||
using var db = GetDbContext();
|
||||
var prop = db.Model.FindEntityType(typeof(MyMultiTenantThing))?.FindProperty("TenantId");
|
||||
|
||||
Assert.Equal(typeof(string), prop?.ClrType);
|
||||
Assert.True(prop?.IsShadowProperty());
|
||||
Assert.Null(prop?.FieldInfo);
|
||||
}
|
||||
Assert.Equal(typeof(string), prop?.ClrType);
|
||||
Assert.True(prop?.IsShadowProperty());
|
||||
Assert.Null(prop?.FieldInfo);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RespectExistingTenantIdStringProperty()
|
||||
{
|
||||
using var db = GetDbContext();
|
||||
var prop = db.Model.FindEntityType(typeof(MyThingWithTenantId))?.FindProperty("TenantId");
|
||||
using var db = GetDbContext();
|
||||
var prop = db.Model.FindEntityType(typeof(MyThingWithTenantId))?.FindProperty("TenantId");
|
||||
|
||||
Assert.Equal(typeof(string), prop!.ClrType);
|
||||
Assert.False(prop.IsShadowProperty());
|
||||
Assert.NotNull(prop.FieldInfo);
|
||||
}
|
||||
Assert.Equal(typeof(string), prop!.ClrType);
|
||||
Assert.False(prop.IsShadowProperty());
|
||||
Assert.NotNull(prop.FieldInfo);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowOnNonStringExistingTenantIdProperty()
|
||||
{
|
||||
using var db = GetDbContext(b => b.Entity<MyThingWithIntTenantId>().IsMultiTenant());
|
||||
Assert.Throws<MultiTenantException>(() => db.Model);
|
||||
}
|
||||
using var db = GetDbContext(b => b.Entity<MyThingWithIntTenantId>().IsMultiTenant());
|
||||
Assert.Throws<MultiTenantException>(() => db.Model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetsTenantIdStringMaxLength()
|
||||
{
|
||||
using var db = GetDbContext();
|
||||
var prop = db.Model.FindEntityType(typeof(MyMultiTenantThing))?.FindProperty("TenantId");
|
||||
using var db = GetDbContext();
|
||||
var prop = db.Model.FindEntityType(typeof(MyMultiTenantThing))?.FindProperty("TenantId");
|
||||
|
||||
Assert.Equal(Internal.Constants.TenantIdMaxLength, prop!.GetMaxLength());
|
||||
}
|
||||
Assert.Equal(Internal.Constants.TenantIdMaxLength, prop!.GetMaxLength());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetGlobalFilterQuery()
|
||||
{
|
||||
// Doesn't appear to be a way to test this except to try it out...
|
||||
var tenant1 = new TenantInfo
|
||||
{
|
||||
Id = "abc"
|
||||
};
|
||||
// Doesn't appear to be a way to test this except to try it out...
|
||||
var tenant1 = new TenantInfo
|
||||
{
|
||||
Id = "abc"
|
||||
};
|
||||
|
||||
var tenant2 = new TenantInfo
|
||||
{
|
||||
Id = "123"
|
||||
};
|
||||
var tenant2 = new TenantInfo
|
||||
{
|
||||
Id = "123"
|
||||
};
|
||||
|
||||
using var db = GetDbContext(null, tenant1);
|
||||
db.Database.EnsureCreated();
|
||||
db.MyMultiTenantThings?.Add(new MyMultiTenantThing() { Id = 1 });
|
||||
db.SaveChanges();
|
||||
using var db = GetDbContext(null, tenant1);
|
||||
db.Database.EnsureCreated();
|
||||
db.MyMultiTenantThings?.Add(new MyMultiTenantThing() { Id = 1 });
|
||||
db.SaveChanges();
|
||||
|
||||
Assert.Equal(1, db.MyMultiTenantThings!.Count());
|
||||
db.TenantInfo = tenant2;
|
||||
Assert.Equal(0, db.MyMultiTenantThings!.Count());
|
||||
}
|
||||
Assert.Equal(1, db.MyMultiTenantThings!.Count());
|
||||
db.TenantInfo = tenant2;
|
||||
Assert.Equal(0, db.MyMultiTenantThings!.Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RespectExistingQueryFilter()
|
||||
{
|
||||
// Doesn't appear to be a way to test this except to try it out...
|
||||
var tenant1 = new TenantInfo
|
||||
{
|
||||
Id = "abc"
|
||||
};
|
||||
// Doesn't appear to be a way to test this except to try it out...
|
||||
var tenant1 = new TenantInfo
|
||||
{
|
||||
Id = "abc"
|
||||
};
|
||||
|
||||
using var db = GetDbContext(config =>
|
||||
{
|
||||
config.Entity<MyMultiTenantThing>().HasQueryFilter(e => e.Id == 1);
|
||||
config.Entity<MyMultiTenantThing>().IsMultiTenant();
|
||||
}, tenant1);
|
||||
db.Database.EnsureCreated();
|
||||
db.MyMultiTenantThings?.Add(new MyMultiTenantThing() { Id = 1 });
|
||||
db.MyMultiTenantThings?.Add(new MyMultiTenantThing() { Id = 2 });
|
||||
db.SaveChanges();
|
||||
using var db = GetDbContext(config =>
|
||||
{
|
||||
config.Entity<MyMultiTenantThing>().HasQueryFilter(e => e.Id == 1);
|
||||
config.Entity<MyMultiTenantThing>().IsMultiTenant();
|
||||
}, tenant1);
|
||||
db.Database.EnsureCreated();
|
||||
db.MyMultiTenantThings?.Add(new MyMultiTenantThing() { Id = 1 });
|
||||
db.MyMultiTenantThings?.Add(new MyMultiTenantThing() { Id = 2 });
|
||||
db.SaveChanges();
|
||||
|
||||
Assert.Equal(1, db.MyMultiTenantThings!.Count());
|
||||
}
|
||||
Assert.Equal(1, db.MyMultiTenantThings!.Count());
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Finbuckle.MultiTenant.Abstractions;
|
||||
using Finbuckle.MultiTenant.Internal;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
||||
|
@ -15,11 +14,10 @@ public class TestDbContext : EntityFrameworkCore.MultiTenantDbContext
|
|||
{
|
||||
private readonly Action<ModelBuilder>? _config;
|
||||
|
||||
public TestDbContext(Action<ModelBuilder>? config, TenantInfo tenantInfo, DbContextOptions options) :
|
||||
base(new StaticMultiTenantContextAccessor<TenantInfo>(tenantInfo), options)
|
||||
public TestDbContext(Action<ModelBuilder>? config, ITenantInfo tenantInfo, DbContextOptions options) : base(tenantInfo, options)
|
||||
{
|
||||
this._config = config;
|
||||
}
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
public DbSet<MyMultiTenantThing>? MyMultiTenantThings { get; set; }
|
||||
public DbSet<MyThingWithTenantId>? MyThingsWithTenantIds { get; set; }
|
||||
|
@ -28,18 +26,18 @@ public class TestDbContext : EntityFrameworkCore.MultiTenantDbContext
|
|||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
// If the test passed in a custom builder use it
|
||||
if (_config != null)
|
||||
_config(modelBuilder);
|
||||
// Of use the standard builder configuration
|
||||
else
|
||||
{
|
||||
modelBuilder.Entity<MyMultiTenantThing>().IsMultiTenant();
|
||||
modelBuilder.Entity<MyThingWithTenantId>().IsMultiTenant();
|
||||
}
|
||||
// If the test passed in a custom builder use it
|
||||
if (_config != null)
|
||||
_config(modelBuilder);
|
||||
// Of use the standard builder configuration
|
||||
else
|
||||
{
|
||||
modelBuilder.Entity<MyMultiTenantThing>().IsMultiTenant();
|
||||
modelBuilder.Entity<MyThingWithTenantId>().IsMultiTenant();
|
||||
}
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once ClassNeverInstantiated.Global
|
||||
|
@ -47,13 +45,13 @@ public class DynamicModelCacheKeyFactory : IModelCacheKeyFactory
|
|||
{
|
||||
public object Create(DbContext context)
|
||||
{
|
||||
return new object();
|
||||
}
|
||||
|
||||
return new object();
|
||||
}
|
||||
|
||||
public object Create(DbContext context, bool designTime)
|
||||
{
|
||||
return new object();
|
||||
}
|
||||
return new object();
|
||||
}
|
||||
}
|
||||
|
||||
public class MyMultiTenantThing
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
// Refer to the solution LICENSE file for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Finbuckle.MultiTenant.Internal;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Finbuckle.MultiTenant.EntityFrameworkCore.Test.Extensions.MultiTenantDbContextExtensions;
|
||||
|
@ -14,7 +13,7 @@ public class TestDbContext : EntityFrameworkCore.MultiTenantDbContext
|
|||
|
||||
public TestDbContext(TenantInfo tenantInfo,
|
||||
DbContextOptions options) :
|
||||
base(new StaticMultiTenantContextAccessor<TenantInfo>(tenantInfo), options)
|
||||
base(tenantInfo, options)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Finbuckle.MultiTenant.Internal;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
||||
|
@ -13,25 +12,26 @@ public class TestDbContext : EntityFrameworkCore.MultiTenantDbContext
|
|||
{
|
||||
private readonly Action<ModelBuilder> _config;
|
||||
|
||||
public TestDbContext(Action<ModelBuilder> config, DbContextOptions options) :
|
||||
base(new StaticMultiTenantContextAccessor<TenantInfo>(new TenantInfo { Id = "dummy" }), options)
|
||||
public TestDbContext(Action<ModelBuilder> config, DbContextOptions options) : base(
|
||||
new TenantInfo {Id = "dummy"},
|
||||
options)
|
||||
{
|
||||
this._config = config;
|
||||
}
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
public DbSet<Blog>? Blogs { get; set; }
|
||||
public DbSet<Post>? Posts { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
optionsBuilder.UseSqlite("DataSource=:memory:");
|
||||
base.OnConfiguring(optionsBuilder);
|
||||
}
|
||||
optionsBuilder.UseSqlite("DataSource=:memory:");
|
||||
base.OnConfiguring(optionsBuilder);
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
_config(modelBuilder);
|
||||
}
|
||||
_config(modelBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
public class Blog
|
||||
|
@ -55,12 +55,12 @@ public class DynamicModelCacheKeyFactory : IModelCacheKeyFactory
|
|||
{
|
||||
public object Create(DbContext context)
|
||||
{
|
||||
return new object();
|
||||
}
|
||||
|
||||
return new object();
|
||||
}
|
||||
|
||||
public object Create(DbContext context, bool designTime)
|
||||
{
|
||||
// Needed for tests that change the model.
|
||||
return new object();
|
||||
}
|
||||
// Needed for tests that change the model.
|
||||
return new object();
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright Finbuckle LLC, Andrew White, and Contributors.
|
||||
// Refer to the solution LICENSE file for more information.
|
||||
|
||||
using Finbuckle.MultiTenant.Internal;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
|
@ -34,8 +33,7 @@ public class MultiTenantDbContextShould
|
|||
Identifier = "abc",
|
||||
Name = "abc"
|
||||
};
|
||||
var mca = new StaticMultiTenantContextAccessor<TenantInfo>(tenant1);
|
||||
var c = new TestBlogDbContext(mca);
|
||||
var c = new TestBlogDbContext(tenant1);
|
||||
|
||||
Assert.NotNull(c);
|
||||
}
|
||||
|
@ -49,65 +47,8 @@ public class MultiTenantDbContextShould
|
|||
Identifier = "abc",
|
||||
Name = "abc"
|
||||
};
|
||||
var mca = new StaticMultiTenantContextAccessor<TenantInfo>(tenant1);
|
||||
var c = new TestBlogDbContext(mca, new DbContextOptions<TestBlogDbContext>());
|
||||
var c = new TestBlogDbContext(tenant1, new DbContextOptions<TestBlogDbContext>());
|
||||
|
||||
Assert.NotNull(c);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkWithCreate()
|
||||
{
|
||||
var tenant1 = new TenantInfo
|
||||
{
|
||||
Id = "abc",
|
||||
Identifier = "abc",
|
||||
Name = "abc"
|
||||
};
|
||||
var c =
|
||||
EntityFrameworkCore.MultiTenantDbContext.Create<TestBlogDbContext, TenantInfo>(tenant1, new DbContextOptions<TestBlogDbContext>());
|
||||
|
||||
Assert.NotNull(c);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkWithCreateNoOptions()
|
||||
{
|
||||
var tenant1 = new TenantInfo
|
||||
{
|
||||
Id = "abc",
|
||||
Identifier = "abc",
|
||||
Name = "abc"
|
||||
};
|
||||
var c = EntityFrameworkCore.MultiTenantDbContext.Create<TestBlogDbContext, TenantInfo>(tenant1);
|
||||
|
||||
Assert.NotNull(c);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateArbitraryDbContext()
|
||||
{
|
||||
var tenant1 = new TenantInfo
|
||||
{
|
||||
Id = "abc",
|
||||
Identifier = "abc",
|
||||
Name = "abc"
|
||||
};
|
||||
var c = EntityFrameworkCore.MultiTenantDbContext.Create<EntityFrameworkCore.MultiTenantIdentityDbContext, TenantInfo>(tenant1);
|
||||
|
||||
Assert.NotNull(c);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowOnInvalidDbContext()
|
||||
{
|
||||
var tenant1 = new TenantInfo
|
||||
{
|
||||
Id = "abc",
|
||||
Identifier = "abc",
|
||||
Name = "abc"
|
||||
};
|
||||
|
||||
Assert.Throws<ArgumentException>(() => EntityFrameworkCore.MultiTenantDbContext.Create<DbContext, TenantInfo>(tenant1));
|
||||
}
|
||||
}
|
|
@ -12,10 +12,18 @@ public class TestBlogDbContext : EntityFrameworkCore.MultiTenantDbContext
|
|||
public DbSet<Blog>? Blogs { get; set; }
|
||||
public DbSet<Post>? Posts { get; set; }
|
||||
|
||||
public TestBlogDbContext(ITenantInfo? tenantInfo) : base(tenantInfo)
|
||||
{
|
||||
}
|
||||
|
||||
public TestBlogDbContext(IMultiTenantContextAccessor multiTenantContextAccessor) : base(multiTenantContextAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
public TestBlogDbContext(ITenantInfo? tenantInfo, DbContextOptions options) : base(tenantInfo, options)
|
||||
{
|
||||
}
|
||||
|
||||
public TestBlogDbContext(IMultiTenantContextAccessor multiTenantContextAccessor, DbContextOptions options) : base(multiTenantContextAccessor, options)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Finbuckle.MultiTenant.Internal;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
||||
|
@ -13,19 +12,20 @@ public class TestDbContext : EntityFrameworkCore.MultiTenantDbContext
|
|||
{
|
||||
private readonly Action<ModelBuilder> _config;
|
||||
|
||||
public TestDbContext(Action<ModelBuilder> config, DbContextOptions options) :
|
||||
base(new StaticMultiTenantContextAccessor<TenantInfo>(new TenantInfo { Id = "dummy" }), options)
|
||||
public TestDbContext(Action<ModelBuilder> config, DbContextOptions options) : base(
|
||||
new TenantInfo { Id = "dummy" },
|
||||
options)
|
||||
{
|
||||
this._config = config;
|
||||
}
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
public DbSet<Blog>? Blogs { get; set; }
|
||||
public DbSet<Post>? Posts { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
_config(modelBuilder);
|
||||
}
|
||||
_config(modelBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
public class Blog
|
||||
|
@ -50,11 +50,11 @@ public class DynamicModelCacheKeyFactory : IModelCacheKeyFactory
|
|||
{
|
||||
public object Create(DbContext context)
|
||||
{
|
||||
return new object();
|
||||
}
|
||||
|
||||
return new object();
|
||||
}
|
||||
|
||||
public object Create(DbContext context, bool designTime)
|
||||
{
|
||||
return new Object(); // Never cache!
|
||||
}
|
||||
return new Object(); // Never cache!
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче