Entity Framework 插入后填充相关记录
Entity Framework populate related records after insert
我的一个实体如下所示有几个相关的 tables(大陆、县、国家),其中外键被插入到数据库中 table
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 / auto incrememnt 生成的主键
但是返回的对象缺少大陆国家县的关联信息
我又创建了一种方法
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;
}
这是entity framework中处理相关记录的正确方法吗?我们是否可以避免在 insert
之后编写额外的方法来获取相关的 table 信息
第一个问题是在没有构建 EF Proxy 的情况下,你想有效地依赖延迟加载。
而不是这个:
ContactAddress dbModel= new ContactAddress();
使用:
ContactAddress dbModel = context.ContactAddresses.Create();
从那里您可以设置 FK 值,EF 应该处理未来的请求以检索导航属性,延迟加载数据。
我提倡的插入方案方法是使用导航属性而不是 FK 属性。在实体中,我通常会声明其中之一,但不会同时声明两者。对于“尽可能快”的操作,我将只映射 FK,对于更完整的视图,我将使用具有 FK 阴影属性的导航属性。这样做的主要原因是,如果您有导航属性和 FK 属性,那么您现在有两个关系的真实来源:
contactAddress.CountryId
// and
contactAddress.Country.CountryId
某些接受 ContractAddress 的代码可能会使用一个或另一个,您将获得不同的行为,具体取决于您设置的是 FK 还是导航 属性,另外还取决于导航 [=36] =] 是否已加载。在您尝试调用 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()
calls /w dedicated throws 可以很容易地成为 Single()
,其中会引发所讨论的实体集的 Expected 1, found 0
异常。这仅取决于您是要处理异常以向客户端提供一些反馈还是仅记录异常。
这避免了重新加载实体的需要。它还断言每个引用的 ID 在创建新行时本身都是有效的。您会得到一个完整的模型,可以使用了。加载实体似乎是不必要的成本,但如果您希望在调用后加载数据,那么这里完成的性能成本与发回时的延迟加载调用没有什么不同。您获得的好处是断言这些值是有效的,每个值都有一个有意义的“抛出”点,而不是在 SaveChanges
调用中一次。
我的一个实体如下所示有几个相关的 tables(大陆、县、国家),其中外键被插入到数据库中 table
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 / auto incrememnt 生成的主键 但是返回的对象缺少大陆国家县的关联信息
我又创建了一种方法
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;
}
这是entity framework中处理相关记录的正确方法吗?我们是否可以避免在 insert
之后编写额外的方法来获取相关的 table 信息第一个问题是在没有构建 EF Proxy 的情况下,你想有效地依赖延迟加载。
而不是这个:
ContactAddress dbModel= new ContactAddress();
使用:
ContactAddress dbModel = context.ContactAddresses.Create();
从那里您可以设置 FK 值,EF 应该处理未来的请求以检索导航属性,延迟加载数据。
我提倡的插入方案方法是使用导航属性而不是 FK 属性。在实体中,我通常会声明其中之一,但不会同时声明两者。对于“尽可能快”的操作,我将只映射 FK,对于更完整的视图,我将使用具有 FK 阴影属性的导航属性。这样做的主要原因是,如果您有导航属性和 FK 属性,那么您现在有两个关系的真实来源:
contactAddress.CountryId
// and
contactAddress.Country.CountryId
某些接受 ContractAddress 的代码可能会使用一个或另一个,您将获得不同的行为,具体取决于您设置的是 FK 还是导航 属性,另外还取决于导航 [=36] =] 是否已加载。在您尝试调用 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()
calls /w dedicated throws 可以很容易地成为 Single()
,其中会引发所讨论的实体集的 Expected 1, found 0
异常。这仅取决于您是要处理异常以向客户端提供一些反馈还是仅记录异常。
这避免了重新加载实体的需要。它还断言每个引用的 ID 在创建新行时本身都是有效的。您会得到一个完整的模型,可以使用了。加载实体似乎是不必要的成本,但如果您希望在调用后加载数据,那么这里完成的性能成本与发回时的延迟加载调用没有什么不同。您获得的好处是断言这些值是有效的,每个值都有一个有意义的“抛出”点,而不是在 SaveChanges
调用中一次。