为相关实体返回空值
Returning Null for the Related Entity
我正在使用 EF 核心 2.1.14
这是我通过脚手架创建的 DbContext 的 Class:
public partial class AgriDbContext : DbContext
{
public AgriDbContext()
{
}
public AgriDbContext(string connectionString)
: base(GetOptions(connectionString))
{
}
private static DbContextOptions GetOptions(string connectionString)
{
return SqlServerDbContextOptionsExtensions.UseSqlServer(new DbContextOptionsBuilder(), connectionString).Options;
}
public virtual DbSet<Advertisement> Advertisements { get; set; }
public virtual DbSet<AgroItem> AgroItems { get; set; }
public virtual DbSet<BuyerAddsDifferentAdsToFav> BuyerAddsDifferentAdsToFavs { get; set; }
public virtual DbSet<BuyersAddAgroItemToInterest> BuyersAddAgroItemToInterests { get; set; }
public virtual DbSet<Category> Categories { get; set; }
public virtual DbSet<City> Cities { get; set; }
public virtual DbSet<SellersFavoritesBuyer> SellersFavoritesBuyers { get; set; }
public virtual DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasAnnotation("ProductVersion", "2.2.0-rtm-35687");
modelBuilder.Entity<Advertisement>(entity =>
{
entity.Property(e => e.Picture).IsUnicode(false);
entity.HasOne(d => d.City)
.WithMany(p => p.Advertisements)
.HasForeignKey(d => d.CityId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("ADVERTISEMENTS_INCLUDE_A_CITY");
entity.HasOne(d => d.Item)
.WithMany(p => p.Advertisements)
.HasForeignKey(d => d.ItemId)
.HasConstraintName("AN_ADVERTISEMENT_IS_RELATED_TO_AN_ITEM");
entity.HasOne(d => d.Seller)
.WithMany(p => p.Advertisements)
.HasForeignKey(d => d.SellerId)
.HasConstraintName("USERS_POST_ADVERTISEMENTS");
});
modelBuilder.Entity<AgroItem>(entity =>
{
entity.Property(e => e.Name).IsUnicode(false);
entity.Property(e => e.Uname).IsUnicode(false);
entity.HasOne(d => d.Category)
.WithMany(p => p.AgroItems)
.HasForeignKey(d => d.CategoryId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("AGRO_ITEMS_BELONG_TO_A_CATEGORY");
});
modelBuilder.Entity<BuyerAddsDifferentAdsToFav>(entity =>
{
entity.HasKey(e => new { e.BuyerId, e.AdId });
entity.HasOne(d => d.Ad)
.WithMany(p => p.BuyerAddsDifferentAdsToFavs)
.HasForeignKey(d => d.AdId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_BUYER_ADDS_DIFFERENT_ADS_TO_FAV_ADVERTISEMENTS");
entity.HasOne(d => d.Buyer)
.WithMany(p => p.BuyerAddsDifferentAdsToFavs)
.HasForeignKey(d => d.BuyerId)
.HasConstraintName("FK_BUYER_ADDS_DIFFERENT_ADS_TO_FAV_USERS");
});
modelBuilder.Entity<BuyersAddAgroItemToInterest>(entity =>
{
entity.HasKey(e => new { e.BuyerId, e.ItemId });
entity.HasOne(d => d.Buyer)
.WithMany(p => p.BuyersAddAgroItemToInterests)
.HasForeignKey(d => d.BuyerId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_BUYERS_ADD_AGRO_ITEM_TO_INTEREST_USERS");
entity.HasOne(d => d.Item)
.WithMany(p => p.BuyersAddAgroItemToInterests)
.HasForeignKey(d => d.ItemId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_BUYERS_ADD_AGRO_ITEM_TO_INTEREST_AGRO_ITEMS");
});
modelBuilder.Entity<Category>(entity =>
{
entity.Property(e => e.Name).IsUnicode(false);
entity.Property(e => e.Uname).IsUnicode(false);
});
modelBuilder.Entity<City>(entity =>
{
entity.HasIndex(e => e.Id)
.HasName("UNIQUE_LOCATION")
.IsUnique();
entity.Property(e => e.Name).IsUnicode(false);
});
modelBuilder.Entity<SellersFavoritesBuyer>(entity =>
{
entity.HasKey(e => new { e.SellerId, e.BuyerId });
entity.HasOne(d => d.Buyer)
.WithMany(p => p.SellersFavoritesBuyerBuyers)
.HasForeignKey(d => d.BuyerId)
.HasConstraintName("FK_SELLERS_FAVORITES_BUYERS_USERS1");
entity.HasOne(d => d.Seller)
.WithMany(p => p.SellersFavoritesBuyerSellers)
.HasForeignKey(d => d.SellerId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_SELLERS_FAVORITES_BUYERS_USERS");
});
modelBuilder.Entity<User>(entity =>
{
entity.HasIndex(e => new { e.CcompanyCode, e.CcountryCode, e.Cphone })
.HasName("UNIQUE_CONTACT")
.IsUnique();
entity.Property(e => e.Address).IsUnicode(false);
entity.Property(e => e.Fname).IsUnicode(false);
entity.Property(e => e.Lname).IsUnicode(false);
entity.HasOne(d => d.City)
.WithMany(p => p.Users)
.HasForeignKey(d => d.CityId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_USERS_CITIES");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
在上面的代码中,CityId 作为外键正确关联,并且在用户 class 中也有一个 属性 用于城市。但是每当我得到一个用户时,它 returns 在城市 属性 中为空,但它在数据库中 returns CityId。
下面是我的实体模型Class
[Table("USERS")]
public partial class User
{
public User()
{
Advertisements = new HashSet<Advertisement>();
BuyerAddsDifferentAdsToFavs = new HashSet<BuyerAddsDifferentAdsToFav>();
BuyersAddAgroItemToInterests = new HashSet<BuyersAddAgroItemToInterest>();
SellersFavoritesBuyerBuyers = new HashSet<SellersFavoritesBuyer>();
SellersFavoritesBuyerSellers = new HashSet<SellersFavoritesBuyer>();
}
public long Id { get; set; }
[Required]
[Column("FName")]
public string Fname { get; set; }
[Required]
[Column("LName")]
public string Lname { get; set; }
[Required]
[Column("CCompanyCode")]
[StringLength(3)]
public string CcompanyCode { get; set; }
[Required]
[Column("CCountryCode")]
[StringLength(3)]
public string CcountryCode { get; set; }
[Required]
[Column("CPhone")]
[StringLength(7)]
public string Cphone { get; set; }
[Required]
public string Address { get; set; }
[Column("GLat", TypeName = "decimal(10, 8)")]
public decimal? Glat { get; set; }
[Column("GLng", TypeName = "decimal(11, 8)")]
public decimal? Glng { get; set; }
public bool BuyerFlag { get; set; }
public bool SellerFlag { get; set; }
public short CityId { get; set; }
[ForeignKey("CityId")]
[InverseProperty("Users")]
public virtual City City { get; set; }
[InverseProperty("Seller")]
public virtual ICollection<Advertisement> Advertisements { get; set; }
[InverseProperty("Buyer")]
public virtual ICollection<BuyerAddsDifferentAdsToFav> BuyerAddsDifferentAdsToFavs { get; set; }
[InverseProperty("Buyer")]
public virtual ICollection<BuyersAddAgroItemToInterest> BuyersAddAgroItemToInterests { get; set; }
[InverseProperty("Buyer")]
public virtual ICollection<SellersFavoritesBuyer> SellersFavoritesBuyerBuyers { get; set; }
[InverseProperty("Seller")]
public virtual ICollection<SellersFavoritesBuyer> SellersFavoritesBuyerSellers { get; set; }
}
我正在使用这种存储库方法来获取用户
public EFarmer.Models.User GetUser(ContactNumberFormat contact)
{
var user = users
.Where(x => x.CcountryCode == contact.CountryCode
&& x.CcompanyCode == contact.CompanyCode
&& x.Cphone == contact.PhoneNumber).First() ?? null;
return (user != null) ? new EFarmer.Models.User
{
Address = user.Address,
City = EFarmer.Models.City.Convert(user.City),
ContactNumber = new ContactNumberFormat(user.CcountryCode, user.CcompanyCode, user.Cphone),
IsBuyer = user.BuyerFlag,
IsSeller = user.SellerFlag,
Location = new GeoLocation { Latitude = user.Glat, Longitude = user.Glng },
Name = new NameFormat { FirstName = user.Fname, LastName = user.Lname },
Id = user.Id
} : null;
}
这里是将实体模型转换为我的业务模型的转换方法,它给出了空引用异常,因为实体模型中的 City 为 null
public static City Convert(EFarmerPkModelLibrary.Entities.City city)
{
return new City
{
GeoLocation = new GeoLocation { Latitude = city.Glat, Longitude = city.Glng },
Id = city.Id,
Name = city.Name
};
}
从您的示例和标记 (EF Core 2.1) 中可以看出,问题可能是延迟加载尚未启用。检查您是否已添加 EF Core 代理的依赖项并将 optionsBuilder.UseLazyLoadingProxies();
添加到 DbContext OnModelConfiguring
覆盖。
不过,建议不要推出自己的映射器,例如静态 Convert 方法,而是寻求利用现有的映射器,例如 AutoMapper。 Automapper 与 EF 集成的一个关键功能是投影 (ProjectTo
)。这可以将您的 DTOs/ViewModels 构建为 Linq 表达式的一部分,该表达式被馈送到数据库,从而产生非常非常高效的查询,并且不会像延迟加载这样的事情出现多次点击。
使用延迟加载,您将有 1 次查询命中以获取用户的所有字段,然后有 1 次查询命中以获取您所在城市的所有字段,加上每个其他延迟加载调用 1 次命中。如果您正在做类似获取用户列表的操作,您将有 1 次点击来获取用户列表,但随后每个用户的每个城市都有 1 次点击!
使用预先加载,您将有 1 次查询命中以获取您的用户及其相关城市的所有字段,这是一个较低的性能命中。但是,它仍在获取 all 字段,无论您的 DTO/ViewModel 是否需要它们。还存在忘记在查询中显式预加载相关数据的风险,尤其是在扩展实体以添加新关系时,这最终会导致延迟加载性能损失或问题。
使用 Automapper 的 ProjectTo
,您将有 1 个查询命中以仅获取来自用户、城市和 DTO/ViewModel 请求的任何其他相关实体的字段。不用记得给Include
亲戚,也不用担心懒加载命中。清洁、高效且面向未来。
我正在使用 EF 核心 2.1.14 这是我通过脚手架创建的 DbContext 的 Class:
public partial class AgriDbContext : DbContext
{
public AgriDbContext()
{
}
public AgriDbContext(string connectionString)
: base(GetOptions(connectionString))
{
}
private static DbContextOptions GetOptions(string connectionString)
{
return SqlServerDbContextOptionsExtensions.UseSqlServer(new DbContextOptionsBuilder(), connectionString).Options;
}
public virtual DbSet<Advertisement> Advertisements { get; set; }
public virtual DbSet<AgroItem> AgroItems { get; set; }
public virtual DbSet<BuyerAddsDifferentAdsToFav> BuyerAddsDifferentAdsToFavs { get; set; }
public virtual DbSet<BuyersAddAgroItemToInterest> BuyersAddAgroItemToInterests { get; set; }
public virtual DbSet<Category> Categories { get; set; }
public virtual DbSet<City> Cities { get; set; }
public virtual DbSet<SellersFavoritesBuyer> SellersFavoritesBuyers { get; set; }
public virtual DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasAnnotation("ProductVersion", "2.2.0-rtm-35687");
modelBuilder.Entity<Advertisement>(entity =>
{
entity.Property(e => e.Picture).IsUnicode(false);
entity.HasOne(d => d.City)
.WithMany(p => p.Advertisements)
.HasForeignKey(d => d.CityId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("ADVERTISEMENTS_INCLUDE_A_CITY");
entity.HasOne(d => d.Item)
.WithMany(p => p.Advertisements)
.HasForeignKey(d => d.ItemId)
.HasConstraintName("AN_ADVERTISEMENT_IS_RELATED_TO_AN_ITEM");
entity.HasOne(d => d.Seller)
.WithMany(p => p.Advertisements)
.HasForeignKey(d => d.SellerId)
.HasConstraintName("USERS_POST_ADVERTISEMENTS");
});
modelBuilder.Entity<AgroItem>(entity =>
{
entity.Property(e => e.Name).IsUnicode(false);
entity.Property(e => e.Uname).IsUnicode(false);
entity.HasOne(d => d.Category)
.WithMany(p => p.AgroItems)
.HasForeignKey(d => d.CategoryId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("AGRO_ITEMS_BELONG_TO_A_CATEGORY");
});
modelBuilder.Entity<BuyerAddsDifferentAdsToFav>(entity =>
{
entity.HasKey(e => new { e.BuyerId, e.AdId });
entity.HasOne(d => d.Ad)
.WithMany(p => p.BuyerAddsDifferentAdsToFavs)
.HasForeignKey(d => d.AdId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_BUYER_ADDS_DIFFERENT_ADS_TO_FAV_ADVERTISEMENTS");
entity.HasOne(d => d.Buyer)
.WithMany(p => p.BuyerAddsDifferentAdsToFavs)
.HasForeignKey(d => d.BuyerId)
.HasConstraintName("FK_BUYER_ADDS_DIFFERENT_ADS_TO_FAV_USERS");
});
modelBuilder.Entity<BuyersAddAgroItemToInterest>(entity =>
{
entity.HasKey(e => new { e.BuyerId, e.ItemId });
entity.HasOne(d => d.Buyer)
.WithMany(p => p.BuyersAddAgroItemToInterests)
.HasForeignKey(d => d.BuyerId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_BUYERS_ADD_AGRO_ITEM_TO_INTEREST_USERS");
entity.HasOne(d => d.Item)
.WithMany(p => p.BuyersAddAgroItemToInterests)
.HasForeignKey(d => d.ItemId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_BUYERS_ADD_AGRO_ITEM_TO_INTEREST_AGRO_ITEMS");
});
modelBuilder.Entity<Category>(entity =>
{
entity.Property(e => e.Name).IsUnicode(false);
entity.Property(e => e.Uname).IsUnicode(false);
});
modelBuilder.Entity<City>(entity =>
{
entity.HasIndex(e => e.Id)
.HasName("UNIQUE_LOCATION")
.IsUnique();
entity.Property(e => e.Name).IsUnicode(false);
});
modelBuilder.Entity<SellersFavoritesBuyer>(entity =>
{
entity.HasKey(e => new { e.SellerId, e.BuyerId });
entity.HasOne(d => d.Buyer)
.WithMany(p => p.SellersFavoritesBuyerBuyers)
.HasForeignKey(d => d.BuyerId)
.HasConstraintName("FK_SELLERS_FAVORITES_BUYERS_USERS1");
entity.HasOne(d => d.Seller)
.WithMany(p => p.SellersFavoritesBuyerSellers)
.HasForeignKey(d => d.SellerId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_SELLERS_FAVORITES_BUYERS_USERS");
});
modelBuilder.Entity<User>(entity =>
{
entity.HasIndex(e => new { e.CcompanyCode, e.CcountryCode, e.Cphone })
.HasName("UNIQUE_CONTACT")
.IsUnique();
entity.Property(e => e.Address).IsUnicode(false);
entity.Property(e => e.Fname).IsUnicode(false);
entity.Property(e => e.Lname).IsUnicode(false);
entity.HasOne(d => d.City)
.WithMany(p => p.Users)
.HasForeignKey(d => d.CityId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_USERS_CITIES");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
在上面的代码中,CityId 作为外键正确关联,并且在用户 class 中也有一个 属性 用于城市。但是每当我得到一个用户时,它 returns 在城市 属性 中为空,但它在数据库中 returns CityId。
下面是我的实体模型Class
[Table("USERS")]
public partial class User
{
public User()
{
Advertisements = new HashSet<Advertisement>();
BuyerAddsDifferentAdsToFavs = new HashSet<BuyerAddsDifferentAdsToFav>();
BuyersAddAgroItemToInterests = new HashSet<BuyersAddAgroItemToInterest>();
SellersFavoritesBuyerBuyers = new HashSet<SellersFavoritesBuyer>();
SellersFavoritesBuyerSellers = new HashSet<SellersFavoritesBuyer>();
}
public long Id { get; set; }
[Required]
[Column("FName")]
public string Fname { get; set; }
[Required]
[Column("LName")]
public string Lname { get; set; }
[Required]
[Column("CCompanyCode")]
[StringLength(3)]
public string CcompanyCode { get; set; }
[Required]
[Column("CCountryCode")]
[StringLength(3)]
public string CcountryCode { get; set; }
[Required]
[Column("CPhone")]
[StringLength(7)]
public string Cphone { get; set; }
[Required]
public string Address { get; set; }
[Column("GLat", TypeName = "decimal(10, 8)")]
public decimal? Glat { get; set; }
[Column("GLng", TypeName = "decimal(11, 8)")]
public decimal? Glng { get; set; }
public bool BuyerFlag { get; set; }
public bool SellerFlag { get; set; }
public short CityId { get; set; }
[ForeignKey("CityId")]
[InverseProperty("Users")]
public virtual City City { get; set; }
[InverseProperty("Seller")]
public virtual ICollection<Advertisement> Advertisements { get; set; }
[InverseProperty("Buyer")]
public virtual ICollection<BuyerAddsDifferentAdsToFav> BuyerAddsDifferentAdsToFavs { get; set; }
[InverseProperty("Buyer")]
public virtual ICollection<BuyersAddAgroItemToInterest> BuyersAddAgroItemToInterests { get; set; }
[InverseProperty("Buyer")]
public virtual ICollection<SellersFavoritesBuyer> SellersFavoritesBuyerBuyers { get; set; }
[InverseProperty("Seller")]
public virtual ICollection<SellersFavoritesBuyer> SellersFavoritesBuyerSellers { get; set; }
}
我正在使用这种存储库方法来获取用户
public EFarmer.Models.User GetUser(ContactNumberFormat contact)
{
var user = users
.Where(x => x.CcountryCode == contact.CountryCode
&& x.CcompanyCode == contact.CompanyCode
&& x.Cphone == contact.PhoneNumber).First() ?? null;
return (user != null) ? new EFarmer.Models.User
{
Address = user.Address,
City = EFarmer.Models.City.Convert(user.City),
ContactNumber = new ContactNumberFormat(user.CcountryCode, user.CcompanyCode, user.Cphone),
IsBuyer = user.BuyerFlag,
IsSeller = user.SellerFlag,
Location = new GeoLocation { Latitude = user.Glat, Longitude = user.Glng },
Name = new NameFormat { FirstName = user.Fname, LastName = user.Lname },
Id = user.Id
} : null;
}
这里是将实体模型转换为我的业务模型的转换方法,它给出了空引用异常,因为实体模型中的 City 为 null
public static City Convert(EFarmerPkModelLibrary.Entities.City city)
{
return new City
{
GeoLocation = new GeoLocation { Latitude = city.Glat, Longitude = city.Glng },
Id = city.Id,
Name = city.Name
};
}
从您的示例和标记 (EF Core 2.1) 中可以看出,问题可能是延迟加载尚未启用。检查您是否已添加 EF Core 代理的依赖项并将 optionsBuilder.UseLazyLoadingProxies();
添加到 DbContext OnModelConfiguring
覆盖。
不过,建议不要推出自己的映射器,例如静态 Convert 方法,而是寻求利用现有的映射器,例如 AutoMapper。 Automapper 与 EF 集成的一个关键功能是投影 (ProjectTo
)。这可以将您的 DTOs/ViewModels 构建为 Linq 表达式的一部分,该表达式被馈送到数据库,从而产生非常非常高效的查询,并且不会像延迟加载这样的事情出现多次点击。
使用延迟加载,您将有 1 次查询命中以获取用户的所有字段,然后有 1 次查询命中以获取您所在城市的所有字段,加上每个其他延迟加载调用 1 次命中。如果您正在做类似获取用户列表的操作,您将有 1 次点击来获取用户列表,但随后每个用户的每个城市都有 1 次点击!
使用预先加载,您将有 1 次查询命中以获取您的用户及其相关城市的所有字段,这是一个较低的性能命中。但是,它仍在获取 all 字段,无论您的 DTO/ViewModel 是否需要它们。还存在忘记在查询中显式预加载相关数据的风险,尤其是在扩展实体以添加新关系时,这最终会导致延迟加载性能损失或问题。
使用 Automapper 的 ProjectTo
,您将有 1 个查询命中以仅获取来自用户、城市和 DTO/ViewModel 请求的任何其他相关实体的字段。不用记得给Include
亲戚,也不用担心懒加载命中。清洁、高效且面向未来。