私はマルチテナントのASP.NETアプリケーションを持っており、データベースにはソフト削除が設定されています。最初は、クエリレベルで直接データの制限を処理しました。たとえば、次のようになります。
var foos = context.Foos.Where(foo => !foo.Deleted && foo.TenantId = currentTenantId).ToList();
ご想像のように、これはデータアクセスレイヤーのすべてのクエリを膨らませ、正しいフィルタ条件を追加することを忘れると、APIを非常に脆弱にします。 Z.EntityFramework.Plus.EF6
使用して、グローバルフィルタリングをコンテキストに適用することを決定しZ.EntityFramework.Plus.EF6
。
public class FooDataContextFactory
{
public FooDataContext CreateContext()
{
var context = new FooDataContext();
context.Filter<Foo>(collection => collection.Where(foo=> !foo.Deleted));
var principal = Thread.CurrentPrincipal as ClaimsPrincipal;
if (principal.HasClaim(claim => claim.Type == "TenantId"))
{
var currentTenantId = int.Parse(principal.FindFirst("TenantId").Value);
context.Filter<Foo>(collection => collection.Where(foo => foo.TenantId == currentTenantId));
}
return context;
}
}
これは、1人のユーザーにとって完全に機能します。ただし、テナントを切り替えると、フィルタ式がクエリプランキャッシュに保存されるという問題が発生します 。これはEntity Framework Plusの既知の問題です。解決されていないため、回避策を見つける必要があります。
私が考えることができる最も即座の解決策は、クエリプランキャッシュの存続期間を現在のセッションに関連付けることです。ユーザーがログアウトするかテナントを切り替えると、キャッシュは破棄されます。これは可能ですか?もしそうなら、私はこれをどのように達成できますか?
私はまったく同じ問題を抱えていて、同じ問題でZ.EntityFramework.Plus.EF6を使って作業しようとしました。 zzzprojectsチームにもEntityFramework.DynamicFiltersがあり、この目的のためにはるかに優れています。キャッシュされたクエリはパラメータ化され、値は実行時に指定したセレクタ関数を使用して注入されます。
using System.Data.Entity;
using EntityFramework.DynamicFilters;
public class Program
{
public class CustomContext : DbContext
{
private int _tenantId;
public int GetTenantId()
{
return _tenantId;
}
// Call this function to set the tenant once authentication is complete.
// Alternatively, you could pass tenantId in when constructing CustomContext if you already know it
// or pass in a function that returns the tenant to the constructor and call it here.
public void SetTenantId(int tenantId)
{
_tenantId = tenantId;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Filter applies to any model that implements ITenantRestrictedObject
modelBuilder.Filter(
"TenantFilter",
(ITenantRestrictedObject t, int tenantId) => t.TenantId == tenantId,
(CustomContext ctx) => ctx.GetTenantId(), // Might could replace this with a property accessor... I haven't tried it
opt => opt.ApplyToChildProperties(false)
);
}
}
public interface ITenantRestrictedObject
{
int TenantId { get; }
}
}