记一次EFCore类型转换错误及解决方案
一 背景
今天在使用EntityFrameworkCore查询的时候在调试的时候总是提示如下错误:Unabletocastobjectoftype'System.Data.SqlTypes.SqlString'totype'System.Data.SqlTypes.SqlGuid'第一次看这个报错肯定是数据库实体和EFCore中定义的某种类型不匹配从而导致类型转换错误,但是业务涉及到这么多的实体Entity,那么到底是哪里类型无法匹配呢?所以第一步肯定是调试代码,然后看报错信息,这里我们首先贴出完整的报错信息,从而方便自己分析具体问题。
System.InvalidCastException:Unabletocastobjectoftype'System.Data.SqlTypes.SqlString'totype'System.Data.SqlTypes.SqlGuid'. atSystem.Data.SqlClient.SqlBuffer.get_SqlGuid() atSystem.Data.SqlClient.SqlDataReader.GetGuid(Int32i) atlambda_method(Closure,DbDataReader) atMicrosoft.EntityFrameworkCore.Storage.Internal.TypedRelationalValueBufferFactory.Create(DbDataReaderdataReader) atMicrosoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.BufferlessMoveNext(DbContext_,Booleanbuffer) atMicrosoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TStatestate,Func`3operation,Func`3verifySucceeded) atMicrosoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.MoveNext() atMicrosoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider._TrackEntities[TOut,TIn](IEnumerable`1results,QueryContextqueryContext,IList`1entityTrackingInfos,IList`1entityAccessors)+MoveNext() atMicrosoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext() atSystem.Linq.Lookup`2.CreateForJoin(IEnumerable`1source,Func`2keySelector,IEqualityComparer`1comparer) atSystem.Linq.Enumerable.JoinIterator[TOuter,TInner,TKey,TResult](IEnumerable`1outer,IEnumerable`1inner,Func`2outerKeySelector,Func`2innerKeySelector,Func`3resultSelector,IEqualityComparer`1comparer)+MoveNext() atSystem.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1source,Func`2keySelector,Func`2elementSelector,IEqualityComparer`1comparer) atSystem.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1source,Func`2keySelector,Func`2elementSelector) atSunlight.Dcs.Application.Sales.SalesOrder.DegradedVehicleContracts.DegradedVehicleContractService.QueryByIdAsync(Int32id)inE:\63318\sales-service\src\sales.orders\Application.Sales.Orders\SalesOrder\DegradedVehicleContracts\DegradedVehicleContractService.cs:line99 atAbp.Threading.InternalAsyncHelper.AwaitTaskWithPostActionAndFinallyAndGetResult[T](Task`1actualReturnValue,Func`1postAction,Action`1finalAction) atSunlight.Dcs.WebApi.Sales.Controllers.Orders.DegradedVehicleContractController.QueryById(Int32id)inE:\63318\sales-service\src\WebApi.Sales\Controllers\Orders\DegradedVehicleContractController.cs:line50 atMicrosoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMappermapper,ObjectMethodExecutorexecutor,Objectcontroller,Object[]arguments) atSystem.Threading.Tasks.ValueTask`1.get_Result() atMicrosoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync() atMicrosoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync() atMicrosoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContextcontext) atMicrosoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State&next,Scope&scope,Object&state,Boolean&isCompleted) atMicrosoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync() atMicrosoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync()
二 解决方案
有了上面的报错信息我们就能够知道大致方向,接下来我们首先来看看报错信息的这段代码。
publicasyncTaskQueryByIdAsync(intid){ varqueryResult=await_degradedVehicleContractRepository.GetAll() .Include(d=>d.DegradedVehicleContractDetails) .SingleOrDefaultAsync(mp=>mp.Id==id); if(queryResult==null) thrownewValidationException($"当前Id为:{id}的降级车合同没有找到"); varresult=_objectMapper.Map (queryResult); result.Details=_objectMapper.Map >(queryResult.DegradedVehicleContractDetails); //获取ProductId varproductIds=queryResult.DegradedVehicleContractDetails.Select(d=>d.ProductId).Distinct().ToArray(); //车辆Id varvinLists=queryResult.DegradedVehicleContractDetails.Select(r=>r.Vin).Distinct().ToArray(); vardetailResult=(fromdetailinqueryResult.DegradedVehicleContractDetails joinproductin_productRepository.GetAll().Where(p=>productIds.Contains(p.Id)) ondetail.ProductIdequalsproduct.Id joinvehicleDictin_vehicleInformationRepository.GetAll().Where(v=>vinLists.Contains(v.Vin)) ondetail.VinequalsvehicleDict.Vin selectnew{ 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; }); returnresult; }
2.1定位报错位置
通过直接对代码进行调试,我们发现只要代码执行获取detailResult这一步的时候就会出现上面的错误,那么到这里我们就可以推断错误的地方就在这里了,所以后面我们的重点就是分析这段代码。
2.2定位产生错误的表名称
这里就是利用前面的Include方法来查询到queryResult结果,然后利用queryResult.DegradedVehicleContractDetails来和Product以及VehicleInformation表来做innerjoin,这里你可能对_productRepository以及_vehicleInformationRepository这个局部变量不是十分熟悉,那么我们先来看看这个局部变量的定义以及初始化。
privatereadonlyIRepository_productRepository; privatereadonlyIRepository _vehicleInformationRepository;
上面是局部变量的定义,在我们的示例代码中我们使用ABP框架来作为整个项目代码的基础框架,这里的IRepository接口来自于ABP框架中定义的接口类型用于直接操作数据库表,这里具体的实现就是通过构造函数来进行注入的,具体请参考下面的实例。
publicDegradedVehicleContractService(IObjectMapperobjectMapper, IRepositorydegradedVehicleContractRepository, IRepository productRepository, IRepository vehicleInformationRepository, IRepository companyRepository, IDegradedVehicleContractManagerdegradedVehicleContractManager, IRepository productAffiProductCategoryRepository, IRepository productCategoryBusinessDomainRepository, IRepository tiledProductCategoryRepository, IRepository businessDomainRepository, IMapperautoMapper){ _objectMapper=objectMapper; _degradedVehicleContractRepository=degradedVehicleContractRepository; _productRepository=productRepository; _vehicleInformationRepository=vehicleInformationRepository; _companyRepository=companyRepository; _degradedVehicleContractManager=degradedVehicleContractManager; _productAffiProductCategoryRepository=productAffiProductCategoryRepository; _productCategoryBusinessDomainRepository=productCategoryBusinessDomainRepository; _tiledProductCategoryRepository=tiledProductCategoryRepository; _businessDomainRepository=businessDomainRepository; _autoMapper=autoMapper; }
有了上面的注释,相信你对上面那部分代码可以有更加深入的理解,回到正题,这里到底是Product实体中的问题还是VehicleInformation实体中存在问题呢?我们首先将
joinvehicleDictin_vehicleInformationRepository.GetAll().Where(v=>vinLists.Contains(v.Vin)) ondetail.VinequalsvehicleDict.Vin
这个部分注释掉,然后再调试代码,我们发现代码竟然不报错了,然后初步判断VehicleInformation这张表里面有问题,然后我们接着注释掉第二部分而保留第三部分,其中注释的部分代码为:
joinproductin_productRepository.GetAll().Where(p=>productIds.Contains(p.Id))ondetail.ProductIdequalsproduct.Id
经过这部分的操作以后,我们发现执行报错,有了这两步的验证之后我们更加确认是VehicleInformation表中存在类型不匹配的问题,然后我们接着往下进行分析。
2.3定位报错字段
既然报错信息中是SqlTypes.SqlString'和SqlTypes.SqlGuid之间的转换有问题那么我们断定有一个字段数据库中定义的类型和实体中定义的类型不匹配,而且其中有一种是guid类型,由于我们的实体中guid类型的字段要少于string类型的字段,所以我们首先从guid类型下手,我们看看VehicleInformation中是否有哪种guid类型和数据库不匹配。然后还真的发现了这个类型 publicGuidUnionId{get;set;},在我们的实体中定义了一个guid类型的字段UnionId这个是在迁移过程迁移到数据库的,然后我们来查看数据库中的类型。
通过查询数据库我们发现数据库中字段UnionId被定义成了varchar(50)类型,明显在和代码中guid类型进行匹配的时候会发生错误,后来我很疑惑我们的开发模式是采用CodeFirst来进行开发的,现有实体然后再通过Migration进行数据库迁移的,应该不会出现这样的错误,事后得知是另外一位同事在开发的过程中手动去更改了这个实体的类型从而导致了这个问题,最后更改数据库UnionId字段类型,然后发现错误消失,至此问题解决。
总结
这篇文章写作的主要目的是如果从一个大致方向来一步步去缩小错误范围,最终来一步步找出错误的根源,最终来解决问题,在这个过程中通过注释掉部分代码来缩小判断范围确实非常有用,另外用到的一个重要的知道思想就是“大胆假设小心求证”的思想来一步步分析问题,然后找到问题的根源,最终解决问题,所以有了上述分析问题解决问题的方法,我们就能够以后解决这一类型的问题,真正做到掌握这一类型问题的解决方法。
以上就是记一次EFCore类型转换错误及解决方案的详细内容,更多关于EFCore类型转换错误及解决方案的资料请关注毛票票其它相关文章!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。