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.Coordinator
1.ReadNextElement(Shaper shaper) +384
at System.Data.Entity.Core.Common.Internal.Materialization.SimpleEnumerator.MoveNext() +88
at System.Linq.Buffer1..ctor(IEnumerable
1 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)
当项目需求发生变化时,有时需要重构,这可以看作是整理和改进事物的机会。
前提
我们在 .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.ErrorHandlingValueReader
1.GetValue(DbDataReader reader, Int32 ordinal) +177 at lambda_method(Closure , Shaper ) +1146 at System.Data.Entity.Core.Common.Internal.Materialization.Coordinator
1.ReadNextElement(Shaper shaper) +384
at System.Data.Entity.Core.Common.Internal.Materialization.SimpleEnumerator.MoveNext() +88
at System.Linq.Buffer1..ctor(IEnumerable
1 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)
当项目需求发生变化时,有时需要重构,这可以看作是整理和改进事物的机会。