Entity Framework core .Include() 问题

20

我一直在研究EF Core,但在include语句方面遇到了问题。对于这段代码,我得到了两个公司,这正是我所期望的。

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

这将返回

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

您可以看到有两家公司,所有相关属性都为null,因为我没有使用任何includes,这是我预期的结果。现在当我将方法更新为以下内容时:

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

    return c;
}

这是它返回的内容:
[
    {
        "id":1,
        "companyName":"new",
        "admins":[
            {
                "id":2,
                "forename":"User",
                "surname":"1",
                "companyId":1
            }
        ]
    }
]

它只返回一个公司,并且仅包括管理员。为什么没有包括另外两个公司及其员工?
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; }
}

你尝试过使用EF 6或更低版本的相同代码吗? - Roman
你的 EF 版本是什么? - Mafii
1
@Mafii,这是EF Core - EF 7,可以看标题。 - Roman
你能否在 SQL 上添加一个分析器,以查看生成的 SQL 数据库脚本? - marius
1
我和@JohnMorrison有同样的问题,虽然下面@MohammadAkbari的答案可以解决这个问题,但这意味着我会失去任何强类型到我的模型。MS文档(https://learn.microsoft.com/en-us/ef/core/querying/related-data)显示include应该工作,并且还展示了如何使用Entry API实现它,尽管出于各种原因,我不想在我的API中公开上下文。我将我的DbContext注入到我的存储库中。对于导致此问题的原因以及如何修复使Include()按预期操作的任何进一步想法? - Ashley Bye
显示剩余6条评论
5个回答

30

我不确定你是否看过这个问题的回答,但问题与JSON序列化器如何处理循环引用有关。在上面的链接中可以找到完整的细节和更多参考资料,并建议深入了解它们,但简而言之,将以下内容添加到startup.cs将配置序列化器以忽略循环引用:

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

9
我不相信这个问题与JSON有任何关系。 - Langdon
1
我不同意,尽管@JohnMorrison 可能只是使用JSON作为表达输出的便捷方式。这看起来确实像一个序列化器引用循环处理问题,但由于使用JSON来显示输出,它也可能看起来像那样。在投票之前,为什么不让OP说明情况呢? - Ashley Bye
实际上这是修复它的方法。非常感谢! - iamnicoj
+1 对于一个非常好的提示,它真的帮了我很多。现在,我尝试将处理设置为 Error 而不是 Ignore,只是想看看会发生什么,但没有任何异常被抛出。Postman 没有收到任何返回(甚至没有未填充子字段的列表)。但是没有报告任何 500 状态或其他内容。当我尝试在本地执行代码并断点时,VS 也没有弹出任何东西。对此有什么想法吗?(使用 Core 2/EF 7 for COre 2。) - DonkeyBanana
我同意@Langdon的观点。这显然是EF核心早期版本中的一个错误。后来的评论者没有相同的版本。 - Gert Arnold
虽然问题描述并不是你帮助解决的问题,但它实际上是我需要的解决方案。由于循环引用,include结果无法正确解析。谢谢! - Bingoabs

5

请确保您正在使用来自 "Microsoft.EntityFrameworkCore" 而不是 "System.Data.Entity" 的 Include 方法


一个救命的提示,我将一个项目升级到了 .net 6 和 ef core,但仍然存在一些对 System.Data.Entity 的引用,这导致了问题。 - VahidNaderi
我希望我能给这个点赞两次!有很多关于这个话题的其他问题和答案,但这是第一个提到这个问题并且这就是我所需要的。 - David

3

EF Core 目前还不支持惰性加载。请参阅此处了解详情。

作为替代方案,您可以使用急切加载。

请阅读这篇文章

以下是我创建的扩展方法,用于实现急切加载。

扩展方法:

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();
}

代码库调用:

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

使用方法:

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

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

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

依赖项:

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;
    }
}

如果有帮助到您,我会很高兴。请注意,这不是生产就绪的代码。


2
我测试了你的代码,发现这个问题也存在于我的测试中。在这篇文章链接中提出了使用数据投影的解决方案。对于你的问题,类似以下内容是可以解决的。
[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;
}

是的,这种方法有效。谢谢。那么你认为这是实体框架上懒加载的问题吗? - John Morrison
当使用include加载方法时,它是急加载,并且数据在第一个查询中加载。 - Mohammad Akbari

-1

我知道这是一个老问题,但它是谷歌的顶部结果,所以我在这里分享我的解决方案。对于Core 3.1 Web项目,有一个快速修复方法。 添加NuGet包Microsoft.EntityFrameworkCore.Proxies。 然后,在配置服务时,只需要在选项构建器中指定即可。文档: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"));
    });
}

现在你的懒加载应该和之前的EF版本一样工作。 如果你不是用于Web项目,你可以直接在你的DbContext中的OnConfiguring方法里完成它。
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
    optionsBuilder.UseLazyLoadingProxies();
}

我的EF相关内容被保存在一个单独的类库中,这样我就可以通过多个公司应用程序重复使用它。因此,在特定应用程序不需要时不进行延迟加载的能力是有用的。因此,为了可重用性目的,我更喜欢传递构建选项。但两者都可以工作。


这显然是 EF Core 早期版本中的一个 bug。它只是没有返回所有公司,那么懒加载会如何改变任何事情呢?此外,在序列化实体时,惰性加载是你最不想发生的事情。 - Gert Arnold

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接