記一次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類型轉換錯誤及解決方案的資料請關註WalkonNet其它相關文章!

推薦閱讀: