Entity Framework reference property

空扰寡人 提交于 2021-02-10 12:43:12

问题


I am working with Entity Framework 6.

I am working with 2 classes:

public partial class StateProvince
{

     public StateProvince()
    {
        Addresses = new HashSet<Address>();
    }

    public int StateProvinceID { get; set; }

    public string StateProvinceCode { get; set; }

    public string CountryRegionCode { get; set; }

    public bool IsOnlyStateProvinceFlag { get; set; }

    public string Name { get; set; }

    public int TerritoryID { get; set; }

    public Guid rowguid { get; set; }

    public DateTime ModifiedDate { get; set; }

    public ICollection<Address> Addresses { get; set; }

    public CountryRegion CountryRegion { get; set; }

}

public partial class CountryRegion
{
    public CountryRegion()
    {
        StateProvinces = new HashSet<StateProvinceTlb>();
    }

    public string CountryRegionCode { get; set; }

    public string Name { get; set; }

    public DateTime ModifiedDate { get; set; }

    public virtual ICollection<StateProvince> StateProvinces { get; set;}
}

I would like to be able to run a query that would return a list of StateProvince, but also include the name from CountryRegion. The idea is that on an edit screen, the user will select, or edit the CountryRegionCode, but the Name will display beside it in a none editable field just for reference.

I tried adding property as a none mapped field to StateProvince and referencing the property on CountryRegion, like below:

    [NotMapped]
    public string CountryName
    {
        get{ return CountryRegion.Name;}
    }

but the problem with this is the CountryRegion has to be loaded in order for that to work, and my goal is to not have to load the entire CountryRegion object.

I've also tried to set it in my query like this:

            List<StateProvince> statP = context.StateProvinces.Select(s => new StateProvince() {s, CountryName = context.CountryRegions.Where(x => x.CountryRegionCode == s.CountryRegionCode).Select(x => x.Name).FirstOrDefault() }).ToList();

but this doesn't work because the object returned is comprised of a StateProvince object and a separate CountryName property.

I've even tried setting each of the fields individually like:

            List<StateProvince> statP = context.StateProvinces.Select(s => new StateProvince() { Name = s.Name, TerritoryID = s.TerritoryID, rowguid = s.rowguid, ModifiedDate = s.ModifiedDate, Addresses=s.Addresses, CountryRegion=s.CountryRegion, CountryName = context.CountryRegions.Where(x => x.CountryRegionCode == s.CountryRegionCode).Select(x => x.Name).FirstOrDefault() }).ToList();

But this again causes the entire CountryRegion object to load, and if I leave those properties out, an exception is thrown. Also, for larger entities this will be hard to maintain.

The TLDR; version of this: Is there a way in Entity Frameworks to have a class mapped to one table, but have a property on that class that references a property on another table without having to retrieve everything on the child table?

I've searched and searched and can't really find much covering this specific kinda of scenario. I'm fairly new to Entity Framework, and I feel like I'm missing something obvious. Any help would be greatly appreciated!!

SOLUTION

This is what I've come up with to solve the issue.

First, I split the CountryRegion table into two separate classes

public partial class CountryRegionHeader
{
    public CountryRegionHeader()
    {
       StateProvinces = new HashSet<StateProvinceTlb>();
    }

    public string CountryRegionCode { get; set; }

    public string Name { get; set; }
}

public partial class CountryRegionDetail
{
    public CountryRegionDetail()
    {
        StateProvinces = new HashSet<StateProvinceTlb>();
    }

    public string CountryRegionCode { get; set; }


    public DateTime ModifiedDate { get; set; }

    public virtual ICollection<StateProvince> StateProvinces { get; set;}

    public virtual CountryRegion CountryRegion {get;set;}

}

I then add the properties for the new classes to my StateProvince class

