-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTenantScopeableEntityBehaviorConfiguration.cs
More file actions
158 lines (134 loc) · 7.06 KB
/
TenantScopeableEntityBehaviorConfiguration.cs
File metadata and controls
158 lines (134 loc) · 7.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using DataAccessClient.Configuration;
using DataAccessClient.EntityBehaviors;
using DataAccessClient.Providers;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace DataAccessClient.EntityFrameworkCore.Relational.Configuration.EntityBehaviors
{
internal static class TenantScopeableEntityBehaviorConfigurationExtensions
{
internal static readonly MethodInfo ModelBuilderConfigureEntityBehaviorITenantScopableMethod;
static TenantScopeableEntityBehaviorConfigurationExtensions()
{
ModelBuilderConfigureEntityBehaviorITenantScopableMethod = typeof(TenantScopeableEntityBehaviorConfigurationExtensions).GetTypeInfo()
.DeclaredMethods
.Single(m => m.Name == nameof(ConfigureEntityBehaviorITenantScopable));
}
internal static ModelBuilder ConfigureEntityBehaviorITenantScopable<TEntity, TIdentifierType>(
ModelBuilder modelBuilder, Expression<Func<TEntity, bool>> queryFilter)
where TEntity : class, ITenantScopable<TIdentifierType>
where TIdentifierType : struct
{
modelBuilder.Entity<TEntity>()
.IsTenantScopable<TEntity, TIdentifierType>(queryFilter);
return modelBuilder;
}
internal static EntityTypeBuilder<TEntity> IsTenantScopable<TEntity, TIdentifierType>(
this EntityTypeBuilder<TEntity> entity, Expression<Func<TEntity, bool>> queryFilter)
where TEntity : class, ITenantScopable<TIdentifierType>
where TIdentifierType : struct
{
entity.Property(e => e.TenantId).IsRequired();
#if NET10_0_OR_GREATER
entity.HasQueryFilter("TenantScopeableEntityBehaviorQueryFilter", queryFilter);
#else
entity.AppendQueryFilter(queryFilter);
#endif
return entity;
}
}
public class TenantScopeableEntityBehaviorConfiguration<TTenantIdentifierType> : IEntityBehaviorConfiguration where TTenantIdentifierType : struct
{
public void OnRegistering(IServiceCollection serviceCollection)
{
serviceCollection.TryAddScoped<IMultiTenancyConfiguration, DefaultMultiTenancyConfiguration>();
}
public Dictionary<string, dynamic> OnExecutionContextCreating(IServiceProvider scopedServiceProvider)
{
var tenantIdentifierProvider = scopedServiceProvider.GetService<ITenantIdentifierProvider<TTenantIdentifierType>>();
var multiTenancyConfiguration = scopedServiceProvider.GetService<IMultiTenancyConfiguration>();
var context = new Dictionary<string, dynamic>();
if (tenantIdentifierProvider != null)
{
context.Add(typeof(ITenantIdentifierProvider<TTenantIdentifierType>).Name, tenantIdentifierProvider);
}
if (multiTenancyConfiguration != null)
{
context.Add(typeof(IMultiTenancyConfiguration).Name, multiTenancyConfiguration);
}
return context;
}
public void OnModelCreating(ModelBuilder modelBuilder, RelationalDbContext relationalDbContext, Type entityType)
{
var entityInterfaces = entityType.GetInterfaces();
if (entityInterfaces.Any(x =>
x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ITenantScopable<>)))
{
var identifierType = entityType.GetInterface(typeof(ITenantScopable<>).Name)
.GenericTypeArguments[0];
var createTenantScopableQueryFilter = GetType().GetMethod(nameof(CreateTenantScopableQueryFilter),
BindingFlags.Static | BindingFlags.NonPublic);
if (createTenantScopableQueryFilter == null)
{
throw new InvalidOperationException(
$"Can not find method {nameof(CreateTenantScopableQueryFilter)} on class {GetType().FullName}");
}
var tenantScopableQueryFilterMethod = createTenantScopableQueryFilter.MakeGenericMethod(entityType);
var tenantScopableQueryFilter = tenantScopableQueryFilterMethod.Invoke(this, new object[]{relationalDbContext});
TenantScopeableEntityBehaviorConfigurationExtensions.ModelBuilderConfigureEntityBehaviorITenantScopableMethod.MakeGenericMethod(entityType, identifierType)
.Invoke(null, new [] { modelBuilder, tenantScopableQueryFilter });
}
}
public void OnBeforeSaveChanges(RelationalDbContext relationalDbContext, DateTime onSaveChangesTime)
{
var tenantIdentifier = relationalDbContext.ExecutionContext
.Get<ITenantIdentifierProvider<TTenantIdentifierType>>().Execute();
foreach (var entityEntry in relationalDbContext.ChangeTracker.Entries<ITenantScopable<TTenantIdentifierType>>()
.Where(c => c.State == EntityState.Added))
{
var tenantId = entityEntry.Entity.TenantId;
if (tenantId.Equals(default(TTenantIdentifierType)))
{
if (tenantIdentifier.HasValue)
{
entityEntry.Entity.TenantId = tenantIdentifier.Value;
}
else
{
throw new InvalidOperationException(
$"CurrentTenantId is needed for new entity of type '{entityEntry.Entity.GetType().FullName}', but the '{typeof(RelationalDbContext).FullName}' does not have one at this moment");
}
}
}
}
public void OnAfterSaveChanges(RelationalDbContext relationalDbContext)
{
}
private static TTenantIdentifierType? CurrentTenantIdentifier(RelationalDbContext dbContext)
{
var tenantIdentifier = dbContext.ExecutionContext.Get<ITenantIdentifierProvider<TTenantIdentifierType>>().Execute();
return tenantIdentifier;
}
private static bool IsTenantScopableQueryFilterEnabled(RelationalDbContext dbContext)
{
var multiTenancyConfiguration = dbContext.ExecutionContext.Get<IMultiTenancyConfiguration>();
return multiTenancyConfiguration.IsQueryFilterEnabled;
}
private static Expression<Func<TEntity, bool>> CreateTenantScopableQueryFilter<TEntity>(RelationalDbContext dbContext)
where TEntity : class, ITenantScopable<TTenantIdentifierType>
{
Expression<Func<TEntity, bool>> tenantScopableQueryFilter = e =>
e.TenantId.Equals(CurrentTenantIdentifier(dbContext)) ||
e.TenantId.Equals(CurrentTenantIdentifier(dbContext)) ==
IsTenantScopableQueryFilterEnabled(dbContext);
return tenantScopableQueryFilter;
}
}
}