Entity Framework 6 DB-First:仅针对 SQL Server 发出 InvalidOperationException

Posted

技术标签:

【中文标题】Entity Framework 6 DB-First:仅针对 SQL Server 发出 InvalidOperationException【英文标题】:Entity Framework 6 DB-First: issue InvalidOperationException only for SQL Server 【发布时间】:2021-10-21 01:32:26 【问题描述】:

前提

我们在 .NET 4.7.2 中有一个使用 Entity Framework 6 db-first 的应用程序(带有一个 `.edmx` 文件)。到目前为止,我们只使用 Devart for Oracle 作为 EF 的 Provider,但现在我们需要处理 SQL Server 数据库。我们所有的 ASP.NET MVC 5 视图(数百个)都需要使用 `System.Data.SqlClient`。没问题,查询效果很好,除了...

问题

该问题与外键和引用表有关。

错误如下:

InvalidOperationException:转换为值类型“System.Int32”失败,因为具体化值为 null。结果类型的泛型参数或查询必须使用可为空的类型。

在 System.Data.Entity.Core.Common.Internal.Materialization.ErrorHandlingValueReader1.GetValue(DbDataReader reader, Int32 ordinal) +177 at lambda_method(Closure , Shaper ) +1146 at System.Data.Entity.Core.Common.Internal.Materialization.Coordinator1.ReadNextElement(Shaper shaper) +384 在 System.Data.Entity.Core.Common.Internal.Materialization.SimpleEnumerator.MoveNext() +88 在 System.Linq.Buffer1..ctor(IEnumerable1 来源)+284 在 System.Linq.Enumerable.ToArray(IEnumerable`1 源) +90 在 Medici.MediciService.GetMedici(GetMediciQuery 参数)

查询示例:

var medici =  _dbContext.Medici
            .Select(x => new MediciDto
            
                Id = x.Id,
                Nome = x.Nome,
                Cognome = x.Cognome,
                TipoMedico = new TipoMedico
                
                    Codice = x.TipoMedico.Id,
                    Descrizione = x.TipoMedico.Desc,
                
            )
            .ToArray();

这些是类:

public class MediciDto

    public int Id get; set; 
    public string Nome  get; set; 
    public string Cognome  get; set; 
    public TipoMedico TipoMedico  get; set; 


public class TipoMedico

    public int Id get; set; 
    public string Descrizione  get; set; 

问题与表Medici 中的外键可以为空,但引用表上的id 不能为空有关。 EF6 的 Devart 提供程序处理此设置的默认值(在本例中为零),而 SQL Server 提供程序运行到 InvalidOperationException

因此,总而言之,我们的问题是:有没有办法将与 Oracle Devart Provider 相同的行为配置为实体框架的默认设置?我们无法查看所有查询来处理检查空值。我们没有这么多时间。

【问题讨论】:

这个问题在topic解决了 @EnesKartal 这个解决方案涉及审查所有 EF 查询。这是我们应该做的最后一件事...... 你的ID字段是否可以为空? @EnesKartal 你是说 TipoMedico 的 ID 吗?不,它不能为空。但是在 Medico 中引用的 TipoMedico 的 ID (FK) 可以为空 将您的 ID 字段更改为“int?”类型。因为“int”不是可空类型而是“int?”可以为空。 【参考方案1】:

我认为您可能不走运,因为该代码是基于 Oracle 提供程序在投射可空引用方面的特殊性的假设而构建的,而其他提供程序(如 SQL Server)不共享。

如果您最终接受需要重新审视您的预测,我知道有两种选择。要么在投影之前显式添加#null 检查,要么考虑利用 Automapper 来处理 Projections /w ProjectTo,因为它确实可以处理 #null 引用,另外还有使 Linq 表达式更加紧凑的额外好处。这将涉及定义 Automapper 使用的映射规则。根据您的实体与 DTO 命名约定的一致性和可预测性,Automapper 可以通过约定解决相当多的问题。

.Select(x => new MediciDto

    Id = x.Id,
    Nome = x.Nome,
    Cognome = x.Cognome,
    TipoMedico = x.TipoMedico != null 
        ? new TipoMedico
        
            Codice = x.TipoMedico.Id,
            Descrizione = x.TipoMedico.Desc,
         : null
).ToArray();

.ProjectTo<MedicoDTO>(config)
.ToArray();

'config' 是一个 MapperConfiguration,它可以是单个作用域的依赖项,也可以根据需要声明。如果 DTO 和实体的命名一致,则可以很简单:

var config = new MapperConfiguration(cfg => 
    cfg.CreateMap<Medico, MedicoDTO>();
    cfg.CreateMap<TipoMedico, TipoMedicoDTO>();
);

但是,在您的示例中,它指的是 MedicoDTO DTO 中的“TipoMedico”,无论是拼写错误还是指缺少后缀的 DTO,或者指的是实体类本身的副本。 (希望不是最后一个,混合实体和 DTO)

当项目需求发生变化时,有时需要进行重构,这可以看作是整理和改进事物的机会。

【讨论】:

以上是关于Entity Framework 6 DB-First:仅针对 SQL Server 发出 InvalidOperationException的主要内容,如果未能解决你的问题,请参考以下文章

Entity Framework 6 开发系列 目录

添加 [DataContract] 到 Entity Framework 6.0 POCO Template

在Oracle中使用Entity Framework 6 CodeFirst

从 Entity Framework 5 升级到 6

如何扩展 Entity Framework 6 模型

Entity Framework 6.0 Raw Sql 查看模型