20

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; }
}
RAM
  • 2,257
  • 2
  • 19
  • 41
John Morrison
  • 203
  • 1
  • 2
  • 6
  • Have you tried the same code with EF 6 or lower? – Roman Jul 24 '16 at 10:47
  • Whats your EF version? – Mafii Jul 24 '16 at 10:51
  • 1
    @Mafii, it is EF Core - EF 7, see the title – Roman Jul 24 '16 at 10:52
  • could you include your class definition and the mapping? what was expected? – DevilSuichiro Jul 24 '16 at 11:37
  • could you add a profiler on sql to see the sql database script generated? – marius Jul 24 '16 at 13:34
  • I test your code, no problem exist! how serialize result. Do you have to check result before serialization – Mohammad Akbari Jul 25 '16 at 04:08
  • @MohammadAkbari ok so it i used the debugger and discovered that the include method is working. I was getting 2 companies all along. But i cannot work out why when i call it from my api i only get one company in the json? ` [HttpGet] public IEnumerable Get() { var c = new CompanyHelper().GetAllCompanies(db); return c; }` – John Morrison Jul 25 '16 at 17:50
  • @DevilSuichiro here is my class structure. – John Morrison Jul 25 '16 at 18:10
  • are you sure the DbContext is correct in your new CompanyHelper? Is any other context open with changed entries in its DbSet? where does your context come from? – DevilSuichiro Jul 26 '16 at 05:49
  • 1
    I have the same problem as @JohnMorrison and whilst the accepted answer by @MohammadAkbari below does work around the problem it means I lose any kind of strong typing to my models. The MS docs (https://learn.microsoft.com/en-us/ef/core/querying/related-data) show that include should work, and also how to achieve it with the Entry API although for various reasons I'd rather not expose the context in my API. I am injecting my `DbContext` into my repositories. Any further ideas on what causes this problem and how to fix it so that `Include()` operates as expected? – Ashley Bye Dec 30 '16 at 08:54
  • @AshleyBye, hi. Was you able to fix the issue? – Sasuke Uchiha Sep 17 '20 at 09:03

5 Answers5

30

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;
    });
Community
  • 1
  • 1
Ashley Bye
  • 1,752
  • 2
  • 23
  • 40
  • 9
    I don't believe this problem has anything to do with JSON. – Langdon Jan 03 '17 at 13:47
  • 1
    I disagree, although it's entirely possible that @JohnMorrison only used JSON as a convenient way to express the output. It certainly looks like a serializer reference loop handling issue, but it could very well look that way due to the use of JSON to show output. Why not let the OP state whether that's the case before downvoting? – Ashley Bye Jan 03 '17 at 18:30
  • Accutaly this is the way to fix it. Thanks a lot! – iamnicoj Jan 30 '17 at 04:09
  • +1 for a very good hint that really helped me. Now, I've tried setting the handling to *Error* instead of *Ignore* just to see what happens but there's no exception being thrown. The postman gets nothing in return (no list with unpopulated subfields even). But there's no 500-status or anything reported back. And VS doesn't pops anything neither, when I try to execute the code locally and breakpoint it. Thought on that? (Using Core 2/EF 7 for COre 2.) – DonkeyBanana May 05 '18 at 22:55
  • I concur with @Langdon. This was clearly a bug in an early EF core version. Later commenters didn't have the same version. – Gert Arnold Dec 15 '20 at 08:42
  • Although the problem description is not the issue you help to solve, but it is actually the solution I need, The `include` result can't be parse correctly due to the circular references. Thanks! – Bingoabs Mar 26 '21 at 05:34
5

Make sure you are using Include from "Microsoft.EntityFrameworkCore" And Not from "System.Data.Entity"

cigien
  • 57,834
  • 11
  • 73
  • 112
Navjyot
  • 118
  • 1
  • 11
  • A life saver hint, I upgraded a project to .net 6 and ef core and there still were some references to `System.Data.Entity` which caused the issue. – VahidNaderi Sep 26 '22 at 10:37
  • I wish I could upvote this twice! Lots of other Q's and A's on this topic, but this is the first to mention this issue and it's all I needed. – David Apr 29 '23 at 00:01
3

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.

devklick
  • 2,000
  • 3
  • 30
  • 47
Venkatesh
  • 269
  • 1
  • 11
2

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;
}
Mohammad Akbari
  • 4,486
  • 6
  • 43
  • 74
-1

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://learn.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.

Mike
  • 1,525
  • 1
  • 14
  • 11
  • This was clearly a bug in an earlier version of EF core. It just didn't return all companies, so how would lazy loading change anything? Also, when serializing entities, lazy loading is the last thing you want to happen. – Gert Arnold Dec 15 '20 at 08:40