我可以为每个会话使用单独的查询计划缓存吗?

时间:2018-08-23 21:00:59

标签: c# asp.net-mvc-4 entity-framework-6 entity-framework-plus

我有一个多租户ASP.NET应用程序,并且我们的数据库已设置为软删除。最初,我们直接在查询级别处理数据限制,例如:

var foos = context.Foos.Where(foo => !foo.Deleted && foo.TenantId = currentTenantId).ToList();

您可以想象,这会使我们的数据访问层中的所有查询膨胀,并且如果忘记了添加正确的过滤条件,会使API非常容易受到攻击。我们已决定使用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;
    }
}

这非常适合单个用户。但是,当您切换租户时,过滤器表达式保存在the query plan cache中会遇到问题。这是Entity Framework Plus的known issue,由于它似乎尚未解决,因此我需要找到一种解决方法。

我能想到的最直接的解决方案是将查询计划缓存的生存期与当前会话相关联,并且当用户注销或切换租户时,缓存将被破坏。这可能吗?如果可以,我该如何实现呢?

1 个答案:

答案 0 :(得分:0)

我遇到了完全相同的问题,并尝试使用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; }
    }
}