EF Core 处理相同的实体,但一个是空的,一个不是

Posted

技术标签:

【中文标题】EF Core 处理相同的实体,但一个是空的,一个不是【英文标题】:EF Core Treating Entities the Same But One Is Null and One Is Not 【发布时间】:2021-12-27 13:32:19 【问题描述】:

好的,大家好。我已经盯着这个看了好几个小时了,我觉得我快疯了。我知道这有点,很抱歉它太长了,我试图让它尽可能容易地流动,但是呃……让我把 tl;dr 放在前面。

tl;dr:对两个对象(EF Core 中的“实体”)进行相同的处理,但由于某种原因,它们的处理方式有所不同,这给了我一个非常烦人的错误。什么给了?

好的,首先我使用的是 Entity Framework 6.4.4、EntityFrameworkCore.Sqlite 和 EntityFrameworkCore.Tools 3.1.4、Visual Studio 16.10.0 和 .NET Core 3.1,但是,出于稍后您会看到的原因,我真的不认为这是相关的。我只是把它包括在内。

所以,这会有点,但我会尽量保持简单。我在访问数据时遇到问题,我不知道为什么。让我从我的模型开始。我将包括三个(并简化它们)

MyNamespace/Models/Project.cs

namespace MyNamespace.Models

    public class Project
    
        public int ID  get; set; 
        // Bunch of other stuff, doesn't matter

        public ICollection<Comment> Comments  get; set; 
        public ICollection<MyNamespace.Models.File> Files  get; set; 
        // Yes I'm intentionally using the full path here because this is where the confusion is
    

MyNamespace/Models/Comment.cs

namespace MyNamespace.Models

    public class Comment
    
        public int ID  get; set; 
        public int ProjectID  get; set; 
        // Other attributes

        public Project Project  get; set; 
    

MyNamespace/Models/File.cs

namespace MyNamespace.Models

    public class File
    
        public int ID  get; set; 
        public int ProjectID  get; set; 
        // Other attributes

        public MyNamespace.Models.Project Project  get; set; 
        // Again being very explicit here because I'm at a loss
    

你看到我在这里得到了什么,对吧? Project 实体可以包含零个或多个 cmets 以及零个或多个文件,并且每个注释或文件可以与一个项目相关联。这是我们正在研究的一对多关系。不是超级复杂,我还有其他具有类似关系的实体,一切都像我期望的那样工作,除了File 实体。我在这里包括Comment 实体来说明,据我所知,我正在正确对待File 实体,与Comment 实体相同,但我不知道为什么它不起作用.不过,请注意,还有其他实体我也在处理同样的事情并且工作正常。

所以这些是有问题的模型。这是我的DbContext

MyNamespace/Data/ProjectDBContext.cs

namespace MyNamespace.Data

    public class ProjectDBContext : DbContext
    
        public DbSet<Project> Projects  get; set; 
        // Other stuff

        public DbSet<Comment> Comments  get; set; 
        public DbSet<MyNamespace.Models.File> Files  get; set; 
    

同样,我有很多东西,一切都按预期工作,直到我添加了这个新的 File 模型。

所以,既然我们已经完成了所有设置,让我们进入代码吧。

