EF使用版本File Version:4.4.20627.0、Product Version:5.0.0.net40,今天在其中一个测试环境(Windows Server 2008 R2、.NET Framework 4.0)发现执行一个查询会抛出如下异常信息(由于寄宿体的原因这个异常通过sos的!StopOnException命令还无法直接捕捉):
System.ArgumentException: The specified value is not an instance of type 'Edm.Int32' Parameter name: value at System.Data.Common.CommandTrees.ExpressionBuilder.Internal.ArgumentValidation.ValidateConstant(TypeUsage constantType, Object value) at System.Data.Objects.ELinq.ExpressionConverter.ConstantTranslator.TypedTranslate(ExpressionConverter parent, ConstantExpression linq) at System.Data.Objects.ELinq.ExpressionConverter.MemberAccessTranslator.TypedTranslate(ExpressionConverter parent, MemberExpression linq) at System.Data.Objects.ELinq.ExpressionConverter.UnaryTranslator.TypedTranslate(ExpressionConverter parent, UnaryExpression linq) at System.Data.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq) at System.Data.Objects.ELinq.ExpressionConverter.EqualsTranslator.TypedTranslate(ExpressionConverter parent, BinaryExpression linq) at System.Data.Objects.ELinq.ExpressionConverter.BitwiseBinaryTranslator.TypedTranslate(ExpressionConverter parent, BinaryExpre...
经过排查缩减导致这个错误的原因是一个Int32赋值生成的查询表达式所致,简略实体定义参考如下:
/// <summary> /// 消息处理状态 /// </summary> public enum HandleStatus { /// <summary> /// 成功 /// </summary> /// <value>0</value> Success = 0, /// <summary> /// 失败 /// </summary> /// <value>1</value> Failed, } /// <summary> /// 消息处理队列查询条件 /// </summary> public sealed class HandleQueueCondition { /// <summary> /// 发送队列状态 /// </summary> public Nullable<HandleStatus> Status { get; set; } } /// <summary> /// 消息处理队列项 /// </summary> [Table("Core_Messaging_HandleQueue"), Serializable] public sealed class HandleQueueItem { /// <summary> /// 发送状态 /// </summary> [NotMapped] public HandleStatus Status { get { return (HandleStatus)this.StatusValue; } set { this.StatusValue = (Int32)value; } } /// <summary> /// 发送状态值 /// </summary> /// <remarks>用户EF的实体映射</remarks> [Column("Status")] public Int32 StatusValue { get; set; } }
为了解决EF 5.0不支持枚举值的现状,我们通过一个中间值保存返回它。生成表达式的部分代码如下:
/// <summary> /// 查询列表 /// </summary> /// <param name="dbContext">数据库上下文</param> /// <param name="condition">查询条件</param> /// <returns>查询列表</returns> private IEnumerable<HandleQueueItem> QueryList(MessagingDbContext dbContext, HandleQueueCondition condition) { Expression<Func<HandleQueueItem, Boolean>> predicate = PredicateExtension.True<HandleQueueItem>(); if (condition.Status.HasValue) predicate = predicate.And<HandleQueueItem>(p => p.StatusValue == (Int32)condition.Status.Value); return dbContext.HandleQueueItems.Where(predicate) .OrderByDescending(p => p.DateCreated) .Skip(condition.PageIndex * condition.PageSize) .Take(condition.PageSize) .ToList(); }
PredicateExtension是针对Expression<Func<T, Boolean>>的扩展,用于合并多个条件表达式。用WinDbg + sos调试,设置断点在System.Data.Entity.dll的System.Data.Common.CommandTrees.ExpressionBuilder.Internal.ArgumentValidation.ValidateConstant函数上发现constantType的名称是Edm.Int32、而value并不是转换后的Int32值而是枚举类型HandleStatus。相同的代码在安装.NET Framework 4.5的测试环境没有问题,通过升级到4.5版本可以解决这个缺陷。
4.0和4.5对于包含转换层级的函数解析表达式都会失败(比如:Convert.Toxxx),但是对于一个显示转换则不同,看更简单的代码段:
dbContext.HandleQueueItems.Where(p => p.StatusValue == Convert.ToInt32(condition.Status.Value)) .OrderByDescending(p => p.DateCreated) .Skip(condition.PageIndex * condition.PageSize) .Take(condition.PageSize) .ToList();
显示转换下两个版本输出的表达式相同((p.StatusValue == Convert(value(ConsoleApplication1.Program+<>c__DisplayClass0).condition.Status.Value))):
dbContext.HandleQueueItems.Where(p => p.StatusValue == ((Int32)condition.Status.Value)) .OrderByDescending(p => p.DateCreated) .Skip(condition.PageIndex * condition.PageSize) .Take(condition.PageSize) .ToList();
真正的变化在于System.Data.Entity的System.Data.Objects.ELinq.ExpressionConverter内部。