实体框架在插入后填充相关记录



我的一个实体如下。有几个相关的表(大陆、县、国家),其中外键插入数据库表

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-increrement主键但在重新编辑的对象上缺少大陆国家县的相关信息

我又创建了一个方法

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专用抛出可以很容易地成为Single(),其中将引发所讨论实体集的Expected 1, found 0异常。这取决于您是想处理异常以向客户端提供一些反馈,还是只记录异常。

这就避免了重新加载实体的需要。它还断言,在创建新行时,每个引用的ID本身都是有效的。你得到了一个完整的模型,准备好了。加载实体似乎是一种不必要的成本,但考虑到您希望在调用后加载数据,在这里完成的性能成本与发送回时的延迟加载调用没有什么不同。你得到的好处是断言这些值是有效的;投掷";在CCD_ 4呼叫处为每个点而不是为所有点一次。

最新更新