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赋值生成的查询表达式所致,简略实体定义参考如下:
////// 消息处理状态/// public enum HandleStatus{ ////// 成功 /// ///0 Success = 0, ////// 失败 /// ///1 Failed,}////// 消息处理队列查询条件/// public sealed class HandleQueueCondition{ ////// 发送队列状态 /// public NullableStatus { get; set; }}/// /// 消息处理队列项/// [Table("Core_Messaging_HandleQueue"), Serializable]public sealed class HandleQueueItem{ ////// 发送状态 /// [NotMapped] public HandleStatus Status { get { return (HandleStatus)this.StatusValue; } set { this.StatusValue = (Int32)value; } } ////// 发送状态值 /// ///用户EF的实体映射 [Column("Status")] public Int32 StatusValue { get; set; }}
为了解决EF 5.0不支持枚举值的现状,我们通过一个中间值保存返回它。生成表达式的部分代码如下:
////// 查询列表/// /// 数据库上下文/// 查询条件///查询列表 private IEnumerableQueryList(MessagingDbContext dbContext, HandleQueueCondition condition){ Expression > predicate = PredicateExtension.True (); if (condition.Status.HasValue) predicate = predicate.And (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内部。