为相关实体返回空值

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亲戚,也不用担心懒加载命中。清洁、高效且面向未来。