首页 > 解决方案 > 实体框架在插入后填充相关记录

问题描述

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

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;

}

这是在实体框架中处理相关记录的正确方法吗?是否可以避免在插入后编写额外的方法来获取相关的表信息

标签: c#entity-framework

解决方案


第一个问题是您希望在尚未构建 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调用时一次全部。


推荐阅读