记一次EFCore类型转换错误及解决方案

一  背景

  今天在使用EntityFrameworkCore 查询的时候在调试的时候总是提示如下错误:Unable to cast object of type 'System.Data.SqlTypes.SqlString' to type 'System.Data.SqlTypes.SqlGuid' 第一次看这个报错肯定是数据库实体和EFCore中定义的某种类型不匹配从而导致类型转换错误,但是业务涉及到这么多的实体Entity,那么到底是哪里类型无法匹配呢?所以第一步肯定是调试代码,然后看报错信息,这里我们首先贴出完整的报错信息,从而方便自己分析具体问题。 

System.InvalidCastException: Unable to cast object of type 'System.Data.SqlTypes.SqlString' to type 'System.Data.SqlTypes.SqlGuid'.
 at System.Data.SqlClient.SqlBuffer.get_SqlGuid()
 at System.Data.SqlClient.SqlDataReader.GetGuid(Int32 i)
 at lambda_method(Closure , DbDataReader )
 at Microsoft.EntityFrameworkCore.Storage.Internal.TypedRelationalValueBufferFactory.Create(DbDataReader dataReader)
 at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.BufferlessMoveNext(DbContext _, Boolean buffer)
 at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
 at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.MoveNext()
 at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider._TrackEntities[TOut,TIn](IEnumerable`1 results, QueryContext queryContext, IList`1 entityTrackingInfos, IList`1 entityAccessors)+MoveNext()
 at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext()
 at System.Linq.Lookup`2.CreateForJoin(IEnumerable`1 source, Func`2 keySelector, IEqualityComparer`1 comparer)
 at System.Linq.Enumerable.JoinIterator[TOuter,TInner,TKey,TResult](IEnumerable`1 outer, IEnumerable`1 inner, Func`2 outerKeySelector, Func`2 innerKeySelector, Func`3 resultSelector, IEqualityComparer`1 comparer)+MoveNext()
 at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
 at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector)
 at Sunlight.Dcs.Application.Sales.SalesOrder.DegradedVehicleContracts.DegradedVehicleContractService.QueryByIdAsync(Int32 id) in E:\63318\sales-service\src\sales.orders\Application.Sales.Orders\SalesOrder\DegradedVehicleContracts\DegradedVehicleContractService.cs:line 99
 at Abp.Threading.InternalAsyncHelper.AwaitTaskWithPostActionAndFinallyAndGetResult[T](Task`1 actualReturnValue, Func`1 postAction, Action`1 finalAction)
 at Sunlight.Dcs.WebApi.Sales.Controllers.Orders.DegradedVehicleContractController.QueryById(Int32 id) in E:\63318\sales-service\src\WebApi.Sales\Controllers\Orders\DegradedVehicleContractController.cs:line 50
 at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
 at System.Threading.Tasks.ValueTask`1.get_Result()
 at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()
 at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
 at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
 at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
 at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
 at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync()

二  解决方案

  有了上面的报错信息我们就能够知道大致方向,接下来我们首先来看看报错信息的这段代码。

public async Task<SimpleDegradedVehicleContractOutput> QueryByIdAsync(int id) {
 var queryResult = await _degradedVehicleContractRepository.GetAll()
 .Include(d => d.DegradedVehicleContractDetails)
 .SingleOrDefaultAsync(mp => mp.Id == id);
 if (queryResult == null)
 throw new ValidationException($"当前Id为:{id}的降级车合同没有找到");
 var result = _objectMapper.Map<SimpleDegradedVehicleContractOutput>(queryResult);
 result.Details = _objectMapper.Map<List<DegradedVehicleContractDetailDto>>(queryResult.DegradedVehicleContractDetails);

 //获取ProductId
 var productIds = queryResult.DegradedVehicleContractDetails.Select(d => d.ProductId).Distinct().ToArray();
 //车辆Id
 var vinLists = queryResult.DegradedVehicleContractDetails.Select(r => r.Vin).Distinct().ToArray();

 var detailResult = (from detail in queryResult.DegradedVehicleContractDetails
 join product in _productRepository.GetAll().Where(p => productIds.Contains(p.Id))
 on detail.ProductId equals product.Id
 join vehicleDict in _vehicleInformationRepository.GetAll().Where(v => vinLists.Contains(v.Vin))
 on detail.Vin equals vehicleDict.Vin
 select new {
 ProductId = product.Id,
 product.ProductType,
 VehicleId = vehicleDict.Id,
 detail.Vin
 }).ToDictionary(r => Tuple.Create(r.ProductId, r.Vin), r => new { r.ProductType, r.VehicleId });

 result.Details.ForEach(r => {
 r.ProductType = detailResult[Tuple.Create(r.ProductId, r.Vin)]?.ProductType;
 r.VehicleId = detailResult[Tuple.Create(r.ProductId, r.Vin)].VehicleId;
 });

 return result;
 }

2.1 定位报错位置

  通过直接对代码进行调试,我们发现只要代码执行获取detailResult这一步的时候就会出现上面的错误,那么到这里我们就可以推断错误的地方就在这里了,所以后面我们的重点就是分析这段代码。

2.2 定位产生错误的表名称

  这里就是利用前面的Include方法来查询到queryResult结果,然后利用queryResult.DegradedVehicleContractDetails来和Product以及VehicleInformation表来做inner join,这里你可能对_productRepository以及_vehicleInformationRepository这个局部变量不是十分熟悉,那么我们先来看看这个局部变量的定义以及初始化。

 private readonly IRepository<Product> _productRepository;
 private readonly IRepository<VehicleInformation> _vehicleInformationRepository;

  上面是局部变量的定义,在我们的示例代码中我们使用ABP框架来作为整个项目代码的基础框架,这里的IRepository接口来自于ABP框架中定义的接口类型用于直接操作数据库表,这里具体的实现就是通过构造函数来进行注入的,具体请参考下面的实例。

public DegradedVehicleContractService(IObjectMapper objectMapper,
 IRepository<DegradedVehicleContract> degradedVehicleContractRepository,
 IRepository<Product> productRepository,
 IRepository<VehicleInformation> vehicleInformationRepository,
 IRepository<Company> companyRepository,
 IDegradedVehicleContractManager degradedVehicleContractManager,
 IRepository<ProductAffiProductCategory> productAffiProductCategoryRepository,
 IRepository<ProductCategoryBusinessDomain> productCategoryBusinessDomainRepository,
 IRepository<TiledProductCategory> tiledProductCategoryRepository,
 IRepository<BusinessDomain> businessDomainRepository,
 IMapper autoMapper) {
 _objectMapper = objectMapper;
 _degradedVehicleContractRepository = degradedVehicleContractRepository;
 _productRepository = productRepository;
 _vehicleInformationRepository = vehicleInformationRepository;
 _companyRepository = companyRepository;
 _degradedVehicleContractManager = degradedVehicleContractManager;
 _productAffiProductCategoryRepository = productAffiProductCategoryRepository;
 _productCategoryBusinessDomainRepository = productCategoryBusinessDomainRepository;
 _tiledProductCategoryRepository = tiledProductCategoryRepository;
 _businessDomainRepository = businessDomainRepository;
 _autoMapper = autoMapper;
 }

  有了上面的注释,相信你对上面那部分代码可以有更加深入的理解,回到正题,这里到底是Product实体中的问题还是VehicleInformation实体中存在问题呢?我们首先将

join vehicleDict in _vehicleInformationRepository.GetAll().Where(v => vinLists.Contains(v.Vin))   on detail.Vin equals vehicleDict.Vin

  这个部分注释掉,然后再调试代码,我们发现代码竟然不报错了,然后初步判断VehicleInformation这张表里面有问题,然后我们接着注释掉第二部分而保留第三部分,其中注释的部分代码为:

join product in _productRepository.GetAll().Where(p => productIds.Contains(p.Id)) on detail.ProductId equals product.Id

  经过这部分的操作以后,我们发现执行报错,有了这两步的验证之后我们更加确认是VehicleInformation表中存在类型不匹配的问题,然后我们接着往下进行分析。

2.3 定位报错字段

  既然报错信息中是SqlTypes.SqlString'和SqlTypes.SqlGuid之间的转换有问题那么我们断定有一个字段数据库中定义的类型和实体中定义的类型不匹配,而且其中有一种是guid类型,由于我们的实体中guid类型的字段要少于string类型的字段,所以我们首先从guid类型下手,我们看看VehicleInformation中是否有哪种guid类型和数据库不匹配。然后还真的发现了这个类型 public Guid UnionId { get; set; },在我们的实体中定义了一个guid类型的字段UnionId这个是在迁移过程迁移到数据库的,然后我们来查看数据库中的类型。

  通过查询数据库我们发现数据库中字段UnionId被定义成了varchar(50)类型,明显在和代码中guid类型进行匹配的时候会发生错误,后来我很疑惑我们的开发模式是采用Code First来进行开发的,现有实体然后再通过Migration进行数据库迁移的,应该不会出现这样的错误,事后得知是另外一位同事在开发的过程中手动去更改了这个实体的类型从而导致了这个问题,最后更改数据库UnionId字段类型,然后发现错误消失,至此问题解决。

总结

  这篇文章写作的主要目的是如果从一个大致方向来一步步去缩小错误范围,最终来一步步找出错误的根源,最终来解决问题,在这个过程中通过注释掉部分代码来缩小判断范围确实非常有用,另外用到的一个重要的知道思想就是“大胆假设小心求证”的思想来一步步分析问题,然后找到问题的根源,最终解决问题,所以有了上述分析问题解决问题的方法,我们就能够以后解决这一类型的问题,真正做到掌握这一类型问题的解决方法。

以上就是记一次EFCore类型转换错误及解决方案的详细内容,更多关于EFCore类型转换错误及解决方案的资料请关注我们其它相关文章!

(0)

相关推荐

  • EFCore 通过实体Model生成创建SQL Server数据库表脚本

    在我们的项目中经常采用Model First这种方式先来设计数据库Model,然后通过Migration来生成数据库表结构,有些时候我们需要动态通过实体Model来创建数据库的表结构,特别是在创建像临时表这一类型的时候,我们直接通过代码来进行创建就可以了不用通过创建实体然后迁移这种方式来进行,其实原理也很简单就是通过遍历当前Model然后获取每一个属性并以此来生成部分创建脚本,然后将这些创建的脚本拼接成一个完整的脚本到数据库中去执行就可以了,只不过这里有一些需要注意的地方,下面我们来通过代码来一

  • .Net Core中使用ref和Span<T>提高程序性能的实现代码

    一.前言 其实说到ref,很多同学对它已经有所了解,ref是C# 7.0的一个语言特性,它为开发人员提供了返回本地变量引用和值引用的机制. Span也是建立在ref语法基础上的一个复杂的数据类型,在文章的后半部分,我会有一个例子说明如何使用它. 二.ref关键字 不论是ref还是out关键,都是一种比较难以理解和操作的语言特性,如C语言中操作指针一样,这样的高级语法总是什么带来一些副作用,但是我不认为这有什么,而且不是每一个C#开发者都要对这些内部运行的机制有着深刻的理解,我觉得不论什么复杂的东

  • 在.NET Core类库中使用EF Core迁移数据库到SQL Server的方法

    前言 如果大家刚使用EntityFramework Core作为ORM框架的话,想必都会遇到数据库迁移的一些问题. 起初我是在ASP.NET Core的Web项目中进行的,但后来发现放在此处并不是很合理,一些关于数据库的迁移,比如新增表,字段,修改字段类型等等,不应该和最上层的Web项目所关联,数据的迁移文件放到这里也感觉有点多余,有点乱乱的感觉,所以才想着单独出来由专门的项目进行管理会比较好,也比较清晰! 注意目标框架选择的是.NET Core 2.0而不是.NET Standard 2.0.

  • .net core实用技巧——将EF Core生成的SQL语句显示在控制台中

    前言 笔者最近在开发和维护一个.NET Core项目,其中使用几个非常有意思的.NET Core相关的扩展,在此总结整理一下. EF Core性能调优 如果你的项目中使用了EF Core, 且正在处于性能调优阶段,那么了解EF Core生成的SQL语句是非常关键的.那么除了使用第三方工具,如何查看EF Core生成的SQL语句呢?这里笔者将给出一个基于.NET Core内置日志组件的实现方式. 创建一个实例项目 我们首先建一个控制台程序,在主程序中我们编写了一个最简单的EF查询. class P

  • .net EF Core专题:EF Core 读取数据时发生了什么?

    原文:https://bit.ly/2UMiDLb 作者:Jon P Smith 翻译:王亮 声明:我翻译技术文章不是逐句翻译的,而是根据我自己的理解来表述的.其中可能会去除一些本人实在不知道如何组织但又不影响理解的句子. 本文将为你详细描绘 EF Core 从数据库中读取数据的"幕后"视图.我将揭开两种数据库读取方式的面纱:一个是普通的查询,另一个是使用 AsNoTracking 方法的非跟踪查询.我还将通过一个实验来演示我是如何解决我的一个客户遇到的性能问题. 我假设你对 EF C

  • CodeFirst从零开始搭建Asp.Net Core2.0网站

    一步步教大家如何搭建Asp.Net Core2.0网站,以下所有都是建立在.NETCore2.0环境已经搭建好 右键解决方案>新建项目> 选择Web>ASP.NETCoreWeb应用程序(.NET Core) 选择Web应用程序,暂时不选择启用Docker,身份验证选择个人用户账户(会自动生成一系列和用户认证的代码) 随后生代码层次目录如下: 其中会包含身份信息的相关实现,比如相关实体信息(user)之类的,如果想对扩展微软自动的生成的用户实体类,可在Models中的Applicatio

  • 详解.Net Core 权限验证与授权(AuthorizeFilter、ActionFilterAttribute)

    在.Net Core 中使用AuthorizeFilter或者ActionFilterAttribute来实现登录权限验证和授权 一.AuthorizeFilter 新建授权类AllowAnonymous继承AuthorizeFilter,IAllowAnonymousFilter public class AllowAnonymous : AuthorizeFilter, IAllowAnonymousFilter { } 新建拦截类继承AuthorizeFilter public class

  • .NET Core类库System.Reflection.DispatchProxy实现简易Aop的方法

    前言 aop即是面向切面编程,众多Aop框架里Castle是最为人所知的,另外还有死去的Spring.NET,当然,.NET Core社区新秀AspectCore在性能与功能上都非常优秀,已经逐渐被社区推崇和有越来越多的人使用.感谢柠檬同学的礼物! 如果大家出于自身需求或者学习,想实现一个Aop,是不是觉得一来就要使用Emit去做?最近我了解到了System.Reflection.DispatchProxy这个corefx类库,已经实现了动态代理功能. 1|1System.Reflection.

  • 从EFCore上下文的使用到深入剖析DI的生命周期最后实现自动属性注入

    故事背景 最近在把自己的一个老项目从Framework迁移到.Net Core 3.0,数据访问这块选择的是EFCore+Mysql.使用EF的话不可避免要和DbContext打交道,在Core中的常规用法一般是:创建一个XXXContext类继承自DbContext,实现一个拥有DbContextOptions参数的构造器,在启动类StartUp中的ConfigureServices方法里调用IServiceCollection的扩展方法AddDbContext,把上下文注入到DI容器中,然后

  • .net core EF Core调用存储过程的方式

    前言 在这里,我们将尝试去学习一下 .net core EF Core 中调用存储过程. 我们知道,EF Core 是不支持直接调用存储过程的,那它又提供了什么样的方式去执行存储过程呢?有如下方法: 1.FromSql,官方文档 DbSet<TEntity>.FromSql() 2.执行SQl命令 DbContext.Database.ExecuteSqlCommand() 但是,这两种方式都有局限性: 1.FromSql方式的结果一定要是实体类型,就是数据库表映射的模型.这意味着,执行存储过

  • 如何在Asp.Net Core中集成Refit

    在很多时候我们在不同的服务之间需要通过HttpClient进行及时通讯,在我们的代码中我们会创建自己的HttpClient对象然后去跨领域额进行数据的交互,但是往往由于一个项目有多个人开发所以在开发中没有人经常会因为不同的业务请求去写不同的代码,然后就会造成各种风格的HttpClient的跨域请求,最重要的是由于每个人对HttpClient的理解程度不同所以写出来的代码可能质量上会有参差不齐,即使代码能够达到要求往往也显得非常臃肿,重复高我们在正式介绍Refit这个项目之前,我们来看看我们在项目

随机推荐