entity-frameworkef-code-first-mapping

Entity Framework how to map composite entities?


I'm newbie in EF and i have such question: I have four Entities, which describe the elements of the address:

public partial class Region : BaseEntity
{
    private ICollection<RegionCity> _regionCities;

    public string Code { get; set; }
    public string Name { get; set; }
    public int Timezone { get; set; }

    public virtual ICollection<RegionCity> RegionCities
    {
        get
        {
            return _regionCities ?? (_regionCities = new List<RegionCity>());
        }
        protected set { _regionCities = value; }
    }
}

public partial class RegionCity : BaseEntity
{
    private ICollection<CityStreet> _cityStreets;

    public int RegionId { get; set; }
    public string CityName { get; set; }

    public virtual Region Region { get; set; }

    public virtual ICollection<CityStreet> CityStreets
    {
        get { return _cityStreets ?? (_cityStreets = new List<CityStreet>()); }
        protected set { _cityStreets = value; }
    }
}

public partial class CityStreet : BaseEntity
{
    public int CityId { get; set; }
    public string StreetName { get; set; }

    public virtual RegionCity City { get; set; }
}

public partial class FullAddress : BaseEntity
{
    public int CityStreetId { get; set; }
    public string HouseNum { get; set; }
    public string OfficeNum { get; set; }

    public virtual CityStreet Street { get; set; }
}

And I have mapping classes:

public class RegionMap : EntityTypeConfiguration<Region>
{
    public RegionMap()
    {
        this.ToTable("Region");
        this.HasKey(r => r.Id);
        this.Property(r => r.Name).HasMaxLength(64);
    }
}

public class RegionCityMap : EntityTypeConfiguration<RegionCity>
{
    public RegionCityMap()
    {
        this.ToTable("RegionCity");
        this.HasKey(rc => rc.Id);

        this.HasRequired(rc => rc.Region)
            .WithMany(r => r.RegionCities)
            .HasForeignKey(rc => rc.RegionId)
            .WillCascadeOnDelete(false);
    }
}

public class CityStreetMap : EntityTypeConfiguration<CityStreet>
{
    public CityStreetMap()
    {
        this.ToTable("CityStreet");
        this.HasKey(cs => cs.Id);

        this.HasRequired(cs => cs.City)
            .WithMany(c => c.CityStreets)
            .HasForeignKey(cs => cs.CityId)
            .WillCascadeOnDelete(false);
    }
}

public class FullAddressMap : EntityTypeConfiguration<FullAddress>
{
    public FullAddressMap()
    {
        this.ToTable("FullAddress");
        this.HasKey(fu => fu.Id);
        this.Property(fu => fu.HouseNum).HasMaxLength(16);

        this.HasRequired(fu => fu.Street)
            .WithMany()
            .HasForeignKey(fu => fu.CityStreetId)
            .WillCascadeOnDelete(false);
    }
}

How i can write mapping class, if i will change class FullAddress as show follow, so that it will justifies its name:

public partial class FullAddress : BaseEntity
{
    public int CityStreetId { get; set; }
    public string HouseNum { get; set; }

    public virtual Region Region { get; set; }
    public virtual RegionCity City { get; set; }
    public virtual CityStreet Street { get; set; }
}

Can i to map RegionCity through CityStreet and then Region through RegionCity?


Solution

  • You can use NotMapped properties to traverse the hierarchy. Or you can declare extra Navigaion Properties and Foreign Keys that skip levels.

    If you change the key structure to use compound keys (which IMO is a best practice here anyway), you can do either one.

    eg

    public class BaseEntity
    {
    
    }
    
    public partial class Region : BaseEntity
    {
        public int RegionID { get; set; }
        public string Code { get; set; }
        public string Name { get; set; }
        public int Timezone { get; set; }
    
        public virtual ICollection<RegionCity> RegionCities { get; } = new HashSet<RegionCity>();
    }
    
    
    public partial class RegionCity : BaseEntity
    {
    
        [Key(), Column(Order=0)]
        public int RegionId { get; set; }
        [Key(), Column(Order = 1)]
        public int RegionCityId { get; set; }
        public string CityName { get; set; }
    
        [ForeignKey("RegionId")]
        public virtual Region Region { get; set; }
    
        public virtual ICollection<CityStreet> CityStreets { get; } = new HashSet<CityStreet>();
    
    }
    
    public partial class CityStreet : BaseEntity
    {
    
        [Key(), Column(Order = 0)]
        public int RegionId { get; set; }
    
        [Key(), Column(Order = 1)]
        public int RegionCityId { get; set; }
    
        [Key(), Column(Order = 3)]
        public int CityStreetId { get; set; }
    
        public string StreetName { get; set; }
    
        [ForeignKey("RegionId,RegionCityId")]
        public virtual RegionCity RegionCity { get; set; }
    
        public virtual ICollection<FullAddress> FullAddresses { get; } = new HashSet<FullAddress>();
    }
    
    public partial class FullAddress : BaseEntity
    {
    
        [Key(), Column(Order = 0)]
        public int RegionId { get; set; }
    
        [Key(), Column(Order = 1)]
        public int RegionCityId { get; set; }
    
        [Key(), Column(Order = 3)]
        public int CityStreetId { get; set; }
    
        [Key(), Column(Order = 4)]
        public int FullAddressId { get; set; }
        public string HouseNum { get; set; }
        public string OfficeNum { get; set; }
    
        [ForeignKey("RegionId,RegionCityId,CityStreetId")]
        public virtual CityStreet Street { get; set; }
    
        [NotMapped]
        public virtual RegionCity RegionCity 
        {
            get
            {
                return this.Street.RegionCity;
            }
            set
            {
                this.Street.RegionCity = value;
            }
        }
    
    
        [NotMapped]
        public virtual Region Region
        {
            get
            {
                return this.Street.RegionCity.Region;
            }
            set
            {
                this.Street.RegionCity.Region = value;
            }
        }
    
    }