如何让Entity Framework在dotnet6.0的DbContext中使用Find/FindAsync检索嵌套对象。

huangapple go评论64阅读模式
英文:

How do you get Entity Framework to retrieve Nested Objects on Find/FindAsync from the DbContext on dotnet6.0

问题

# 问题摘要

## 目标的详细信息
我想使用EFCore和SQLite从数据库中检索一个对象及其嵌套的对象成员。

## 预期结果与实际结果
目前,只有基本类型被正确实例化,而嵌套对象保持在默认/空状态,列表保持为空。
我期望的是EFCore将进行额外的调用并完全填充我的对象。

## 错误消息
没有抛出错误,只返回了垃圾数据。

# 我尝试过的方法
1. 我尝试删除数据库,并希望使用EFCore重建,以解决可能在多次更新中出现的任何小问题。
2. 我还检查了数据库,以确保所有我需要的嵌套数据的条目都是有效的。
3. 我还尝试使用原始SQL,但在尝试使用表连接时会引发关于已引用某些内容的错误。

# 简化的重现 #
我正在使用Microsoft.AspNetCore.Components.DataAnnotations.Validation 3.2.0-rc1.2023.4来访问ValidateComplexObjects。

## FullName.cs
```csharp
public class FullName
{
    public ulong ID { get; set; }
    
    #region Name

    [Display(
        Name = "First Name", 
        AutoGenerateField = true, 
        AutoGenerateFilter = true, 
        Description = "The first name of a person", 
        GroupName = "Name",
        Order = 2, Prompt = "Enter first name here", 
        ShortName = "FName")]
    [MinLength(3, ErrorMessage = "First name can't be shorter than 3 letters")]
    [StringLength(100)]
    [Required]
    public string FirstName { get; set; }
    
    [Display(
        Name = "Last Name", 
        AutoGenerateField = true, 
        AutoGenerateFilter = true, 
        Description = "The last name of a person", 
        GroupName = "Name",
        Order = 1, Prompt = "Enter last name here", 
        ShortName = "LName")]
    [MinLength(3, ErrorMessage = "Last name can't be shorter than 3 letters")]
    [StringLength(100)]
    [Required]
    public string LastName { get; set; }
    
    [Display(
        Name = "Middle Name", 
        AutoGenerateField = true, 
        AutoGenerateFilter = true, 
        Description = "The middle name of a person", 
        GroupName = "Name",
        Order = 50, Prompt = "Enter middle name here", 
        ShortName = "MName")]
    [MinLength(3, ErrorMessage = "Middle name can't be shorter than 3 letters")]
    [StringLength(100)]
    [Required]
    public string MiddleName { get; set; }

    public string Name => $"{FirstName} {MiddleName} {LastName}";
    #endregion

}

Track.cs

// 可以运行的轨道和它们的长度的数据
public record Track(int Id, [Required] string Name, [Required]int distanceInMiles);

Person.cs

public class Person{
   public ulong Id {get;set;}
   [ValidateComplexType]  public FullName Name {get; set;}
   [Required] public string Description {get;set;}
   [ValidateComplexType] public List<Track> FriendsName {get;set;}
}

ApplicationDbContext.cs

public class ApplicationDbContext : IdentityDbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    public DbSet<FullName> Names { get; set; }
    public DbSet<Person> Persons { get; set; }
    public DbSet<Track> Tracks { get; set; }
}

ViewPeople.razor

@page "/Person/View/{id}"
// 项目引用
@inject ApplicationDbContext ApplicationDbContext

@if(validId){ @*Blah Blah HTML*@ }
else { "Invalid Id Requested" }

@code {
    Person person;
    
    [Parameter]
    public string id { get; set; }

    bool validId = false;

    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        validId = ulong.TryParse(id, out var iId);
        person = await ApplicationDbContext.Persons.FindAsync(iId);
        validId = person is not null;
    }
}

由EFCore生成的表格的外观

Name 表格

id FirstName MiddleName LastName
1 John Wittle Door

Track 表格

id, Name, DistanceInMiles, PersonId

id Name DistanceInMiles PersonId
1 Honalulu 12km 1
2 Honalulu 2 1km x
3 Honalulu 6 4km 1

Person 表格

id, NameId, Description

id NameId Description
1 1 "我不是一具尸体"

期望

我期望的

{ 
 	Id: 1, 
 	Name : 
 	{ 
 		Id: 1, 
 		FirstName: "John", 
 		MiddleName: "Wittle",
 		LastName: "Doe"
	 },
	 Description: "我不是一具尸体",
	 Tracks = [
	 	{Id: 1, Name: "Honalulu", DistanceInMidles: 12km },
	 	{Id: 2, Name: "Honalulu 2", DistanceInMidles: 1km },
	 	{Id: 3, Name: "Honalulu 6", DistanceInMidles: 4km },
	 ]
}

我得到的

{ 
 	Id: 1, 
 	Name : 
 	{ 
 		Id: 0, 
 		FirstName: null, 
 		MiddleName: null,
 		LastName: null
	 },
	 Description: "我不是一具尸体",
	 Tracks = []
}

<details>
<summary>英文:</summary>

# Summarization of Problem

## Details about goal 
I want to retrieve an object and its nested object members from the database using EFCore and SQLite. 

## Expected and Actual Results
Currently, only the basic types are being instantiated properly while the nested objects are left in a default/null state and lists are left empty.
What I expect to happen is that EFCore will make the additional calls and fill my object completely.

## Error Messages
No errors are thrown, just garbage data is returned.

# What I have attempted
1. I have tried to drop the database and rebuild with EFCore in hopes of fixing any minor problems that might have shown up over multiple updates. 
2. I also checked the database to make sure there were valid entries to all the nested data that I required.
3. I also tried using raw sql but it would throw an error about something already being referenced before when trying to use table joining.


# Simplified Reproduction #
I am using Microsoft.AspNetCore.Components.DataAnnotations.Validation 3.2.0-rc1.2023.4 to get access to ValidateComplexObjects.

## FullName.cs

public class FullName
{
public ulong ID { get; set; }

#region Name

[Display(
    Name = &quot;First Name&quot;, 
    AutoGenerateField = true, 
    AutoGenerateFilter = true, 
    Description = &quot;The first name of a person&quot;, 
    GroupName = &quot;Name&quot;,
    Order = 2, Prompt = &quot;Enter first name here&quot;, 
    ShortName = &quot;FName&quot;)]
[MinLength(3, ErrorMessage = &quot;First name can&#39;t be shorted than 3 letters&quot;)]
[StringLength(100)]
[Required]
public string FirstName { get; set; }

[Display(
    Name = &quot;Last Name&quot;, 
    AutoGenerateField = true, 
    AutoGenerateFilter = true, 
    Description = &quot;The last name of a person&quot;, 
    GroupName = &quot;Name&quot;,
    Order = 1, Prompt = &quot;Enter last name here&quot;, 
    ShortName = &quot;LName&quot;)]
[MinLength(3, ErrorMessage = &quot;Last name can&#39;t be shorted than 3 letters&quot;)]
[StringLength(100)]
[Required]
public string LastName { get; set; }

[Display(
    Name = &quot;Middle Name&quot;, 
    AutoGenerateField = true, 
    AutoGenerateFilter = true, 
    Description = &quot;The middle name of a person&quot;, 
    GroupName = &quot;Name&quot;,
    Order = 50, Prompt = &quot;Enter middle name here&quot;, 
    ShortName = &quot;MName&quot;)]
[MinLength(3, ErrorMessage = &quot;Middle name can&#39;t be shorted than 3 letters&quot;)]
[StringLength(100)]
[Required]
public string MiddleName { get; set; }

public string Name =&gt; $&quot;{FirstName} {MiddleName} {LastName}&quot;;
#endregion

}


## Track.cs

// Data on tracks that can be ranned on and how long they are
public record Track(int Id, [Required] string Name, [Required]int distanceInMiles);



## Person.cs

public class Person{
public ulong Id {get;set;}
[ValidateComplexType] public FullName Name {get; set;}
[Required] public string Description {get;set;}
[ValidateComplexType] public List<Track> FriendsName {get;set;}
}


## ApplicationDbContext.cs

public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}

public DbSet&lt;FullName&gt; Names { get; set; }
public DbSet&lt;Person&gt; Persons { get; set; }
public DbSet&lt;Track&gt; Tracks { get; set; }

}


## ViewPeople.razor

@page "/Person/View/{id}"
// Project usings
@inject ApplicationDbContext ApplicationDbContext

@if(validId){ @Blah Blah HTML@ }
else { "Invalid Id Requested" }

@code {
Person person;

[Parameter]
public string id { get; set; }

bool validId = false;

protected override async Task OnInitializedAsync()
{
    await base.OnInitializedAsync();
    validId = ulong.TryParse(id, out var iId);
    person = await ApplicationDbContext.Persons.FindAsync(iId);
    validId = person is not null;
}

}


## What the table generated from EFCore looks like

### Name Table
| id | FirstName | MiddleName | LastName |
| -------- | -------------- |-------------- |-------------- |
| 1 | John | Wittle | Door |

### Track Table
id, Name, DistanceInMiles, PersonId

| id | Name | DistanceInMiles | PersonId |
| -------- | -------------- |-------------- |-------------- |
| 1 | Honalulu | 12km | 1 |
| 2 | Honalulu 2 | 1km | x |
| 3 | Honalulu 6 | 4km | 1 |

### Person Table
id, NameId, Description

| id | NameId | Description |
| -------- | -------------- |-------------- |-------------- |
| 1 | 1 | &quot;I am not a cadaver&quot; |

### Expectations
#### What I expect

{
Id: 1,
Name :
{
Id: 1,
FirstName: "John",
MiddleName: "Wittle",
LastName: "Doe"
},
Description: "I am not a cadaver",
Tracks = [
{Id: 1, Name: "Honalulu", DistanceInMidles: 12km },
{Id: 2, Name: "Honalulu 2", DistanceInMidles: 1km },
{Id: 3, Name: "Honalulu 6", DistanceInMidles: 4km },
]
}


#### What I Get

{
Id: 1,
Name :
{
Id: 0,
FirstName: null,
MiddleName: null,
LastName: null
},
Description: "I am not a cadaver",
Tracks = []
}





</details>


# 答案1
**得分**: 1

EFCore会按照你要求的方式执行操作。请考虑你在"ViewPeople.razor"文件中的"OnInitializedAsync"方法。

```csharp
protected override async Task OnInitializedAsync()
{
    ..
    person = await ApplicationDbContext.Persons.FindAsync(iId);
    ..
}

