【问题标题】:.NET Core and EF relations : problem with getting data.NET Core 和 EF 关系:获取数据的问题
【发布时间】:2022-01-01 22:26:27
【问题描述】:

问题

我有两个有关系的表;我可以毫无问题地从一个表或另一个表中获取我想要的任何数据,但我无法同时从两个表中获取数据。

环境:

  • Visual Studio 2022
  • 在 .NET 6.0 上运行的 Webassembly Blazor 项目
  • Nuget 包
    • Microsoft.EntityFrameworkCore 6.0.1
    • Pomelo.EntityFrameworkCore.MySql
  • 数据库 MariaDb 13.3.31

我构建数据环境的方式:

使用 PhpMyAdmin,我创建了 2 个具有关系的表。

然后我在包管理器控制台中启动了脚手架命令:

Scaffold-DbContext "Server=192.168.0.22;user=MyUser;Password=MyPassword;Database=MyDb;" 
         Pomelo.EntityFrameworkCore.MySql 
         -OutputDir Models -Context "MySqlstockDataContext" -DataAnnotations

之后,我使用 VS2022 命令“添加新控制器”创建了控制器,并选择了“使用实体框架执行操作的 API 控制器”

我的模型,DbContext 和控制器是使用 VS 2022 的工具构建的,我没有为此编写任何代码。

