c# - 实体框架在插入后填充相关记录
问题描述
我的一个实体如下所示有几个相关的表(大陆、县、国家),其中外键插入到数据库表中
public class ContactAddress
{
[Key]
public int Id { get; set; }
[Required, MaxLength(150)]
public string AddressLine { get; set; }
[MaxLength(150)]
public string Town { get; set; }
[MaxLength(50)]
public string PostCode { get; set; }
[ForeignKey(nameof(Continent))]
public int? ContinentId { get; set; }
public Continent Continent { get; set; }
[ForeignKey(nameof(Country))]
public int? CountryId { get; set; }
public Country Country { get; set; }
[ForeignKey(nameof(County))]
public int? CountyId { get; set; }
public County County { get; set; }
}
这是向数据库插入新记录的代码
public async Task<ContactAddress> AddNewContactAddress(AddressViewModel viewModel)
{
ContactAddress dbModel= new ContactAddress();
dbModel.AddressLine = viewmodel.AddressLine;
dbModel.Town=viewmodel.Town;
dbModel.PostCode=viewmodel.PostCode;
dbModel.ContinentId=viewmodel.ContinentId;
dbModel.CountryId=viewmodel.CountryId;
dbModel.CountyId=viewmodel.CountyId;
dbContext.ContactAddresses.Add(dbModel);
await context.SaveChangesAsync().ConfigureAwait(false);
return dbModel;
}
返回的 dbModel 包含所有这些信息以及插入记录的 Id / 自动增量生成的主键但是返回的对象上缺少大陆国家县的相关信息
我又创造了一种方法
public async Task<ContactAddress> GetContactAddressDetails(int addressId)
{
return await context.ContactAddresses
.Include(c => c.Continent)
.Include(c => c.Country)
.Include(c => c.County)
.FirstOrDefaultAsync(p => p.Id == addressId);
}
public async Task<ContactAddress> AddNewContactAddress(AddressViewModel viewModel)
{
dbModel = convert fromm viewModel to dbModel
context.ContactAddresses.Add(dbModel);
await context.SaveChangesAsync().ConfigureAwait(false);
if (dbModel.Id > 0)
{
dbModel = await GetContactAddressDetails(dbModel.Id).ConfigureAwait(false);
}
return dbModel;
}
这是在实体框架中处理相关记录的正确方法吗?是否可以避免在插入后编写额外的方法来获取相关的表信息
解决方案
第一个问题是您希望在尚未构建 EF 代理时有效地依赖延迟加载。
而不是这个:
ContactAddress dbModel= new ContactAddress();
利用:
ContactAddress dbModel = context.ContactAddresses.Create();
从那里你可以设置你的 FK 值,EF 应该处理未来的请求来检索导航属性,延迟加载数据。
我提倡插入场景的方法是使用导航属性而不是 FK 属性。在实体中,我通常会声明一个或另一个,但不会同时声明两者。对于“尽可能快”的操作,我将只映射 FK,对于更完整的视图,我将使用导航属性和 FK 的阴影属性。这样做的主要原因是,如果您有导航属性和 FK 属性,那么您现在有两个关系的真实来源:
contactAddress.CountryId
// and
contactAddress.Country.CountryId
一些接受 ContractAddress 的代码可能会使用一个或另一个,并且您将获得不同的行为,具体取决于您是否设置了 FK 或导航属性,以及是否已加载导航属性。在您尝试调用 SaveChanges() 之前,您也不会知道发送的任何提供的值是否有效,让您尝试处理如果一个或多个 FK 值无效时该怎么做。
使用导航属性看起来更像:
public async Task<ContactAddress> AddNewContactAddress(AddressViewModel viewModel)
{
ContactAddress dbModel= new ContactAddress();
dbModel.AddressLine = viewmodel.AddressLine;
dbModel.Town=viewmodel.Town;
dbModel.PostCode=viewmodel.PostCode;
dbModel.Continent = context.Continents.SingleOrDefault(x => x.ContinentId == viewModel.ContinentId) ?? throw new ArgumentException("The provided continent ID was not valid.");
dbModel.Country=context.Countries.SingleOrDefault(x => x.CountryId == viewModel.CountryId) ?? throw new ArgumentException("The provided country ID was not valid.");
dbModel.Country=context.Counties.SingleOrDefault(x => x.CountyId == viewModel.CountyId) ?? throw new ArgumentException("The provided county ID was not valid.");
dbContext.ContactAddresses.Add(dbModel);
await context.SaveChangesAsync().ConfigureAwait(false);
return dbModel;
}
SingleOrDefault()
调用 /w 专用 throws 可以很容易地引发相关实体集Single()
的Expected 1, found 0
异常。这仅取决于您是要处理异常以向客户端提供一些反馈还是仅记录异常。
这避免了重新加载实体的需要。它还断言每个引用的 ID 在创建新行时本身都是有效的。您会得到一个准备就绪的完整模型。加载实体似乎是不必要的成本,但考虑到您希望在调用后加载数据,这里完成的性能成本与发回时的延迟加载调用没有什么不同。您获得的好处是断言这些值是有效的,每个值都有一个有意义的“抛出”点,而不是在SaveChanges
调用时一次全部。
推荐阅读
- python - Postgres:返回由整数组成的复合对象作为整数数组的函数
- visual-studio - Visual Studio ASP.NET Core 调试 IIS Express - 无法访问站点
- javascript - 输入中的日期格式
- wpf - WPF:样式覆盖属性
- antlr4 - 为什么词法分析器规则名称单词导致解析错误
- azure - 通过 REST API 从 Azure Devops 构建中获取使用的管道工件
- java - BigQuery 出现问题
- flutter - 使用flutter_inappwebview加载网页时的CircularProgressIndicator在ios中不起作用
- r - 在R中的光栅.tif文件中设置最大值
- docker - Dockerfile:使用 ADD 文件作为 dockerfile 的第一行,而不是 FROM