【问题标题】:DbSet, ModelBuilder, and EF Navigation PropertiesDbSet、模型构建器和 EF 导航属性
【发布时间】:2013-11-03 21:01:51
【问题描述】:

我正在尝试实现一个多租户应用程序,在该应用程序中我通过租户对象查询数据库,而不是直接脱离上下文。在我有这个之前:

public User GetUserByEmail(string email)
    {
        using (var db = CreateContext())
        {
            return db.Users.FirstOrDefault(u => u.Email.Equals(email, StringComparison.OrdinalIgnoreCase));
        }
    }

现在我有了这个:

public User GetUserByEmail(string email)
    {
        using (var db = CreateContext())
        {
            return _tenant.Users.FirstOrDefault(u => u.Email.Equals(email, StringComparison.OrdinalIgnoreCase));
        }
    }

其中租户如下:

public class Tenant
{
    public Tenant()
    {
    }

    [Key]
    [Required]
    public int TenantId { get; set; }

    public virtual DbSet<User> Users { get; set; }
    // etc
}

我的用户模型具有以下内容:

public virtual List<Tenant> Tenants { get; set; }

在我的上下文配置中,我有以下内容:

modelBuilder.Entity<Tenant>()
        .HasMany(e => e.Users)
        .WithMany()
        .Map(m =>
        {
            m.ToTable("UserTenantJoin");
            m.MapLeftKey("TenantId");
            m.MapRightKey("UserId");
        });

但是我遇到了一个问题,即 DbSet 与上面的模型构建器不兼容 - 它让 HasMany 窒息,说不能从使用情况中推断出 DbSet 的使用。

我改用 ICollection,但是在我的服务层中,所有对 _tenant.Users.Include(stuff)Find() 的调用都中断了。

使用 ICollection 时会中断的服务方法示例:

   public User GetUserWithInterestsAndAptitudes(string username)
    {
        using (var db = CreateContext())
        {
            return _tenant.Users.  // can't use .Include on ICollection
                Include(u => u.Relationships).
                Include(u => u.Interests).
                Include(u => u.Interests.Select(s => s.Subject)).
                Include(u => u.Interests.Select(s => s.Aptitude)).
                FirstOrDefault(s => s.Username.Equals(username, StringComparison.OrdinalIgnoreCase));
        }
    }

我希望有一个解决方案可以让我保持导航属性可查询,而无需重新构建我的服务层。

一个选择是我通过db.Users 将所有内容恢复为使用上下文,然后为每个查询添加另一个条件.Where(u =&gt; u.TenantId == _tenant.TenantId) - 但我试图避免这种情况。

我们将不胜感激。

【问题讨论】:

    标签: c# asp.net entity-framework ef-code-first


    【解决方案1】:

    我有一个类似于您试图避免的解决方案。

    我有一个真实的 DbContext,它只能通过 TenantContext 访问。

    public class RealContext
    {
         public DbSet<User> Users { get; set; }
         [...]
    }
    
    public class TenantContext 
    {
        private RealContext realContext;
        private int tenantId;
        public TenantContext(int tenantId)
        {
            realContext = new RealContext();
            this.tenantId= tenantId;
        }
        public IQueryable<User> Users { get { FilterTenant(realContext.Users); }     }
    
        private IQueryable<T> FilterTenant<T>(IQueryable<T> values) where T : class, ITenantData
        {
             return values.Where(x => x.TenantId == tenantId);
        }
        public int SaveChanges()
        {
            ApplyTenantIds();
            return realContext.SaveChanges();
        }
    }
    

    使用这种方法,我确信在没有获得正确租户的情况下不会发送查询。为了在上下文中添加和删除项目,我使用这两种通用方法。

    public void Remove<T>(params T[] items) where T : class, ITenantData
    {
        var set = realContext.Set<T>();
        foreach(var item in items)
            set.Remove(item);
    }
    
    public void Add<T>(params T[] items) where T : class, ITenantData
    {
        var set = realContext.Set<T>();
        foreach (var item in items)
            set.Add(item);
    }
    

    【讨论】:

    • 谢谢,这很有趣。我很好奇 - 当您从服务/存储库层新建 TenantContext 时,您如何访问tenantId?如果您有时间,我发布了另一个问题:stackoverflow.com/questions/19756369/…
    • 在我的例子中,tenantId 存储在 usertable 的列中。
    • 您对将 FilterTenant 的输出转换为 DbSet 有什么想法,这样我就不必为 Remove 和 Add 编写泛型了?
    • 从来没有想过这个想法。甚至可能吗?如果演员表有效,我认为它没有任何问题。
    • 我很害怕,没有尝试。我确实认为这是可能的(resharper 曾经建议过它),但不知道它有多安全,所以我采纳了你最初的建议:)。到现在为止还挺好!感谢您的帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多