你要求EFCore只返回具有ID "iID" 的人员记录,没有其他内容。要包括相关实体,你需要明确告诉EFCore如何操作。在你的特定情况下,可以这样做:

person = await ApplicationDbContext.Persons
   .Include(x => x.FriendsName)
   .FirstOrDefaultAsync(x => x.Id == iId);

如果你的跟踪实体还有其他依赖实体,可以链式调用Include,例如:

person = await ApplicationDbContext.Persons
   .Include(x => x.FriendsName)
   .ThenInclude(x => x.DependentEntitiesOfTrack)
   .FirstOrDefaultAsync(x => x.Id == iId);
英文:

EFCore does exactly would you have asked it to do. Consider your 'OnInitializedAsync' in your 'ViewPeople.razor'.

protected override async Task OnInitializedAsync()
{
    ..
    person = await ApplicationDbContext.Persons.FindAsync(iId);
    ..
}

All your are asking EFCore to do, is to return the person record with ID 'iID' - nothing else. In order to include dependent entities you need to explicitly tell EFCore to do so. In your particular case:

person = await ApplicationDbContext.Persons
   .Include(x =&gt; x.FriendsName);
   .FirstOrDefaultAsync(x =&gt; x.Id == iId);

If your Track entity had another dependent entity, .Include-calls could also be chained, e.g.:

person = await ApplicationDbContext.Persons
   .Include(x =&gt; x.FriendsName);
   .ThenInclude(x =&gt; x.DependentEntitiesOfTrack)
   .FirstOrDefaultAsync(x =&gt; x.Id == iId);

huangapple
  • 本文由 发表于 2023年5月26日 12:56:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/76337766.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定