記一次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字段類型

  通過查詢數據庫我們發現數據庫中字段UnionId被定義成了varchar(50)類型,明顯在和代碼中guid類型進行匹配的時候會發生錯誤,后來我很疑惑我們的開發模式是采用Code First來進行開發的,現有實體然后再通過Migration進行數據庫遷移的,應該不會出現這樣的錯誤,事后得知是另外一位同事在開發的過程中手動去更改了這個實體的類型從而導致了這個問題,最后更改數據庫UnionId字段類型,然后發現錯誤消失,至此問題解決。

  總結

  這篇文章寫作的主要目的是如果從一個大致方向來一步步去縮小錯誤范圍,最終來一步步找出錯誤的根源,最終來解決問題,在這個過程中通過注釋掉部分代碼來縮小判斷范圍確實非常有用,另外用到的一個重要的知道思想就是“大膽假設小心求證”的思想來一步步分析問題,然后找到問題的根源,最終解決問題,所以有了上述分析問題解決問題的方法,我們就能夠以后解決這一類型的問題,真正做到掌握這一類型問題的解決方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM