9

Closed

EF cross checks EDM and CLR enum members in some cases

description

We relaxed requirements for matching EDM and CLR enum types so that the EDM type does not have to have all members that the CLR type has. However we missed a check we had in DbExpressions. It shows up when trying to construct an array of enum const values. Reported on stack overflow (http://stackoverflow.com/questions/13049202/reference-external-type-enum-and-contains-method#comment17742729_13049202). Note this is for ModelFirst/DatabaseFirst only. CodeFirst always creates enum types with all the members.

EDM enum definition
<EnumType Name="SomeEnum"  />
Clr enum definition
public enum SomeEnum : int
{
    value1 = 0,
    value2 = 1
}

Query:
var enumList = new List<SomeEnum>() { SomeEnum.value1, SomeEnum.value2 };
var items = context.Table1.Where(e => enumList.Contains(e.EnumField));
Exception:
The type 'SomeEnum' does not match the EDM enumeration type 'SomeEnum' or its underlying  type 'Int32' Parameter name: value. 
Stack trace:
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.TypedTranslator1.Translate(ExpressionConverter parent, Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.NewArrayInitTranslator.<>c__DisplayClass88.<TypedTranslate>b__86(Expression e)
at System.Linq.Enumerable.WhereSelectEnumerableIterator
2.MoveNext()
at System.Data.Common.CommandTrees.ExpressionBuilder.Internal.EnumerableValidator3.Validate(IEnumerable1 argument, String argumentName, Int32 expectedElementCount, Boolean allowEmpty, Func3 map, Func2 collect, Func3 deriveName)
at System.Data.Common.CommandTrees.ExpressionBuilder.Internal.EnumerableValidator
3.Validate()
at System.Data.Common.CommandTrees.ExpressionBuilder.Internal.ArgumentValidation.CreateExpressionList(IEnumerable1 arguments, String argumentName, Boolean allowEmpty, Action2 validationCallback)
at System.Data.Common.CommandTrees.ExpressionBuilder.Internal.ArgumentValidation.ValidateNewCollection(IEnumerable1 elements, DbExpressionList& validElements)
at System.Data.Common.CommandTrees.ExpressionBuilder.DbExpressionBuilder.CreateNewCollection(IEnumerable
1 elements)
at System.Data.Objects.ELinq.ExpressionConverter.NewArrayInitTranslator.TypedTranslate(ExpressionConverter parent, NewArrayExpression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TypedTranslator1.Translate(ExpressionConverter parent, Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.ConstantTranslator.TypedTranslate(ExpressionConverter parent, ConstantExpression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TypedTranslator
1.Translate(ExpressionConverter parent, Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.ContainsTranslator.TranslateContains(ExpressionConverter parent, Expression sourceExpression, Expression valueExpression)
at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.ContainsTranslator.Translate(ExpressionConverter parent, MethodCallExpression call)
at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.SequenceMethodTranslator.Translate(ExpressionConverter parent, MethodCallExpression call, SequenceMethod sequenceMethod)
at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TypedTranslator1.Translate(ExpressionConverter parent, Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TranslateLambda(LambdaExpression lambda, DbExpression input)
at System.Data.Objects.ELinq.ExpressionConverter.TranslateLambda(LambdaExpression lambda, DbExpression input, DbExpressionBinding& binding)
at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.OneLambdaTranslator.Translate(ExpressionConverter parent, MethodCallExpression call, DbExpression& source, DbExpressionBinding& sourceBinding, DbExpression& lambda)
at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.OneLambdaTranslator.Translate(ExpressionConverter parent, MethodCallExpression call)
at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.SequenceMethodTranslator.Translate(ExpressionConverter parent, MethodCallExpression call, SequenceMethod sequenceMethod)
at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TypedTranslator
1.Translate(ExpressionConverter parent, Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.Convert()
at System.Data.Objects.ELinq.ELinqQueryState.GetExecutionPlan(Nullable1 forMergeOption)
at System.Data.Objects.ObjectQuery
1.GetResults(Nullable1 forMergeOption)
at System.Data.Objects.ObjectQuery
1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
at System.Data.Entity.Internal.Linq.InternalQuery`1.GetEnumerator()

The fix is to change the verification logic to be similar what we have in mapping - we just need to check that any member of EDM enum type exists on the CLR enum type (and the values are the same) but not the other way around.
Closed May 6, 2013 at 5:00 PM by lukew
Verified fixed

comments

Paluth wrote Jan 30, 2013 at 10:50 AM

I don't really see how this issue has a medium impact. It makes using enums almost impossible. Together with Bug 532 that hasn't been released on nuget yet, I think that the best solution is rewriting my code to completely stop using enums in Entity Models.

moozzyk wrote Jan 30, 2013 at 4:23 PM

There is an easy workaround for this one - specify all the enum members you have on the CLR enum type in the CSDL. In addition it seems to affect only a limited number of scenarios. Bug 532 was fixed in EF6 and you can get a version containing the fix from nuget. The fix for EF5 had to be made in .NET Framework which has a different shipping model. For EF5 you can expect to see the fix in one of the future updates. Note that this particular bug did not meet the bar for EF5 so most likely it will be fixed in EF6 only.

ahamedminhaj wrote Mar 3, 2013 at 1:01 PM

Not a direct solution, but this workaround is working for my requirements, if you used hefty enums you can always continue with enums. no need to rewriting.

var enumList = new List<int>() { (int)SomeEnum.value1, (int)SomeEnum.value2 };
var items = context.Table1.Where(e => enumList.Contains((int)e.EnumField));

benjaminjnr2k11 wrote Mar 5, 2013 at 7:00 PM

I don't think it's nice to leave this issue unfixed in EF5.
Not everyone will have the luxury of quickly moving to EF6 as soon as that is available.
And for models heavily laced with enums, this is just a bummer.