我正在撰写多租户申请表。几乎所有表都有“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();
当我尝试在FavVendor.Vendor上设置外键时也收到以下错误
FavVendor_Vendor_Source ::多重性在关系'FavVendor_Vendor'中的角色'FavVendor_Vendor_Source'中无效。由于Dependent Role属性不是关键属性,因此Dependent Role的多重性的上限必须为'*'。
答案 0 :(得分:4)
EF自然不支持棘手的问题。 DTO和投影为您提供所需控制的情况之一。仍然存在纯EF解决方案,但必须非常仔细地编程。我将尽力涵盖尽可能多的方面。
让我们从无法开始。
这应该可以通过建立“一对一”关系并使外键为“供应商ID”和“帐户ID”来实现
这是不可能的。 物理(商店)关系是one-to-many
(Vendor
(一)到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中编写子查询,然后当我更新记录时,删除正在发生。
上述两个问题都可以通过特殊方式编写代码来解决。
首先,由于nether lazy 和渴望加载支持过滤,唯一剩下的选项是explicit loading(在显式加载时应用过滤器中描述相关实体文档部分)或投影并依赖于上下文导航属性修正(实际上显式加载是基于)。为了避免副作用,必须关闭所涉及实体的延迟加载(我已经通过从导航属性中删除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;
}
}
从断开连接的实体更新特定AccountId的供应商和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();
}
}
(对于异步版本,只需在相应的await
方法上使用Async
)
为了防止删除不相关的FavVendors
,您首先从数据库加载Vendor
过滤 FavVendors
,然后根据传递的对象{{1内容可以添加新内容,更新或删除现有的FavVendors
记录。
总结一下,它是可行的,但很难实现和维护(特别是如果您需要在返回引用FavVendor
的其他实体的查询中包含Vendor
和筛选FavVendors
,因为您不能使用典型的Vendor
方法。您可以考虑尝试一些第三方软件包,例如Entity Framework Plus
,其查询过滤器和包含查询过滤器功能可以显着简化查询部分。
答案 1 :(得分: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; }
}
在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);
}
..
我将您的复合键更改为身份键我认为更好但是您选择
答案 2 :(得分: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) ;