OData 101: Using the [NotMapped] attribute to exclude Enum properties
TL;DR: OData does not currently support enum types, and WCF Data Services throws an unclear exception when using the EF provider with a model that has an enum property. Mark the property with a [NotMapped]
attribute to get around this limitation.
In today’s OData 101, we’ll take a look at a problem you might run into if you have an Entity Framework provider and are using Entity Framework 5.0+.
The Problem
The problem lies in the fact that Entity Framework and WCF Data Services use a common format to describe the data model: the Entity Data Model (EDM). In Entity Framework 5.0, the EF team made some modifications to MC-CSDL, the specification that codifies CSDL, the best-known serialization format for an EDM. Among other changes, the EnumType element was added to the specification. An EF 5.0 model in a project that targets .NET 4.5 will allow developers to add enum properties. However, WCF Data Services hasn’t yet done the work to implement support for enum properties. (And the OData protocol doesn’t have a mature understanding of how enums should be implemented yet; this is something we’re working through with OASIS.)
If you are trying to use an EF model that includes an enum property with WCF Data Services, you’ll get the following error:
The server encountered an error processing the request. The exception message is 'Value cannot be null. Parameter name: propertyResourceType'. See server logs for more details. The exception stack trace is:
at System.Data.Services.WebUtil.CheckArgumentNull[T](T value, String parameterName) at System.Data.Services.Providers.ResourceProperty..ctor(String name, ResourcePropertyKind kind, ResourceType propertyResourceType) at System.Data.Services.Providers.ObjectContextServiceProvider.PopulateMemberMetadata(ResourceType resourceType, IProviderMetadata workspace, IDictionary2 knownTypes, PrimitiveResourceTypeMap primitiveResourceTypeMap) at System.Data.Services.Providers.ObjectContextServiceProvider.PopulateMetadata(IDictionary2 knownTypes, IDictionary2 childTypes, IDictionary2 entitySets) at System.Data.Services.Providers.BaseServiceProvider.PopulateMetadata() at System.Data.Services.Providers.BaseServiceProvider.LoadMetadata() at System.Data.Services.DataService1.CreateMetadataAndQueryProviders(IDataServiceMetadataProvider& metadataProviderInstance, IDataServiceQueryProvider& queryProviderInstance, BaseServiceProvider& builtInProvider, Object& dataSourceInstance) at System.Data.Services.DataService1.CreateProvider() at System.Data.Services.DataService1.HandleRequest() at System.Data.Services.DataService1.ProcessRequestForMessage(Stream messageBody) at SyncInvokeProcessRequestForMessage(Object , Object[] , Object[] ) at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs) at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc) at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc) at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage41(MessageRpc& rpc) at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc& rpc) at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc) at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(MessageRpc& rpc) at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(MessageRpc& rpc) at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc& rpc) at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(MessageRpc& rpc) at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
Fortunately this error is reasonably easy to workaround.
Workaround
The first and most obvious workaround should be to consider removing the enum property from the EF model until WCF Data Services provides proper support for enums.
If that doesn’t work because you need the enum in business logic, you can use the [NotMapped]
attribute from System.ComponentModel.DataAnnotations.Schema
to tell EF not to expose the property in EDM.
For example, consider the following code:
using System; using System.ComponentModel.DataAnnotations.Schema; namespace WcfDataServices101.UsingTheNotMappedAttribute { public class AccessControlEntry { public int Id { get; set; } // An Enum property cannot be mapped to an OData feed, so it must be // explicitly excluded with the [NotMapped] attribute. [NotMapped] public FileRights FileRights { get; set; } // This property provides a means to serialize the value of the FileRights // property in an OData-compatible way. public string Rights { get { return FileRights.ToString(); } set { FileRights = (FileRights)Enum.Parse(typeof(FileRights), value); } } } [Flags] public enum FileRights { Read = 1, Write = 2, Create = 4, Delete = 8 } }
In this example, we’ve marked the enum property with the [NotMapped]
attribute and have created a manual serialization property which will help to synchronize an OData-compatible value with the value of the enum property.
Implications
There are a few implications of this workaround:
- The
[NotMapped]
attribute also controls whether EF will try to create the value in the underlying data store. In this case, our table in SQL Server would have an nvarchar column called Rights that stores the value of the Rights property, but not an integer column that stores the value of FileRights. - Since the FileRights column is not stored in the database, LINQ-to-Entities queries that use that value will not return the expected results. This is a non-issue if your EF model is only queried through the WCF Data Service.
Result
The result of this workaround is an enumeration property that can be used in business logic in the service code, for instance, in a FunctionImport
. Since the [NotMapped]
attribute prevents the enum value from serializing, your OData service will continue to operate as expected and you will be able to benefit from enumerated values.
In the example above, there is an additional benefit of constraining the possible values for Rights to some combination of the values of the FileRights
enum. An invalid value (e.g., Modify) would be rejected by the server since Enum.Parse
will throw an ArgumentException
.
Comments
Anonymous
October 08, 2012
Good grief ... can you guys ever get the EF, MVC, Web API, and WCF Data Services stuff aligned? This has been going on for YEARS at this stage. The whole thing is a colossal, untrammeled, MESS. I've been writing software for nearly 25 years and I've never seen such a badly-managed, unsynchronized, disaster. Every few weeks one of these teams throws out some new "feature" that the others don't yet support. And the guidance for architecting even the simplest n-tier application? Laughable. Get it together.Anonymous
November 26, 2012
I found this useful, even because it's the only actual way to get the EF 5 working with oData. ThanksAnonymous
November 29, 2012
Is there an official bug# for tracking this on the EF CodePlex site?Anonymous
September 27, 2013
Will this be fixed when either EF 6 comes out or VS 2013? I assume at that same time WCF Data Services will have an update (seems to still be an issue with 5.6)?