实体框架多租户自定义共享表

TWi*_*lly 6 sql-server entity-framework

我正在写一个多租户申请.几乎所有表都有"AccountId"来指定哪个租户拥有该记录.我有一个表,其中包含所有租户都可以访问的"供应商"列表,它没有AccountId.

一些租户希望将自定义字段添加到供应商记录中.

如何在Code First Entity Framework中进行设置?这是我的解决方案到目前为止,但我必须获取所有喜欢的供应商,因为我不能在EF中编写子查询,然后当我更新记录时,删除正在发生.

public class Vendor
{
    public int Id { get;set;} 
    public string Name { get; set; }
}

public class TenantVendor
{
    public int AccountId { get;set;} 
    public int VendorId{ get;set;} 
    public string NickName { get; set; }
}


// query
// how do I only get single vendor for tenant?
var vendor = await DbContext.Vendors
                            .Include(x => x.TenantVendors) 
                            .SingleAsync(x => x.Id == vendorId);

// now filter tenant's favorite vendor
// problem: if I update this record later, it deletes all records != account.Id
vendor.TenantVendors= vendor.FavoriteVendors
                            .Where(x => x.AccountId == _account.Id)
                            .ToList();
Run Code Online (Sandbox Code Playgroud)

我知道我需要使用多列外键,但是我无法设置它.

架构应如下所示..

Vendor
 Id

FavVendor
 VendorId
 AccountId
 CustomField1
Run Code Online (Sandbox Code Playgroud)

然后我可以查询供应商,获取登录帐户的FavVendor并继续我的快乐方式.

我目前的解决方案,它给了我一个额外的"Vendor_Id"外键,但没有正确设置它

这应该可以通过建立"一对一"关系并使外键为"供应商ID"和"帐户ID"来实现.

现在尝试在实体框架中进行此设置...

public class Vendor
{
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual FavVendor FavVendor { get; set; }
}

public class FavVendor
{
    public string NickName { get; set; }

    [Key, Column(Order = 0)]
    public int VendorId { get; set; }
    public Vendor Vendor { get; set; }

    [Key, Column(Order = 1)]
    public int AccountId { get; set; }
    public Account Account { get; set; }
}


 // query to get data
  var dbVendorQuery = dbContext.Vendors
         .Include(x => x.FavVendor)
         .Where(x => x.FavVendor == null || x.FavVendor.AccountId == _account.Id) ;

 // insert record
 if (dbVendor.FavVendor == null)
 {
     dbVendor.FavVendor = new FavVendor()
     { 
        Account = _account,
     };
  } 
  dbVendor.FavVendor.NickName = nickName;

  dbContext.SaveChanges();
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

当我尝试在FavVendor.Vendor上设置外键时也收到以下错误

FavVendor_Vendor_Source ::多重性在关系'FavVendor_Vendor'中的角色'FavVendor_Vendor_Source'中无效.由于"从属角色"属性不是关键属性,因此从属角色的多重性的上限必须为"*".

Iva*_*oev 4

EF 自然不支持棘手的问题。DTO 和投影为您提供所需控制的情况之一。仍然存在纯 EF 解决方案,但必须非常仔细地编程。我将尽力涵盖尽可能多的方面。

让我们从不能做的事情开始。

这应该可以通过设置“一对一”关系并将外键设置为“供应商 ID”和“帐户 ID”来实现

这不可能。物理(存储)关系是((一)到(多)),尽管特定的逻辑关系是。但 EF 仅支持物理关系,因此根本无法表示逻辑关系,而且逻辑关系还是动态的。one-to-manyVendorFavVendorAccountIdone-to-one

很快,这种关系必须one-to-many与您最初设计中的一样。这是最终的模型:

public class Vendor
{
    public int Id { get; set; }
    public string Name { get; set; }

    public ICollection<FavVendor> FavVendors { get; set; }
}

public class FavVendor
{
    public string NickName { get; set; }

    [Key, Column(Order = 0)]
    public int VendorId { get; set; }
    public Vendor Vendor { get; set; }

