2017-03-08 76 views
5

我正在編寫多租戶應用程序。幾乎所有的表都有「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(); 

我知道我需要使用多列外鍵,但我在設置時遇到了問題。

架構應該像下面..

Vendor 
Id 

FavVendor 
VendorId 
AccountId 
CustomField1 

然後我可以查詢供應商,獲取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(); 

enter image description here

也收到以下錯誤,當我嘗試和FavVendor.Vendor

設置外鍵

FavVendor_Vendor_Source:多重不是在關係中的作用「FavVendor_Vendor_Source「FavVendor_Vendor」有效。因爲「依賴角色」屬性不是關鍵屬性,所以「依賴角色」的多重性的上界必須爲「*」。

回答

3

棘手的問題。 DTO和投影爲您提供所需控制的情況之一。仍然存在純粹的EF解決方案,但必須非常小心地進行編程。我會盡力涵蓋儘可能多的方面。

先用不能完成。

這應該通過建立「一對一」的關係,並有外鍵是「供應商ID」和「帳戶ID」

這是不可能的可能。所述物理(商店)之間的關係是one-to-manyVendor(一個)FavVendor(許多)),雖然對於特定的AccountId邏輯關係one-to-one。但EF只支持物理關係,所以根本無法表示邏輯關係,這也是動態的。

不久,您的關係必須是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; } 
} 

這是迄今爲止我的解決方案,但我有,因爲我不能寫在EF一個子查詢,然後當我更新的記錄,以獲取所有喜歡的供應商,刪除正在發生。

上述兩個問題都可以通過用特殊方式代碼來解決。

首先,由於陰懶惰也不急切裝載支持篩選,剩下的唯一選擇是explicit loading(在應用過濾器描述的,當明確地裝載相關實體的文件部分)或突出部和依靠上下文導航屬性fixup(實際上顯式加載是基於)。爲了避免副作用,必須關閉相關實體的延遲加載(我已通過從導航屬性中刪除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; 
    } 
} 

或更一般地說,對於供應商查詢(已經應用了過濾,排序,分頁等):

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; 
    } 
} 

更新一個賣方及FavVendor從斷開實體特定ACCOUNTID:

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(); 
    } 
} 

(對於異步版本只是在對應Async方法使用await

爲了防止刪除無關FavVendors,你首先加載Vendor過濾FavVendors從數據庫,然後根據傳遞的對象FavVendors內容添加新的,更新或刪除現有的FavVendor記錄。

總括來說,這是可行的,但很難實現和維護(特別是如果你需要包括Vendor,並在查詢過濾FavVendors返回一些其他實體引用Vendor,因爲你不能使用典型Include方法)。您可以考慮嘗試一些第三方軟件包,如Entity Framework Plus ,其查詢過濾器包含查詢過濾器功能可以顯着簡化查詢部分。

0

你的重點是不正確

,而不是

Vendor  TenantVendor  One to many 
Vendor  FavVendor  One to many 
Account FavVendor  One to many 

我覺得應該是

Vendor  TenantVendor  OK 
TenantVendor FavVendor  One to many 

在您的評論

獲取登錄帳戶的FavVendor並繼續我的快樂方式。

所以每個帳戶有你的私人供應商對於關係應該是favVendor和TenantVendor

之間

您的查詢,以可能是有些像

// query 
// how do I only get single vendor for tenant? 
var vendor = DbContext.TenantVendor 
        .Include(x => x.Vendor) 
        .Where(x => x.VendorId == [your vendor id]) 
        .SingleOrDefault(); 

// now filter tenant's favorite vendor 
// problem: if I update this record later, it deletes all records != account.Id 
vendor.TenantVendors= DbContext.FavVendor 
        .Where(x => x.TenantVendor.AccountId = [account id]) 
        .ToList(); 

這裏樣品的EntityFramework地圖

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

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

public class FavVendor 
{ 
    public int Id { get;set; } 

    public string NickName { get; set; } 
    public int TenantVendorId { get; set; } 
    public virtual TenantVendor TenantVendor { get; set; } 
} 

In DbContext

.... 

     protected override void OnModelCreating(DbModelBuilder builder) 
     { 


      builder.Entity<Vendor>() 
       .HasKey(t => t.Id) 
       .Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 

      builder.Entity<TenantVendor>() 
       .HasKey(t => t.Id) 
       .Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 

      builder.Entity<TenantVendor>() 
       .HasRequired(me => me.Vendor) 
       .WithMany() 
       .HasForeignKey(me => me.VendorId) 
       .WillCascadeOnDelete(false); 

      builder.Entity<FavVendor>() 
       .HasKey(t => t.Id) 
       .Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 

      builder.Entity<FavVendor>() 
       .HasRequired(me => me.TenantVendor) 
       .WithMany() 
       .HasForeignKey(me => me.TenantVendorId) 
       .WillCascadeOnDelete(false); 

      } 

      .. 

我改變你的組合鍵來標識密鑰,我認爲是更好,但你選擇不自然的EF支持

0

首先,很難回答問題的方式。這裏有兩個問題,一個是關於自定義字段,另一個是關於收藏供應商。我還必須假設AccountId是指租戶的主鍵;如果是這樣的話,您可以考慮將AccountId重命名爲TenantId以保持一致性。

關於第一部分:

一些商戶要添加自定義字段到供應商的記錄。

這取決於需要自定義字段的程度。這在系統的其他領域是否需要?如果是這樣的話,這是像MongoDB這樣的NoSQL數據庫的好處之一。如果自定義的字段都只是在這一個領域我想補充一個TenantVendorCustomField表:

public class TenantVendorCustomField 
{ 
    [Key] 
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 
    public int Id {get; set;} 
    public int AccountId { get;set;} 
    public int VendorId{ get;set;} 
    public string FieldName { get; set; } 
    public string Value {get; set; } 

    [ForeignKey("AccountId")] 
    public virtual Tenant Tenant { get; set; } 

    [ForeignKey("VendorId")] 
    public virtual Vendor Vendor { get; set; } 

} 

約喜愛的廠商的下一個部分:

,但我必須獲取所有喜愛廠商

我真的很想知道更多關於這裏的業務需求。每個租戶都需要擁有一個最喜歡的供應商嗎?租戶可以有多個最喜歡的供應商嗎?

根據這些回答最喜歡的可能是TenantVendor的屬性:

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

var dbVendorQuery = dbContext.TenantVendors 
    .Include(x => x.Vendor) 
    .Where(x => x.TenantVendor.Favorite && x.TenantVendor.AccountId == _account.Id) ;