我知道,我的组织有点奇怪,(但我认为这有点合适?),但我将包括所有我认为相关的内容。我正在使用视图组件和 Razor 页面,所以,首先,我们有一个常规的 Razor 页面(如果你们真的想要它,我会包含 MyNamespace/Pages/Projects/View.cshtml.cs,但我真的不认为问题出在这里,所以我'现在要跳过它。)

MyNamepsace/Pages/Projects/View.cshtml

@model MyNamespace.Pages.Projects.View

<!-- Some stuff -->

<div>
    @await Component.InvokeAsync("CommentsBox", Model.Project)
</div>

<div>
    @await Component.InvokeAsync("FilesBox", Model.Project)
</div>

<!-- Other stuff -->

现在,我想澄清一下,Files 的东西不起作用。 Comments 的东西可以。我将两者都包括在内是为了说明它适用于一个而不是另一个,我不知道为什么。抱歉,我们的视图组件的第一部分。

MyNamespace/ViewComponents/CommentsBox.cs

namespace MyNamespace.ViewComponents

    public class CommentsBox : ViewComponent
    
        public IViewComponentResult Invoke(Project project)
        
            return View("Default", project)
        
    

MyNamespace/ViewComponents/FilesBox.cs

namespace MyNamespace.ViewComponents

    public class FilesBox : ViewComponent
    
        public IViewComponentResult Invoke(Project project)
        
            // We'll come back to this later, but I have a breakpoint set at this line.
            return View("Default", project)
        
    

现在,观点(我实际上不知道当我称这些“观点”时我是否使用了正确的用语,但这不是重点)

MyNamespace/Pages/Shared/CommentsBox/Default.cshtml

@model MyNamespace.Models.Project

<!-- Some stuff -->

@foreach (var comment in Model.Comments)

    // Just display info, this all works fine


<!-- Some stuff -->

MyNamespace/Pages/Shared/FilesBox/Default.cshtml

@model MyNamespace.Models.Project

<!-- Some stuff -->

@foreach (var file in Model.Files) // ERROR

    // Supposed to display the info


<!-- Some stuff -->

<!-- Do note, there is some code here to upload a file. If I remove the code above that's throwing the error, It actually does upload the file. -->

所以我得到的错误是抱怨MyNamespace/Pages/Shared/FilesBox/Default.cshtml 中的for 循环,它是ArgumentNullException。它抱怨Model.Files 为空,所以我无法对其进行迭代。但这没有任何意义,(你会看到它有一点作用,但我不知道为什么它是null)。因为,对于我没有 cmets 和文件的项目,MyNamespace/Pages/Shared/CommentsBox/Default.cshtml 中的循环工作得很好。为什么它适用于 Comments 而不是 Files

此外,如果我从 MyNamespace/Pages/Shared/FilesBox/Default.cshtml 中删除 for 循环并使用上传文件的代码来上传文件,它就可以正常工作。完全没有问题。现在,我们知道我们在与该项目关联的数据库中有一个文件。所以,只是为了好玩,让我们改变MyNamespace/Pages/Shared/FilesBox/Default.cshtml

MyNamespace/Pages/Shared/FilesBox/Default.cshtml

@model MyNamespace.Models.Project

<!-- Some stuff -->

@try

    @foreach (var file in Model.Files)
    
        // We should display info here
    

catch (Exception ArgumentNullException)

    <text>Woops we've caught our exception</text>


<!-- Some stuff -->

<!-- Do note, there is some code here to upload a file. It actually does upload the file, if I remove the code above that's throwing the error -->

好的,所以我们知道我们在数据库中有一个文件,我可以在数据库中看到它(而不是在网页上),我可以看到它的 ProjectID 正是我所期望的。再次运行它,我们发现了我们的错误情况。为什么?我们知道我们在数据库中有一个文件,其中 ProjectID 指向我们的项目。请记住,我们对 cme​​ts 没有这个问题。即使数据库中没有带有此ProjectID 的 cmets,我们也不会收到此错误。然而,即使我们确实在数据库中有一个文件,我们仍然会收到错误,我们甚至可以通过询问错误条件来确认。

好的,所以,我们知道数据库中有一个文件指向我们的项目,但我们仍在输入错误条件。我将MyNamespace/Pages/Shared/FilesBox/Default.cshtml 改回原来的,没有错误检查。数据库保留其信息,无需担心。但是我们又回到了同样的错误。它只是没有将File 实体与其正确的Project 实体相关联。

还记得那个断点吗?这就是事情变得好奇的地方。如果我检查从MyNamepsace/Pages/Projects/View.cshtmlMyNamespace/ViewComponents/FilesBox.csMyNamespace/Pages/Shared/FilesBox/Default.cshtmlProject 对象,就在它转到MyNamespace/Pages/Shared/FilesBox/Default.cshtml 之前,我看到了一些有趣的东西。我们可以在运行时的那个时刻检查对象,并看到确实,我们的Project 对象的Files 属性是null,但是对于Comments?不是null,而是Count = 0...所以...为什么?我觉得我为他们俩做了完全相同的事情。我以相同的方式设置它们,在DbContext 中对它们进行相同处理,在模型中对它们进行相同处理,以及它们与Project 的关联。为什么会有不同的待遇?

此外,如果我们在该断点检查Project 对象,我们可以看到它的Comments 属性,其中值是Count = 0,类型是System.Collections.Generic.ICollection&lt;MyNamespace.Models.Comment&gt; System.Collections.Generic.HashSet&lt;MyNamespace.Models.Comment&gt;。但是,对于我们的Project 对象的Files 属性,其值为null(即使我们知道数据库中有ProjectID 指向该项目的文件),类型只是System.Collection.Generic.ICollection&lt;MyNamespace.Models.File&gt; .我什至尝试将模型MyNamespace/Models/Project.cs 更改为

MyNamespace/Models/Project.cs

namespace MyNamespace.Models

    public class Project
    
        public int ID  get; set; 
        // Bunch of other stuff, doesn't matter

        public ICollection<Comment> Comments  get; set; 
        public HashSet<MyNamespace.Models.File> Files  get; set; 
        // See? I'm specifying the HashSet type here
    

但是,无济于事。同样的错误,同样的行为。

我确定我缺少的东西都很小,但我一直在寻找这个,当我把这整件事写出来时,已经有一天多的时间了,我只是不知所措。它是 Visual Studio 的东西吗?我错过了什么吗?我只是不知道。我希望你们这些优秀的人能够看到我看不到的东西,或者能够为我指明正确的方向。

【问题讨论】:

【参考方案1】:

哇哦。还记得我说过,“如果你们真的想要的话,我会加入MyNamespace/Pages/Projects/View.cshtml.cs,但我真的不认为问题出在这里,所以我现在要跳过它”?

嗯,我应该把它包括在内。没有追查她的对象的傻瓜有祸了。这是来自MyNamespace/Pages/Projects/View.cshtml.cs的相关代码

MyNamespace/Pages/Projects/View.cshtml.cs

namespace MyNamespace.Pages.Projects

    public class View : PageModel
    
        private readonly IReposity _reposity;

        public Project Project  get; set; 

        public View (IRepository repository)
        
            _repository = repository;
        

        public async Task<IActionResult> OnGetAsync(int id)
        
            // Some stuff

            Project = _repository.GetOneProjectByIDAsNoTrackingAsync(id);

            // Some other stuff
        
    

所以,你看,我正在使用存储库来访问我的数据,所以让我们看看该存储库中的相关方法。 叹息

MyNamespace/Repositories/Repository.cs

namespace MyNamespace.Repositories

    private ProjectDBContext _context;

    public Repository(ProjectDBContext context)
    
        _context = context;
    

    public async Task<Project> GetOneProjectByIDAsNoTrackingAsync(int id)
    
        return await _context.Projects
            .Include(p => p.Comments)
            .Inlcude(p => p.SomethingElse)
            // Include a bunch of other stuff
            .ThenInclude(s => s.SomethingElseChild)
            .AsNotracking()
            .FirstOrDefaultAsync(m => m.ID == id);
    

你看,我从来没有在这里包含我的File 实体。哎呀,对吧?应该是

MyNamespace/Repositories/Repository.cs

namespace MyNamespace.Repositories

    private ProjectDBContext _context;

    public Repository(ProjectDBContext context)
    
        _context = context;
    

    public async Task<Project> GetOneProjectByIDAsNoTrackingAsync(int id)
    
        return await _context.Projects
            .Include(p => p.Comments)
            .Inlcude(p => p.SomethingElse)
            .Include(p => p.Files) // <== THIS THIS THIS
            // Include a bunch of other stuff
            .ThenInclude(s => s.SomethingElseChild)
            .AsNotracking()
            .FirstOrDefaultAsync(m => m.ID == id);
    

我写了整个 1,500 字的问题只是为了意识到我错过了什么。无论如何,我将把它留在那里并为后代回答这个问题。

【讨论】:

以上是关于EF Core 处理相同的实体,但一个是空的,一个不是的主要内容,如果未能解决你的问题,请参考以下文章

EF Core 过滤掉多对多关系中的重复实体

使用 EF Core 保存附加实体时如何删除子实体

EF Core 5 在迁移中创建了两次表

EF6 vs Entity Framework Core:插入实体而不将主键(身份)重置为零

EF Core选择由相关实体过滤的一个实体

更新子记录 EF Core 3.0 中的错误实体之间的关联已被切断,但关系被标记为“必需”