"Where" 子句在 "ProjectTo" 和 Nullable 类型之后?
"Where" clause after "ProjectTo" and Nullable types?
使用 ASP.NET Core 5 和 EF Core 5 后端开发 REST API。
我得到了以下实体、DTO 和映射(仅包括相关代码):
// Entities
//
class Book {
public int Id { get; set; }
public string Title { get; set; }
public int AuthorId { get; set; } // foreing-key
public Author Author { get; set; } // nav prop
}
class Author {
public int Id { get; set; }
public string Name { get; set; }
}
(assume proper EF config to indicate HasOne/HasMany WithOne/WithMany accordingly)
// DTOs
//
class AuthorDTO { ... }
class BookDTO {
...
AuthorDTO Author { get; set; }
}
// Automapper Maps
//
CreateMap<Book, BookDTO>();
CreateMap<Author, AuthorDTO>();
如果我运行下面的代码,生活就好了。事实上,对 BookDTO 运行 的根级字段进行的任何过滤都很好:
var data = await dbContex
.Books
.ProjectTo<BookDTO>(mapper.ConfigurationProvider)
.Where(bDto => bDto.Id == 4)
.ToListAsync();
对嵌套的 AuthorDTO 字段进行的过滤只要该字段为可空类型或引用类型就有效。例如
var data = await dbContex
.Books
.ProjectTo<BookDTO>(mapper.ConfigurationProvider)
.Where(bDto => bDto.Author.Name == "John")
.ToListAsync();
但是,对不可空类型或值类型的查询失败:
var data = await dbContex
.Books
.ProjectTo<BookDTO>(mapper.ConfigurationProvider)
.Where(bDto => bDto.Author.Id == 10)
.ToListAsync();
错误:
"The binary operator Equal is not defined for the types 'System.Nullable`1[System.Int32]' and 'System.Int32'."
如果我将 AuthorDTO Id 声明为 int?
,则代码有效。
我已经在 Where 子句中尝试了几种转换组合。
欢迎提出任何建议。
PS:如果我将 Where 子句放在 ProjectTo 投影之前并根据实体字段而不是 DTO 字段进行所有过滤,就会发生 None 这些问题。对于那些想知道为什么我要基于 DTO 进行过滤的人:我正在使用 Sieve,一个允许我进行过滤和分页“a la OData”的包,以及客户端在调用我的 API 是 DTO 中的那些,所以我真的需要在 ProjectTo 之后应用所有查询。
一般来说,问题出在 EF Core 中,因为 AutoMapper ProjectTo
只生成一个 Select
,您可以手动执行此操作,但仍然会遇到同样的问题。
问题出在可为空的引用上。默认情况下 AutoMapper 假定任何引用类型 属性 允许 null
,因此生成的投影是这样的(注意条件运算符和 null
检查):
Author = src.Author == null ? null : new AuthorDto
{
Id = src.Author.Id,
Name = src.Author.Name,
}
一旦你有,任何尝试在生成的 Author
属性 不可空成员上应用额外的 LINQ 运算符(Where
、OrderBy
等)都会命中令人不快的 EF Core bug/limitation(因为对于常规实体引用导航属性 w/o 投影,它的处理方式可能不同并且以某种方式起作用)。
话虽如此,解决方案是什么?如果 属性 是可选的(允许 null
),则无能为力(实际上有一个丑陋的解决方法需要手动导航 属性 扩展,这会扼杀导航的所有好处属性,通常不适用于 AutoMapper)。
但是如果需要属性(不允许 null
,即源来自 required relationship),那么解决方案就是删除条件运算符并仅生成
Author = new AuthorDto
{
Id = src.Author.Id,
Name = src.Author.Name,
}
使用 AutoMapper 可以通过两种方式实现。首先是更改所有引用类型属性的默认值
AllowNullDestinationValues = false; // Default is true
(对于名为 AllowNullCollections
的集合有类似的 属性,默认情况下也是 true
,并且设置为 false
是有意义的,因为在 LINQ 中实体集合永远不会 null
)
另一种方法是保留默认值并为每个单独配置 属性:
CreateMap<Book, BookDto>()
.ForMember(dst => dst.Author, opt => opt.DoNotAllowNull());
实际上还有第三种选择 - 更改默认值,然后使用 opt.AllowNull()
仅针对可选属性覆盖它。
请记住 - 无论您使用什么选项,如果您有可选的引用 属性 类型,它都不会起作用,直到 EF Core 提供了 null
投影问题的解决方案。
使用 ASP.NET Core 5 和 EF Core 5 后端开发 REST API。
我得到了以下实体、DTO 和映射(仅包括相关代码):
// Entities
//
class Book {
public int Id { get; set; }
public string Title { get; set; }
public int AuthorId { get; set; } // foreing-key
public Author Author { get; set; } // nav prop
}
class Author {
public int Id { get; set; }
public string Name { get; set; }
}
(assume proper EF config to indicate HasOne/HasMany WithOne/WithMany accordingly)
// DTOs
//
class AuthorDTO { ... }
class BookDTO {
...
AuthorDTO Author { get; set; }
}
// Automapper Maps
//
CreateMap<Book, BookDTO>();
CreateMap<Author, AuthorDTO>();
如果我运行下面的代码,生活就好了。事实上,对 BookDTO 运行 的根级字段进行的任何过滤都很好:
var data = await dbContex
.Books
.ProjectTo<BookDTO>(mapper.ConfigurationProvider)
.Where(bDto => bDto.Id == 4)
.ToListAsync();
对嵌套的 AuthorDTO 字段进行的过滤只要该字段为可空类型或引用类型就有效。例如
var data = await dbContex
.Books
.ProjectTo<BookDTO>(mapper.ConfigurationProvider)
.Where(bDto => bDto.Author.Name == "John")
.ToListAsync();
但是,对不可空类型或值类型的查询失败:
var data = await dbContex
.Books
.ProjectTo<BookDTO>(mapper.ConfigurationProvider)
.Where(bDto => bDto.Author.Id == 10)
.ToListAsync();
错误:
"The binary operator Equal is not defined for the types 'System.Nullable`1[System.Int32]' and 'System.Int32'."
如果我将 AuthorDTO Id 声明为 int?
,则代码有效。
我已经在 Where 子句中尝试了几种转换组合。
欢迎提出任何建议。
PS:如果我将 Where 子句放在 ProjectTo 投影之前并根据实体字段而不是 DTO 字段进行所有过滤,就会发生 None 这些问题。对于那些想知道为什么我要基于 DTO 进行过滤的人:我正在使用 Sieve,一个允许我进行过滤和分页“a la OData”的包,以及客户端在调用我的 API 是 DTO 中的那些,所以我真的需要在 ProjectTo 之后应用所有查询。
一般来说,问题出在 EF Core 中,因为 AutoMapper ProjectTo
只生成一个 Select
,您可以手动执行此操作,但仍然会遇到同样的问题。
问题出在可为空的引用上。默认情况下 AutoMapper 假定任何引用类型 属性 允许 null
,因此生成的投影是这样的(注意条件运算符和 null
检查):
Author = src.Author == null ? null : new AuthorDto
{
Id = src.Author.Id,
Name = src.Author.Name,
}
一旦你有,任何尝试在生成的 Author
属性 不可空成员上应用额外的 LINQ 运算符(Where
、OrderBy
等)都会命中令人不快的 EF Core bug/limitation(因为对于常规实体引用导航属性 w/o 投影,它的处理方式可能不同并且以某种方式起作用)。
话虽如此,解决方案是什么?如果 属性 是可选的(允许 null
),则无能为力(实际上有一个丑陋的解决方法需要手动导航 属性 扩展,这会扼杀导航的所有好处属性,通常不适用于 AutoMapper)。
但是如果需要属性(不允许 null
,即源来自 required relationship),那么解决方案就是删除条件运算符并仅生成
Author = new AuthorDto
{
Id = src.Author.Id,
Name = src.Author.Name,
}
使用 AutoMapper 可以通过两种方式实现。首先是更改所有引用类型属性的默认值
AllowNullDestinationValues = false; // Default is true
(对于名为 AllowNullCollections
的集合有类似的 属性,默认情况下也是 true
,并且设置为 false
是有意义的,因为在 LINQ 中实体集合永远不会 null
)
另一种方法是保留默认值并为每个单独配置 属性:
CreateMap<Book, BookDto>()
.ForMember(dst => dst.Author, opt => opt.DoNotAllowNull());
实际上还有第三种选择 - 更改默认值,然后使用 opt.AllowNull()
仅针对可选属性覆盖它。
请记住 - 无论您使用什么选项,如果您有可选的引用 属性 类型,它都不会起作用,直到 EF Core 提供了 null
投影问题的解决方案。