    [Key, Column(Order = 1)]
    public int AccountId { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,这是我的解决方案,但我必须获取所有最喜欢的供应商,因为我无法在 EF 中编写子查询,然后当我更新记录时,就会发生删除。

上述两个问题都可以通过特殊方式编写代码来解决。

首先,由于延迟加载和急切加载都不支持过滤,因此唯一剩下的选项是显式加载(在文档的显式加载相关实体时应用过滤器部分中描述)或投影并依赖上下文导航属性修复(实际上显式加载是基于)。为了避免副作用,必须关闭相关实体的延迟加载(我已经通过virtual从导航属性中删除关键字来做到这一点),并且数据检索应该始终通过新的短期DbContext实例进行,以消除相关实体的无意加载由相同的导航属性修复功能引起的数据,我们依靠该功能来过滤FavVendors.

话虽如此,以下是一些操作:

检索特定 AccountId 的已过滤 FavVendors 的供应商:

通过 ID 检索单个供应商:

public static partial class VendorUtils
{
    public static Vendor GetVendor(this DbContext db, int vendorId, int accountId)
    {
        var vendor = db.Set<Vendor>().Single(x => x.Id == vendorId);
        db.Entry(vendor).Collection(e => e.FavVendors).Query()
            .Where(e => e.AccountId == accountId)
            .Load();
        return vendor;
    }

    public static async Task<Vendor> GetVendorAsync(this DbContext db, int vendorId, int accountId)
    {
        var vendor = await db.Set<Vendor>().SingleAsync(x => x.Id == vendorId);
        await db.Entry(vendor).Collection(e => e.FavVendors).Query()
            .Where(e => e.AccountId == accountId)
            .LoadAsync();
        return vendor;
    }
}
Run Code Online (Sandbox Code Playgroud)

或者更一般地说,对于供应商查询(已应用过滤、排序、分页等):

public static partial class VendorUtils
{
    public static IEnumerable<Vendor> WithFavVendor(this IQueryable<Vendor> vendorQuery, int accountId)
    {
        var vendors = vendorQuery.ToList();
        vendorQuery.SelectMany(v => v.FavVendors)
            .Where(fv => fv.AccountId == accountId)
            .Load();
        return vendors;
    }

    public static async Task<IEnumerable<Vendor>> WithFavVendorAsync(this IQueryable<Vendor> vendorQuery, int accountId)
    {
        var vendors = await vendorQuery.ToListAsync();
        await vendorQuery.SelectMany(v => v.FavVendors)
            .Where(fv => fv.AccountId == accountId)
            .LoadAsync();
        return vendors;
    }
}
Run Code Online (Sandbox Code Playgroud)

从断开连接的实体更新特定 AccountId 的 Vendor 和 FavVendor:

public static partial class VendorUtils
{
    public static void UpdateVendor(this DbContext db, Vendor vendor, int accountId)
    {
        var dbVendor = db.GetVendor(vendor.Id, accountId);
        db.Entry(dbVendor).CurrentValues.SetValues(vendor);

        var favVendor = vendor.FavVendors.FirstOrDefault(e => e.AccountId == accountId);
        var dbFavVendor = dbVendor.FavVendors.FirstOrDefault(e => e.AccountId == accountId);
        if (favVendor != null)
        {
            if (dbFavVendor != null)
                db.Entry(dbFavVendor).CurrentValues.SetValues(favVendor);
            else
                dbVendor.FavVendors.Add(favVendor);
        }
        else if (dbFavVendor != null)
            dbVendor.FavVendors.Remove(dbFavVendor);

        db.SaveChanges();
    }
}
Run Code Online (Sandbox Code Playgroud)

(对于异步版本,只需await在相应的Async方法上使用)

为了防止删除不相关的记录FavVendors,您首先加载从数据库中过滤的Vendor记录,然后根据传递的对象内容添加新的、更新的或删除现有的记录。 FavVendorsFavVendorsFavVendor

回顾一下,它是可行的,但很难实现和维护(特别是如果您需要在返回一些其他实体引用的查询中包含Vendor和过滤,因为您无法使用典型的方法)。您可能会考虑尝试一些第三方软件包,例如Entity Framework Plus ,其查询过滤器包含查询过滤器功能可以显着简化查询部分。FavVendorsVendorInclude