数据库(一个简单的数据库,其中包含可以移动的文章(库存的输入或输出):

这是模型类“Articles”的生成代码:

Screenshot of model class Articles

这是模型类“Mouvements”的生成代码:

Screenshot of model class Mouvements

这是模型类“DbContext”的生成代码:

Screenshot of DbContext

ArticlesController:

Screenshot of ArticlesController

运动控制器:

Screenshot of MouvementsController

剃刀页面:

Screenshot for Razor page displaying the data in browser

我已经尝试过的:

在这个剃须刀页面中,一行

<td>@mouvement.IdArticleNavigation.NomArticle</td>

即使在MouvementsController 也是问题,我用

return await _context.Mouvements
                     .Include(e =>  e.IdArticleNavigation.NomArticle)
                     .ToListAsync();

而不仅仅是

return await _context.Mouvements.ToListAsync();

为了让事情顺利进行,我想我应该这样写:

<td>@Mouvement. Articles.NomArticle</td>

在 Razor 页面和

return await _context.Mouvements
                     .Include(e => e.Articles.NomArticle)
                     .ToListAsync();

在 MouvementsController 中。

但智能感知并没有给我这种可能性。智能感知为我提供链接文章和运动的唯一内容是Include.IdArticleNavigation.NomArticle,但这不起作用。

这是我在浏览器控制台看到的错误:

发生未处理的错误

在 VS 2022 控制台中,错误提示

“e.IdArticleNavigation.NomArticle”在包含中无效。

Screenshot of the error

我必须承认我是 .NET Core、C#、Visual Studio 的新手,我只是出于爱好编写代码。所以,我在网上看到的所有例子对我来说都不是很清楚(复杂的开发者词汇!)

如果可能的话,希望有人能够帮助我一个简单的方法!


你好凯厄斯, 非常感谢这种快速而敏锐的反应,我认为这很好理解。但是,也许我不应该使用您的建议,因为我仍然无法查询这两个表。 在下面,当我尝试重现您建议的代码时,您可以看到我在 Intellisense 命题中的内容。
我们可以看到,没有来自“article”表的属性命题。

为了确保我的问题很清楚,让我告诉我我尝试得到的结果:我想获得“mouvement”表的所有属性和“nom_article”属性(以及其他属性)对应的“文章”表。在 SQL 中,我认为它对应于:SELECT * FROM mouvement INNER JOIN article ON mouvement.id_article = article.id_article; 这将允许最终获得显示如下表格的属性:

id_mvt|id 文章|type_mvt|Qte_mvt|date_mvt|nom_article

最好的问候。

【问题讨论】:

  • 请勿将代码发布为图片 - 在您的问题中使用适当的代码块。关注guidelines
  • 您好 Quain,感谢您的建议。我在发布时已经尝试过了,但问题是发布代码的标记是 { } 并且代码本身有许多相同的标记(例如开始和结束一个方法)。所以贴出来的代码被分成了几个灰底白底,变得难以阅读。这是我的第一篇文章,我会努力改进的!最好的问候。
  • 尝试将代码放在```c#``` 标记之间 - 这应该可以解决问题。更准确地说,第一行只是```C#,然后是几行代码,然后是单独的行```
  • 除了不发布屏幕截图之外,将您的代码缩减为重现问题的小型 sn-p。您可能会通过精简来找到解决方案,它可以帮助您找到问题所在,并且使问题更具可读性。
  • @Phil:如果每行都以 4 个空格开头,那么您将不会遇到白色/灰色问题。专业提示:在您的 IDE 中,选择代码,再次缩进(按 Tab),然后复制该代码。额外的缩进为您提供了所需的 4 个空格。如果您将缩进设置为 2 个空格而不是 4 个空格,那么显然您的代码会缩进两次。

标签: c# .net entity-framework-core blazor-webassembly


【解决方案1】:

“e.IdArticleNavigation.NomArticle”在包含中无效。

您应该在 Include 中指定一个 导航属性,而不是简单的属性

一篇文章有​​许多运动。如果你想从一个运动中访问一篇文章,你可以使用它的 nav 属性:

myMovement.IdArticleNavigation //an article

EF 在看到您在对象图上导航时执行连接:

context.Movements.Select(m => new { m.MovementName, m.IdArticleNavigation.ArticleName });

因为你已经在一个Select中访问了一个相关实体的一个属性,所以EF会自动在sql中创建一个JOIN来获取每个Movement的相关Article,这样它就可以访问相关数据了

如果你在 Where 子句中导航,它会做类似的事情

我们使用Include 告诉 EF 执行 JOIN,即使我们不使用 where/select 中的相关数据。这就像说“我想要所有的动作……以及它们相关的文章,因为稍后在客户端代码中我将使用一些文章属性”

但要清楚:您无需指定稍后要使用的文章的属性,您只需指定文章,EF 将下载整个内容

var helloMovement = context.Movements
  .Include(m => m.IdArticleNavigation)
  .First(m => m.MovementName == "hello");

如果没有包含,helloMovement.IdArticleNavigation 将为空

如果你只想要某些道具(例如你知道每篇文章都有一个 5 兆字节的 json 列并且你不想下载它)你可以使用我上面提到的匿名类型模式在“导航select" - 这种情况下生成的sql只会提到文章名和动作名


编辑

我们可以看到,没有来自“文章”表的属性命题。

我开始理解您查看表、SQL、关系、EF 和对象图的方式之间的脱节

为了确保我的问题很清楚,让我告诉我我尝试得到的结果:我想获得“mouvement”表的所有属性和“nom_article”属性(以及其他属性)对应的“文章”表。

在SQL中我认为它对应于:SELECT * FROM mouvement INNER JOIN article ON mouvement.id_article = article.id_article;

是的,但您需要了解的是,SQL 生成矩形数据块,而 EF/C# 生成更像

的对象图

让我们有一些简单的 SQL 表:

People
Id, Name, Age
1, John, 27
2, Mary, 23

Skills
Id, PersonId, Description
1, 1, Stonework
2, 1, Welding
3, 2, Carpentry
4, 2, Plumbing

如果你加入这些,你会得到一个矩形数据块

Id, Name, Age, Id, PersonId, Description
1, John, 27, 1, 1, Stonework
1, John, 27, 2, 1, Welding
2, Mary, 23, 3, 2, Carpentry
2, Mary, 23, 4, 2, Plumbing

这就是 SQL 所做的 - 它重复人员行,因为他们每个人都有 2 个技能

EF 不这样做。你有一些拥有技能列表的人:

{ name: John, age: 27, skills: [ { description: stonework }, { description: welding } ] }
{ name: Mary, age: 23, skills: [ { description: carpentry }, { description: plumbing } ] }

它更像是一棵树,或者一个图表。有一个人对象和两个技能对象。如果 EF 加入,它必须做的一件事是对所有返回的多个 John 进行重复数据删除,这样你只会得到一个 Person,而这个 Person 有 2 个技能。每个技能也链接回人,所以你可以有一个有技能的人,而技能有一个人(与我们开始时相同的人)..

你可以写这样的代码:

myPerson.Skills.First().Person.Skills.First().Person.Skills.First().Person.Skills.First().Person.Skills.First().Person.Skills.First().Person.Skills.First().Person.Skills.First().Person.Skills.First();

图表只是转来转去。 Skills是一个List,第一项是Skill,里面有Person,里面有Skills,第一个是...

EF 的工作是观察您在链接实体的图表中导航并形成 SQL,以便检索数据以填充图表以支持您的导航

当你这样做时:

context.People.Where(p => p.Name == "John");

你得到约翰。即使他在数据库中有技能,他的技能列表也将是空的。 EF 没有看到您使用 Skills,因此它不会费心加入它。它只需要最少的连接数来提取您似乎想要使用的数据

如果你说:

context.People.Where(p => p.Skills.Any(s => s.Description== "Carpentry"));

EF 会进行连接(实际上,它可能会使用 EXISTS,但我们暂时忽略它)因此它可以让 WHERE 工作(它需要相关数据)但它不会选择技能,因为看起来你又不需要它们

如果您在 Select 中提到:

context.People.Select(p => new { p.Name, p.Skills });

EF 可以看到你正在使用技能,所以它会加入它,这样它就可以为你提供你所要求的(人名及其技能的匿名类型)

如果你没有提到技能:

context.People.Select(p => p);

然后你会得到所有的人,但他们所有的技能集合都是空的,因为 EF 看不到你想使用它们

如果你告诉 EF 你也想要这些技能(因为你打算在其他时候使用它们):

context.People.Include(p => p.Skills);

然后你会得到所有的 People 对象并且他们的 Skills 集合会被技能填充


现在让我们回到 SQL 中的矩形数据块,以及 EF 中的图表

SELECT * FROM people JOIN skills on people.Id = skills.peopleId

是的,加入技能和人员将让您“只需在选择中使用技能/人员的名称”:

SELECT name, Description FROM people JOIN skills on people.Id = skills.peopleId

但实际上你应该使用表名(或别名),所以 SQL 仍然有“记住数据来自哪里”的概念:

SELECT people.name, skills.Description FROM people JOIN skills on people.Id = skills.peopleId

C# 中的对象图不允许您“输入随机属性名称”——您必须“指定数据的来源”

myPerson.Skills.First().Description.Substring(2);

现在,快速聊聊 LINQ 输入和输出

context.People                     //People is a list of Person
  .Where(p => p.Name == "John")    //Where outputs a list of Person
  .Where(p => p.Age == 27)         //Where outputs a list of Person

LINQ 中的某些内容会改变您的输出,而其他内容则不会。当您像 A.B.C.D 这样链接方法调用时,您必须牢记 A 输出的内容是输入 B 的内容,B 输出的内容是输入 C 的内容。

有些东西输出的东西与输入的东西相似:

Where - called on a list of X, produces a list of X

有些事情会稍微改变结果:

First - called on a list of X, produces a single X

有些事情会彻底改变结果:

Select - called on a list of X, produces a list of Y

包含是不会改变结果的事情之一。你有:

context.Skills               //a list of Skill
  .Include(s => s.Person)    //essentially still a list of Skill

包含人并不会神奇地导致从输入的技能列表中出现某种不同类型的对象。当您在Include 之后说Select 时,您仍然从技能列表

context.Skills               //a list of Skills
  .Include(s => s.Person)    //essentially still a list of Skill
  .Select(s => ...           //s is a Skill

对于列表中的每个技能,如果您想联系具有该技能的人,您必须导航到那里:

context.Skills               //a list of Skills
  .Include(s => s.Person)    //essentially still a list of Skill
  .Select(s => s.Person.Name)//s is a Skill, with a Person property that is a Person, that has a Name property

您必须这样做,因为这就是对象图在 C# 中的工作方式——它们不像在 SQL 中那样工作,您可以在加入列后开始提及列的名称;您总是必须具体说明如何在 C# 中获取某些属性

您可以创建一个具有您想要的所有属性的匿名类型:

context.Skills               
  .Include(s => s.Person)    
  .Select(s => new { PersonName = s.Person.Name, SkillDesc = s.Description})

这会产生一个新对象,不是一个人,也不是一个技能,而是一个只有 string PersonNamestring SkillDesc 属性的临时持有者。如果您将 that 输入另一个 Select,然后 您将看到一组具有这些属性的已更改对象(并且您的 Person/Skill 的所有其他属性都是丢失)

context.Skills               
  .Include(s => s.Person)    
  .Select(s => new { PersonName = s.Person.Name, SkillDesc = s.Description})
  .Select(at => at.PersonName)

这就像执行以下 SQL:

SELECT PersonName 
FROM
  (
    SELECT people.name as PersonName, skills.Description as SkillDesc
    FROM people JOIN skills on people.Id = skills.peopleId
  ) at

这将允许最终获得显示如下表格的属性:

id_mvt|id article|type_mvt|Qte_mvt|date_mvt|nom_article

希望你现在已经完成了你需要做的事情。当你从

开始时
 context.Movements

你让 EF 看到你使用文章中的属性:

 context.Movements
   .Where(m => ...)      //Movements in, Movements out
   .Select(m => new {    //Movements in, new anonymous object out
     m.IdMvt,
     m.IdArticle,
     ...
     m.IdArticleNavigation.NomArticle
   });

记住;此处不需要包含,因为您正在选择相关数据(但您必须通过运动导航到它!)

你也可以这样做:

var x = context.Movements
   .Include(m => m.IdArticleNavigation)
   .Where(m => ...)      //Movements in, Movements out
   .ToList();            //Movements in, Movements out

这会生成一个移动列表,具有完全填充的 IdArticleNavigation 属性,这些属性是相关的文章。你可以这样做

x.First()               //a Movement
  .IdArticleNavigation  //the Article related to the first Movement
      .Nom              //the name of the Article

【讨论】:

  • 很抱歉在我自己的问题中回答了 Caius(是什么让 Caius 不知道我从 Flater 收到的评论!
  • 我们可以看到没有来自“文章”表的属性命题 - 那是因为你还没有导航到它。输入e.IdArticleNavigation.SOMETHING。当您以 Movements 开头时,您可以包括 X、Y 或 Z,但它总是返回到 MovementsMovements.Include(x).Include(y).Include(z) 可以完成,但 x/y/z 必须是移动上/可访问的导航属性。如果你想链接,你可以使用 ThenInclude,但如果你在包含x 之后按.,你看不到从 X 导入到 Movements 中的属性;您仍然需要导航到它们
  • @Phil 我进行了编辑
  • Caius,我已经做出了回答,但我不确定您是否会收到没有评论的通知。
  • Caius,很抱歉,解释对我来说似乎很清楚,但我仍然无法从这两个表中获取数据。不知道是不是代码的问题。
猜你喜欢
  • 2021-05-20
  • 2022-11-02
  • 1970-01-01
  • 1970-01-01
  • 2020-02-01
  • 2021-06-04
  • 2022-12-06
  • 1970-01-01
  • 2022-07-15
相关资源
最近更新 更多