记⼀次EFCore类型转换错误及解决⽅案
⽬录
⼀背景
⼆解决⽅案
2.1 定位报错位置
2.2 定位产⽣错误的表名称
2.3 定位报错字段
总结
⼀背景
  今天在使⽤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._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\ders\Application.Sales.Orders\SalesOrder\DegradedVehicleContracts\DegradedVehicleC  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._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
union是什么类型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类型转换错误及解决⽅案的资料请关注其它相关⽂章!