Entity Framework 6 DB-First:仅针对 SQL 服务器发出 InvalidOperationException

Entity Framework 6 DB-First: issue InvalidOperationException only for SQL Server

前提

我们在 .NET 4.7.2 中有一个应用程序使用 Entity Framework 6 db-first(带有一个 .edmx 文件)。到目前为止,我们只使用 Devart for Oracle 作为 EF 的提供者,但现在我们需要处理 SQL 服务器数据库。我们所有的 ASP.NET MVC 5 视图(数百个)都需要使用“System.Data.SqlClient”。没问题,查询工作正常,除了...

问题

该问题与外键有关并已引用 table。

错误如下:

InvalidOperationException: the cast to value type 'System.Int32' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.

at 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
at System.Data.Entity.Core.Common.Internal.Materialization.SimpleEnumerator.MoveNext() +88
at System.Linq.Buffer1..ctor(IEnumerable1 source) +284
at System.Linq.Enumerable.ToArray(IEnumerable`1 source) +90
at Medici.MediciService.GetMedici(GetMediciQuery param)

查询示例:

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; }
}

问题与以下事实有关:table Medici 中的外键可以为 null,但引用的 table 上的 id 不是。 EF6 的 Devart 提供程序处理此设置的默认值(在本例中为零),而 SQL 服务器提供程序运行到 InvalidOperationException.

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

我认为您可能不走运,因为该代码是根据一个假设构建的,即 Oracle 提供程序围绕投影可为空的引用而存在的特性,而 SQL Server 等其他提供程序不共享这些引用。

如果您最终接受需要重新审视您的预测,我知道有两种选择。要么在投影之前显式添加 #null 检查,要么考虑利用 Automapper 来处理投影 /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,或者它指的是实体的副本 class本身。 (希望不是最后一个,混合实体和 DTO)

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