简体   繁体   中英

Entity Framework core .Include() issue

Been having a play about with ef core and been having an issue with the include statement. For this code I get 2 companies which is what i expected.

public IEnumerable<Company> GetAllCompanies(HsDbContext db)
{
    var c = db.Company;
    return c;
}

This returns

[
    {
        "id":1,
        "companyName":"new",
        "admins":null,
        "employees":null,
        "courses":null
    },
    {
        "id":2,
        "companyName":"Test Company",
        "admins":null,
        "employees":null,
        "courses":null
    }
]

As you can see there are 2 companies and all related properties are null as i havnt used any includes, which is what i expected. Now when I update the method to this:

public IEnumerable<Company> GetAllCompanies(HsDbContext db)
{
    var c = db.Company
        .Include(t => t.Employees)
        .Include(t => t.Admins)
        .ToList();

    return c;
}

this is what it returns:

[
    {
        "id":1,
        "companyName":"new",
        "admins":[
            {
                "id":2,
                "forename":"User",
                "surname":"1",
                "companyId":1
            }
        ]
    }
]

It only returns one company and only includes the admins. Why did it not include the 2 companies and their employees?

public class Company
{
    public int Id { get; set; }
    public string CompanyName { get; set; }
    public List<Admin> Admins { get; set; }
    public List<Employee> Employees { get; set; }
    public List<Course> Courses { get; set; }

    public string GetFullName()
    {
        return CompanyName;
    }
}

public class Employee
{
    public int Id { get; set; }
    public string Forename { get; set; }
    public string Surname { get; set; }
    public int CompanyId { get; set; }
    [ForeignKey("CompanyId")]
    public Company company { get; set; }

    public ICollection<EmployeeCourse> Employeecourses { get; set; }
}

public class Admin
{
    public int Id { get; set; }
    public string Forename { get; set; }
    public string Surname { get; set; }
    public int CompanyId { get; set; }
    [ForeignKey("CompanyId")]
    public Company Company { get; set; }
}

I'm not sure if you've seen the accepted answer to this question , but the problem is to do with how the JSON Serializer deals with circular references. Full details and links to more references can be found at the above link, and I'd suggest digging into those, but in short, adding the following to startup.cs will configure the serializer to ignore circular references:

services.AddMvc()
    .AddJsonOptions(options => {
        options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
    });

Lazy loading is not yet possible with EF Core. Refer here .

Alternatively you can use eager loading.

Read this article

Below is the extension method i have created to achieve the eager loading.

Extension Method:

public static IQueryable<TEntity> IncludeMultiple<TEntity, TProperty>(
    this IQueryable<TEntity> source,
    List<Expression<Func<TEntity, TProperty>>> navigationPropertyPath) where TEntity : class
{
    foreach (var navExpression in navigationPropertyPath)
    {
        source= source.Include(navExpression);
    }
    return source.AsQueryable();
}

Repository Call:

public async Task<TEntity> FindOne(ISpecification<TEntity> spec)
{
    return await Task.Run(() => Context.Set<TEntity>().AsQueryable().IncludeMultiple(spec.IncludeExpression()).Where(spec.IsSatisfiedBy).FirstOrDefault());
}

Usage:

List<object> nestedObjects = new List<object> {new Rules()};

ISpecification<Blog> blogSpec = new BlogSpec(blogId, nestedObjects); 

var challenge = await this._blogRepository.FindOne(blogSpec);

Dependencies:

public class BlogSpec : SpecificationBase<Blog>
{
    readonly int _blogId;
    private readonly List<object> _nestedObjects;

    public ChallengeSpec(int blogid, List<object> nestedObjects)
    {
        this._blogId = blogid;
        _nestedObjects = nestedObjects;
    }

    public override Expression<Func<Challenge, bool>> SpecExpression
    {
        get { return blogSpec => blogSpec.Id == this._blogId; }
    }

    public override List<Expression<Func<Blog, object>>> IncludeExpression()
    {
        List<Expression<Func<Blog, object>>> tobeIncluded = new List<Expression<Func<Blog, object>>>();
        if (_nestedObjects != null)
            foreach (var nestedObject in _nestedObjects)
            {
                if (nestedObject is Rules)
                {
                    Expression<Func<Blog, object>> expr = blog => blog.Rules;
                    tobeIncluded.Add(expr);
                }
                
            }

        return tobeIncluded;
    }
}

Will be glad if it helps. Please note this is not a production ready code.

I test your code, this problem exist in my test. in this post LINK Proposed that use data projection. for your problem Something like the following, is work.

[HttpGet]
public dynamic Get()
{
    var dbContext = new ApplicationContext();

    var result = dbContext.Companies
        .Select(e => new { e.CompanyName, e.Id, e.Employees, e.Admins })
        .ToList();

    return result;
}

I know this is an old issue, but its the top result in google, so im putting my solution i I found here. For a Core 3.1 web project there is a quick fix. Add nuget package Microsoft.EntityFrameworkCore.Proxies . Then you simply just need to specify in your options builder when configuring your services. Docs: https://docs.microsoft.com/en-us/ef/core/querying/related-data

public void ConfigureServices(IServiceCollection services) {    
    services.AddDbContextPool<YourDbContext>(options => {
        options.UseLazyLoadingProxies();
        options.UseSqlServer(this.Configuration.GetConnectionString("MyCon"));
    });
}

Now your lazy loading should work as it did in previous EF versions. If you not using it for a web project, you can do it right inside of the OnConfiguringMethod inside of your DbContext itself.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
    optionsBuilder.UseLazyLoadingProxies();
}

My EF stuff is kept in a separate class library so i can re-use it through multiple company applications. So having the ability to not lazy load when not needed for a particular application is useful. So i prefer passing in the build options, for reuse-ability purposes. But both will work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM