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'中无效.由于"从属角色"属性不是关键属性,因此从属角色的多重性的上限必须为"*".
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
| 归档时间: |
|
| 查看次数: |
313 次 |
| 最近记录: |