記一次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,TElement](IEnumerable`1 source,Func`2 keySelectorHVNnLtfOe,Func`2 elementSelector,IEqualityComparer`1 comparer) at System.Linq.Enumerable.ToDictionary[TSource,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 _productReposit程式設計客棧ory.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 joinwww.cppcns.com,這裡你可能對_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;
_co程式設計客棧mpanyRepository = 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這張表裡面有問題,然後我們接著註釋掉第二部分而保留第三部分,其中註釋的部分程式碼為:
http://www.cppcns.comjoin 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型別轉換錯誤及解決方案的資料請關注我們其它相關文章!