[ForeignKey("CountryRegionCode)]
public CountryRegionHeader CountryRegionHeader {get;set;}

[ForeignKey("CountryRegionCode)]
public CountryRegionDetail CountryRegionDetail {get;set;}

I then add the DBSets to my model context for CountryRegionHeader and CountryRegionDetail and tie them together using the fluent API in the OnModelCreating method

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<CountryRegionHeader>()
        .HasRequired(e => e.CountryRegionDetail)
        .WithRequiredPrincipal();
}

In addition to this, I've created another class just called CountryRegion which has all of the properties from both the Header and Detail as well as the Header and Detail object themselves. The properties actually point back to the header and detail. This really isn't necessary, but it makes the code cleaner and easier to use. Also, when sending the data down to a web client I can just serialize the CountryRegion object and exclude the Header and Detail object. So basically my CountryRegion class looks like this:

public Class CountryRegion
{
    public CountryRegionHeader Header;
    public CountryRegionDetail Detail;

    public CountryRegionCode
    {
       //need some special logic here for the key since both Header or
       //Detail share the primary key and both may not be loaded
       get
       {
           if (Header != null)
               return Header.CountryRegionCode;
           else
               return Detail.CountryRegionCode; 
        }
        set
        {
            if (Header != null) Header.CountryRegionCode = value;
            if (Detail != null) Detail.CountryRegionCode = value;
        }
    }

    public string Name
    {
        get
        {
            return Header.Name;
        }
        set
        {
            Header.Name = value;
        }

    }

    public DateTime ModifiedDate
    {
        get
        {
            return Detail.ModifiedDate ;
        }
        set
        {
            Detail.ModifiedDate = value;
        }

    }

    public virtual ICollection<StateProvince> StateProvinces
    {
        get
        {
            return Detail.StateProvinces ;
        }
        set
        {
            Detail.StateProvinces = value;
        }

    }

}

So, Now When I query I can do something like:

List<StateProvince> query =       db.StateProvince.Include(o=>o.CountryRegionHeader).ToList();

And I only retrieve the data I need without retrieving the entire CountryRegion record

Also, If I'm working with just the CountryRegion, I can query like this:

List<CountryRegion> query = (from a in db.CountryRegionHeader join b in    db.CountryRegionDetail on a.CountryRegionCode equals b.CountryRegionCode select new Employee(){Header = a, Detail = b}).ToList(); 

回答1:


SOLUTION

This is what I've come up with to solve the issue.

First, I split the CountryRegion table into two separate classes

public partial class CountryRegionHeader
{
    public CountryRegionHeader()
    {
       StateProvinces = new HashSet<StateProvinceTlb>();
    }

    public string CountryRegionCode { get; set; }

    public string Name { get; set; }
}

public partial class CountryRegionDetail
{
    public CountryRegionDetail()
    {
        StateProvinces = new HashSet<StateProvinceTlb>();
    }

    public string CountryRegionCode { get; set; }


    public DateTime ModifiedDate { get; set; }

    public virtual ICollection<StateProvince> StateProvinces { get; set;}

    pubic virtual CountryRegion CountryRegion {get;set;}

}

I then add the properties for the new classes to my StateProvince class

[ForeignKey("CountryRegionCode)]
public CountryRegionHeader CountryRegionHeader {get;set;}

[ForeignKey("CountryRegionCode)]
public CountryRegionDetail CountryRegionDetail {get;set;}

I then add the DBSets to my model context for CountryRegionHeader and CountryRegionDetail and tie them together using the fluent API in the OnModelCreating method

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<CountryRegionHeader>()
        .HasRequired(e => e.CountryRegionDetail)
        .WithRequiredPrincipal();
}

In addition to this, I've created another class just called CountryRegion which has all of the properties from both the Header and Detail as well as the Header and Detail object themselves. The properties actually point back to the header and detail. This really isn't necessary, but it makes the code cleaner and easier to use. Also, when sending the data down to a web client I can just serialize the CountryRegion object and exclude the Header and Detail object. So basically my CountryRegion class looks like this:

public Class CountryRegion
{
    public CountryRegionHeader Header;
    public CountryRegionDetail Detail;

    public CountryRegionCode
    {
       //need some special logic here for the key since both Header or
       //Detail share the primary key and both may not be loaded
       get
       {
           if (Header != null)
               return Header.CountryRegionCode;
           else
               return Detail.CountryRegionCode; 
       }
       set
       {
           if (Header != null) Header.CountryRegionCode = value;
           if (Detail != null) Detail.CountryRegionCode = value;
       }
   }

   public string Name
   {
       get
       {
           return Header.Name;
       }
       set
       {
           Header.Name = value;
       }

   }

   public DateTime ModifiedDate
   {
       get
       {
            return Detail.ModifiedDate ;
       }
       set
       {
            Detail.ModifiedDate = value;
       }

   }

   public virtual ICollection<StateProvince> StateProvinces
   {
      get
      {
           return Detail.StateProvinces ;
      }
      set
      {
           Detail.StateProvinces = value;
      }

   }

}

So, Now When I query I can do something like:

List<StateProvince> query =       db.StateProvince.Include(o=>o.CountryRegionHeader).ToList();

And I only retrieve the data I need without retrieving the entire CountryRegion record

Also, If I'm working with just the CountryRegion, I can query like this:

List<CountryRegion> query = (from a in db.CountryRegionHeader join b in    db.CountryRegionDetail on a.CountryRegionCode equals b.CountryRegionCode select new Employee(){Header = a, Detail = b}).ToList(); 



回答2:


One way to achieve this is to add a computed column on your database that gives you the CountryRegion name on your StateProvince.

With this all you have to do is to request the StateProvince table and your database server will give you the relevant associated CountryRegion name.

Edit

After your comment I had another idea.

Create a class for your usage :

public class StateProvinceEdition : StateProvince
{
    public string CountryRegionName { get; set; }

    public StateProvinceEdition(StateProvince stateProvince, string countryRegionName)
    {
        this.StateProvinceID = stateProvince.StateProvinceID;
        // And so on ...

        // Adding country region name
        this.CountryRegionName = countryRegionName;
    }
}

Then you need to project your query result into your object using Linq :

 List<StateProvinceEdition> statP = context.StateProvinces.Select(s => new StateProvince(s, s.CountryRegion.Name)).ToList();

I do not have a database to test it upon so if EF throws an exception regarding the constructor, try to switch the Select and ToList statements to first load objects from the database and then project the result in the custom class.

Second edit

Similar to the first idea, you can create a view in your database for your purpose and then use Entity Framework to query this view. With this solution you do not need to change your database other than adding a view.



来源:https://stackoverflow.com/questions/35984325/entity-framework-reference-property

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!