Entity Framework 4.1 - Dynamic Eager Loading - c#-4.0

I have an Entity Framework model (some properties have been excluded to keep it simple):
public class Media
{
public int MediaID { get; set; }
public ICollection<Track> Tracks { get; set; }
public ICollection<RelatedMedia> RelatedMedias { get; set; }
}
I then have my DbContext:
public class MediaServiceContext : DbContext
{
public DbSet<Media> Medias { get; set; }
}
I can then retrieve data using the following, and it works great:
public Media Media_Get(int id)
{
using (MediaServiceContext mc = new MediaServiceContext())
{
return mc.Medias.Include("Tracks").Include("RelatedMedias").Single(m => m.MediaID == id);
}
}
My question is, I may not want to load one or both of the related entities in some cases, depending on which part of my application is calling this code; how can I make the Includes dynamic?
I have tried this:
public Media Media_Get(int id, bool includeRelated, bool includeTracks)
{
using (MediaServiceContext mc = new MediaServiceContext())
{
IQueryable<Media> query = mc.Medias;
if (includeRelated)
query = query.Include("RelatedMedias");
if (includeTracks)
query = query.Include("Tracks");
return query.Single(m => m.MediaID == id);
}
}
...but I get a 'Specified cast in not valid' exception.
I have also tried this proposed solution, but it produces a 'unable to cast DbQuery to ObjectQuery' exception. Changing the extension method in the linked solution from '(ObjectQuery)source' to '(DbQuery)source' then causes the same 'Specified cast in not valid' exception.
I have hunted high and low for a solution on this but with no luck. Any help would be much appreciated.
Amendment - Here's the stack trace:
at System.Data.SqlClient.SqlBuffer.get_Int64()
at lambda_method(Closure , Shaper )
at System.Data.Common.Internal.Materialization.Coordinator.HasNextElement(Shaper shaper)
at System.Data.Common.Internal.Materialization.Shaper`1.RowNestedResultEnumerator.MoveNext()
at System.Data.Common.Internal.Materialization.Shaper`1.ObjectQueryNestedEnumerator.TryReadToNextElement()
at System.Data.Common.Internal.Materialization.Shaper`1.ObjectQueryNestedEnumerator.MoveNext()
at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source)
at System.Linq.Queryable.SingleOrDefault[TSource](IQueryable`1 source, Expression`1 predicate)
at API.Areas.V1.Models.RetailerManager.Media_Get(Int32 id, String retailerKey, Boolean includeLicenses, Boolean includeProperties, Boolean includeRelated, Boolean includeTracks) in C:\Users\garth\Documents\Development\WebApplications\api\Areas\V1\Models\RetailerManager.cs:line 28
at API.Areas.V1.Controllers.RetailerController.Media(Nullable`1 id, String httpVerb, Boolean includeLicenses, Boolean includeProperties, Boolean includeRelated, Boolean includeTracks) in C:\Users\garth\Documents\Development\WebApplications\api\Areas\V1\Controllers\RetailerController.cs:line 25
at lambda_method(Closure , ControllerBase , Object[] )
at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass15.<InvokeActionMethodWithFilters>b__12()
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation)

Your stack trace shows that .SingleOrDefault() caused this exception, but I don't see .SingleOrDefault() in your code.
I do see this:
return query.Single(m => m.MediaID == id);
Is it possible that Media.MediaID is a long and not an int?
Update
As another alternative to answer your original question, I answered a question a couple of weeks ago in relation to this. The sample code in my answer has to do with dynamic order by, but we use a very similar pattern for dynamic eager loading (see the first comment after my answer).
Instead of a method signature like this:
public Media Media_Get(int id, bool includeRelated, bool includeTracks)
Your signature would look more like this:
public Media Media_Get(MediaGetter mediaGetter)
...and you would use it like this:
var media = someInstance.Media_Get(
new MediaGetter { ID = id, }
.EagerLoad(m => m.Tracks)
.EagerLoad(m => m.RelatedTracks)
);

Related

Automapper throws System.ArgumentException Method T GetValue[T]() is a generic method definition (Parameter 'method')

I am working on a for me rather large blazor web app and use Automapper to simplify mapping. After upgrade to VS 2019 and update to newest nuget packages I get this error:
avsweb.ApplicationTests.Mappings.MappingTests.ShouldHaveValidConfiguration
Source: MappingTests.cs line 69
Duration: 1 ms
Message:
System.ArgumentException : Method T GetValue[T]() is a generic method definition (Parameter 'method')
Stack Trace:
Expression.ValidateMethodInfo(MethodInfo method, String paramName)
Expression.ValidateMethodAndGetParameters(Expression instance, MethodInfo method)
Expression.Call(Expression instance, MethodInfo method)
<>c.<MemberAccesses>b__3_0(Expression inner, MemberInfo getter)
Enumerable.Aggregate[TSource,TAccumulate](IEnumerable`1 source, TAccumulate seed, Func`3 func)
ExpressionFactory.MemberAccesses(IEnumerable`1 members, Expression obj)
TypeMapPlanBuilder.Chain(IMemberMap memberMap, Type destinationType)
TypeMapPlanBuilder.BuildValueResolverFunc(IMemberMap memberMap, Expression destValueExpr, Expression defaultValue)
TypeMapPlanBuilder.CreatePropertyMapFunc(IMemberMap memberMap, Expression destination, MemberInfo destinationMember)
TypeMapPlanBuilder.CreateAssignmentFunc(Expression destinationFunc)
TypeMapPlanBuilder.CreateMapperLambda(HashSet`1 typeMapsPath)
TypeMap.CreateMapperLambda(IConfigurationProvider configurationProvider, HashSet`1 typeMapsPath)
TypeMap.Seal(IConfigurationProvider configurationProvider)
MapperConfiguration.Seal()
MapperConfiguration.ctor(MapperConfigurationExpression configurationExpression)
<>c.<AddAutoMapperClasses>b__12_2(IServiceProvider sp)
CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
ServiceProviderEngineScope.GetService(Type serviceType)
ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
<>c.<AddAutoMapperClasses>b__12_3(IServiceProvider sp)
CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
CallSiteRuntimeResolver.VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context)
CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
ServiceProviderEngine.GetService(Type serviceType)
ServiceProvider.GetService(Type serviceType)
ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
MappingTests.ctor(ITestOutputHelper _outputHelper) line 65
Automapper is registerd using the ServiceCollection Extention. All ValuResolvers are registered through custom service extension.
I have at the moment no glue where to look at. Does anyone hav a hint?
For anyone coming across this post later and "removing the generic method" is not an answer: AutoMapper has a feature to map Pre/PostFixes.
By default it knows the prefix "Get" and tries to map the source method GetValue[T]()to the destination property Value. However, it doesn't know how to map generic methods by default, thus the mapping fails.
To fix this, there are two options:
Rename the method to not include the "Get"
As stated in the documentation below, clear the prefixes using: cfg.ClearPrefixes()
This behaviour is defined briefly here:
https://docs.automapper.org/en/stable/Configuration.html#recognizing-pre-postfixes
OK, found the reasen for problem. To hande key/value pairs I added following class to project:
public class KeyValuePair
{
public string Key { get; set; }
public string Value { get; set; }
public T GetValue<T>()
{
var value = (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromInvariantString(Value);
return value;
}
public void SetValue<T>(T value)
{
Value = value.ToString();
}
}
The method GetValue should provide a way to get the string value converted to object of type T. Through some automation process a mapping profile hase been defined for this class. This finally leads to the Argument exception either when validating mapping or when instantiating automapper (IMapper). I don't know why this error exactly happens, but mapping is for this class not useful anyway. so removing it from profile solved the case.
The hard part of it was to find the failing class out of 50+ classes / dto's. Luckily I create the Automapper profile dynamically during startup from methods in the class definitions. So it was possible to implement a testmethod with xUnit and Memberdata creating a mappingprofile for each single class and verify each single configuration. This did show the failing mappings.
Here a snippet how the map is created:
public partial class SettingDto : IMapperBase<Setting>
{
public void MappingBase(Profile profile)
{
// Generated Mapping
profile.CreateMap<Setting, SettingDto>()
.IncludeAllDerived()
.ForMember(m => m.Id, s => s.MapFrom(id => id.SettingId))
;
}
}
Class Setting derived from KeyValuePair class shown above.
Here a snippet for testing the single map:
[Theory]
[InlineData(typeof(ReychSettingDto),"MappingBase")]
public void SingleClassProfileMapperTest(Type type, string method)
{
// Arrange
logger.Information("Processing {#Name}", type.Name);
var profile = new MappingProfile(type, method) as Profile;
var config = new MapperConfiguration(cfg => { cfg.AddProfile(profile); });
// Act
//Action act = () => config.AssertConfigurationIsValid();
//act.Should().Throw<AutoMapperConfigurationException>();
try
{
config.AssertConfigurationIsValid();
}
catch (AutoMapperConfigurationException aex)
{
logger.Error(aex, "Config");
}
// Assert
}
public static IEnumerable<object[]> GetProfiles(Type mapType, string method, int skip, int count)
{
var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == "avsweb.Application");
var allObjects = assembly.GetExportedTypes().Where(t => t.GetInterfaces().Any(i =>
i.IsGenericType && i.GetGenericTypeDefinition() == mapType))
.Select(o => new object[] { o, method })
.ToList();
if (count < 0)
{
return allObjects;
}
return allObjects.Skip(skip).Take(count);
}
}
public partial class MappingProfile : Profile
{
public MappingProfile(Type type, string method)
{
var instance = Activator.CreateInstance(type);
var methodInfo = type.GetMethod(method);
methodInfo?.Invoke(instance, new object[] { this });
}
}
Hopefuly it helps somebody

unable to deserialise odata to Dataset using JSON.NET due to odata.metadata

I'm trying to get some help on deserializing a JSON reponse to a DataSet.
in theory this should be easy as using this example
http://www.newtonsoft.com/json/help/html/DeserializeDataSet.htm
DataSet dataSet = JsonConvert.DeserializeObject<DataSet>(json);
DataTable dataTable = dataSet.Tables["Table1"];
However the JSON I am getting back is supplemented / decorated using "odata.metadata"
see below.
{"odata.metadata":"http://nodts004.cloudapp.net:7058/TNPMaster2016Dev/OData/$metadata#NP_Customer","value":[{"No":"01121212","Name":"Spotsmeyer's Furnishings","City":"Miami","Amount":"0","Customer_Posting_Group":"FOREIGN","Balance_LCY":"0","Sales_LCY":"0","Profit_LCY":"0","Balance_Due_LCY":"0","Payments_LCY":"0","Inv_Amounts_LCY":"0","Cr_Memo_Amounts_LCY":"0","Outstanding_Orders":"0","Shipped_Not_Invoiced":"0","No_of_Quotes":0,"No_of_Blanket_Orders":0,"No_of_Orders":6,"No_of_Invoices":0,"No_of_Return_Orders":0,"No_of_Credit_Memos":0,"No_of_Pstd_Shipments":0,"No_of_Pstd_Invoices":0,"No_of_Pstd_Return_Receipts":0,"No_of_Pstd_Credit_Memos":0,"No_of_Ship_to_Addresses":0,"Outstanding_Orders_LCY":"0","Shipped_Not_Invoiced_LCY":"0"},{"No":"01445544","Name":"Progressive Home Furnishings","City":"Chicago","Amount":"0","Customer_Posting_Group":"FOREIGN","Balance_LCY":"1499.02","Sales_LCY":"1499.02","Profit_LCY":"305.12","Balance_Due_LCY":"1499.02","Payments_LCY":"0","Inv_Amounts_LCY":"1499.02","Cr_Memo_Amounts_LCY":"0","Outstanding_Orders":"0","Shipped_Not_Invoiced":"0","No_of_Quotes":0,"No_of_Blanket_Orders":0,"No_of_Orders":0,"No_of_Invoices":0,"No_of_Return_Orders":0,"No_of_Credit_Memos":0,"No_of_Pstd_Shipments":1,"No_of_Pstd_Invoices":1,"No_of_Pstd_Return_Receipts":0,"No_of_Pstd_Credit_Memos":0,"No_of_Ship_to_Addresses":0,"Outstanding_Orders_LCY":"0","Shipped_Not_Invoiced_LCY":"0"},{"No":"01454545","Name":"New Concepts Furniture","City":"Atlanta","Amount":"0","Customer_Posting_Group":"FOREIGN","Balance_LCY":"222241.32","Sales_LCY":"0","Profit_LCY":"0","Balance_Due_LCY":"222241.32","Payments_LCY":"0","Inv_Amounts_LCY":"222241.32","Cr_Memo_Amounts_LCY":"0","Outstanding_Orders":"15609","Shipped_Not_Invoiced":"0","No_of_Quotes":0,"No_of_Blanket_Orders":0,"No_of_Orders":1,"No_of_Invoices":0,"No_of_Return_Orders":0,"No_of_Credit_Memos":0,"No_of_Pstd_Shipments":0,"No_of_Pstd_Invoices":0,"No_of_Pstd_Return_Receipts":0,"No_of_Pstd_Credit_Memos":0,"No_of_Ship_to_Addresses":0,"Outstanding_Orders_LCY":"8702.82","Shipped_Not_Invoiced_LCY":"0"},{"No":"01905893","Name":"Candoxy Canada Inc.","City":"Thunder Bay","Amount":"0","Customer_Posting_Group":"FOREIGN","Balance_LCY":"0","Sales_LCY":"0","Profit_LCY":"0","Balance_Due_LCY":"0","Payments_LCY":"0","Inv_Amounts_LCY":"0","Cr_Memo_Amounts_LCY":"0","Outstanding_Orders":"0","Shipped_Not_Invoiced":"0","No_of_Quotes":0,"No_of_Blanket_Orders":0,"No_of_Orders":0,"No_of_Invoices":0,"No_of_Return_Orders":0,"No_of_Credit_Memos":0,"No_of_Pstd_Shipments":0,"No_of_Pstd_Invoices":0,"No_of_Pstd_Return_Receipts":0,"No_of_Pstd_Credit_Memos":0,"No_of_Ship_to_Addresses":0,"Outstanding_Orders_LCY":"0","Shipped_Not_Invoiced_LCY":"0"},{"No":"01905899","Name":"Elkhorn Airport","City":"Elkhorn","Amount":"0","Customer_Posting_Group":"FOREIGN","Balance_LCY":"0","Sales_LCY":"0","Profit_LCY":"0","Balance_Due_LCY":"0","Payments_LCY":"0","Inv_Amounts_LCY":"0","Cr_Memo_Amounts_LCY":"0","Outstanding_Orders":"0","Shipped_Not_Invoiced":"0","No_of_Quotes":0,"No_of_Blanket_Orders":0,"No_of_Orders":0,"No_of_Invoices":0,"No_of_Return_Orders":0,"No_of_Credit_Memos":0,"No_of_Pstd_Shipments":0,"No_of_Pstd_Invoices":0,"No_of_Pstd_Return_Receipts":0,"No_of_Pstd_Credit_Memos":0,"No_of_Ship_to_Addresses":0,"Outstanding_Orders_LCY":"0","Shipped_Not_Invoiced_LCY":"0"}]}
I have for certain scenarios created a POCO to deal with the returned json for the properties
public class RootObject2
{
[JsonProperty("odata.metadata")]
public string odatametadata { get; set; }
[JsonProperty("odata.nextLink")]
public string NextLinkUrl { get; set; }
}
and
public class RootObject
{
[JsonProperty("odata.metadata")]
public string odatametadata { get; set; }
[JsonProperty("odata.nextLink")]
public string NextLinkUrl { get; set; }
public List<UrlItem> Value { get; set; }
}
These are used in instances where I know the returned JSON will contain certain structures and can be safely dealt with.
The problem is that the VALUE part of the JSON will be dynamic in many instances and I wanted to take advantage of the dynamic nature of the JSONConvert functions to build DataSets that I can then pass through as a source for an Excel table. It should be noted that the data coming back will never be definable.
When i use the code:
DataSet dataSet = JsonConvert.DeserializeObject<DataSet>(json);
I get an error, because I need to be passing the sting / contents of the VALUE node/element to the DeserialseObject.
Is there a setting on the JSON converter that allows this?
I have tried to create a POCO with a string field and then after mapping the VALUE node to the POCO passing the string to the JSONConverter but this errors out.
A solution to this would be most helpful.
Thanks.
B....
You can create your own custom subclass of DataSetConverter that strips out non-array-valued properties from the root DataSet object:
public class DataSetConverter : Newtonsoft.Json.Converters.DataSetConverter
{
public override bool CanConvert(Type valueType)
{
if (!base.CanConvert(valueType))
return false;
return typeof(DataSet).IsAssignableFrom(valueType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
var token = JObject.Load(reader);
// Strip non-array-valued properties
foreach (var property in token.Properties().Where(p => p.Value.Type != JTokenType.Array).ToList())
property.Remove();
using (var subReader = token.CreateReader())
{
while (subReader.TokenType == JsonToken.None)
subReader.Read();
return base.ReadJson(subReader, objectType, existingValue, serializer); // Use base class to convert
}
}
}
Then use it as follows:
var dataSet = JsonConvert.DeserializeObject<DataSet>(json, new JsonSerializerSettings { Converters = new JsonConverter[] { new DataSetConverter() } });
var dataTable = dataSet.Tables["value"];
Prototype fiddle.

AutoMapper cannot convert enum to nullable int?

I got AutoMapperMappingException exception
Exception of type 'AutoMapper.AutoMapperMappingException' was thrown. ---> System.InvalidCastException: Invalid cast from 'DummyTypes' to 'System.Nullable`1[[System.Int32, ...
when
public enum DummyTypes : int
{
Foo = 1,
Bar = 2
}
public class DummySource
{
public DummyTypes Dummy { get; set; }
}
public class DummyDestination
{
public int? Dummy { get; set; }
}
[TestMethod]
public void MapDummy()
{
Mapper.CreateMap<DummySource, DummyDestination>();
Mapper.AssertConfigurationIsValid();
DummySource src = new DummySource()
{
Dummy = DummyTypes.Bar
};
Mapper.Map<DummySource, DummyDestination>(src);
}
Should not AutoMapper map this implicitly without any extra explicit rule?
P.S. I cannot change the definition of DummyDestination.Dummy to enum. I have to deal with such interfaces.
It looks like no, it won't take care of this automatically for you. Interestingly, it will map an enum to a regular int.
Looking at AutoMapper's source, I think the problematic line is:
Convert.ChangeType(context.SourceValue, context.DestinationType, null);
Assuming context.SourceValue = DummyTypes.Foo and context.DestinationType is int?, you would end up with:
Convert.ChangeType(DummyTypes.Foo, typeof(int?), null)
which throws a similar exception:
Invalid cast from 'UserQuery+DummyTypes' to
'System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0
So I think really the question is why can't we cast a variable of type enum to int? That question has already been asked here.
This seems like a bug in AutoMapper. Anyway the workaround is to map the property manually:
Mapper.CreateMap<DummySource, DummyDestination>()
.ForMember(dest => dest.Dummy, opt => opt.MapFrom(src => (int?)src.Dummy));
Just in case if anyone want to try using a type converter
Mapper.CreateMap<int?, DummyTypes.Foo?>().ConvertUsing(new FooTypeConverter());
public class FooTypeConverter: TypeConverter<int?, DummyTypes.Foo?>
{
protected override DummyTypes.Foo? ConvertCore(int? source)
{
return source.HasValue ? (DummyTypes.Foo?)source.Value : null;
}
}
Cheers

WCF Data service $select returning 'Not Implemented' exception

I have a project I am working on that has a Order entity that has a navigation property for OrderITems:
public class Order
{
public int OrderId { get; set; }
public int CustomerId { get; set; }
public virtual ICollection<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
public int OrderId { get; set; }
public int ProductId { get; set; }
public int Quantity { get; set; }
public virtual Order Order { get; set; }
}
If I query the OrderItems like so:
http://mysvc.com/mysvc/OrderItems?$expand=Order&$filter=ProductId eq 1234
It works well but I ultimately need to bind to the Order only. So, I would like to project like this:
http://mysvc.com/mysvc/OrderItems?$expand=Order&$filter=ProductId eq 1234&$select=Order
However, I receive a 'Not Implemented' exception.
Th project is using an EntityFramework code first 4.1 data provider and all of the wiring "appears" correct. The only thing extra I added was routing to produce the clean restful uris and support for the $format option via a IDispatchMessageInspector.
Ok, Thanks to Vitek's input I was able to get a more complete exception:
<error>
<code/>
<message xml:lang="en-US">Not Implemented</message>
<innererror>
<message>Unable to create a constant value of type 'System.Data.Services.Internal.ProjectedWrapper1'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
</message>
<type>System.NotSupportedException</type>
<stacktrace> 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.ConditionalTranslator.TypedTranslate(ExpressionConverter parent, ConditionalExpression 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.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.SelectTranslator.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(Nullable`1 forMergeOption)
at System.Data.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
at System.Data.Objects.ObjectQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
at System.Data.Entity.Internal.Linq.InternalQuery`1.GetEnumerator()
at System.Data.Entity.Infrastructure.DbQuery`1.System.Collections.IEnumerable.GetEnumerator()
at System.Data.Services.Internal.ProjectedWrapper.EnumerableWrapper.System.Collections.IEnumerable.GetEnumerator()
at System.Data.Services.WebUtil.GetRequestEnumerator(IEnumerable enumerable)
</stacktrace>
</innererror>
</error>
If this is using EF Code First it needs to define the service as DataService and override the CreateDataSource to use the trick described in this article in section #5: http://social.technet.microsoft.com/wiki/contents/articles/5234.aspx
Otherwise WCF DS treats the DbContext as a reflection provider would and runs queries intended for LINQ to Objects. This mostly works, but for more complicated queries it sometimes breaks. If the WCF DS knows it runs agains EF, it creates queries for EF which will work always.

Handling Serialization Exceptions in ServiceStack

I am using ServiceStack to create a service which accepts request from and HTML form (POSTed). One of the DTO properties is an Enum, and when the input doesn't match the Enum members, I get the following exception:
Error occured while Processing Request: KeyValueDataContractDeserializer: Error converting to type: Requested value 'MyValue' was not found.
System.Runtime.Serialization.SerializationException: KeyValueDataContractDeserializer: Error converting to type: Requested value 'MyValue' was not found. ---> System.ArgumentException: Requested value 'MyValue' was not found.
at System.Enum.TryParseEnum(Type enumType, String value, Boolean ignoreCase, EnumResult& parseResult)
at System.Enum.Parse(Type enumType, String value, Boolean ignoreCase)
at ServiceStack.ServiceModel.Serialization.StringMapTypeDeserializer.PopulateFromMap(Object instance, IDictionary`2 keyValuePairs)
How can I intercept this exception and handle it myself in my service code?
There are a couple of ways to handle this situation:
You can make the DTO Enum property a string (since everything can successfully deserialize into a string :) and then convert that yourself manually i.e.
using ServiceStack.Common; //ToEnum<> is an extension method
public class RequestDto
{
public string EnumString { get; set; }
}
public override object OnGet(RequestDto request)
{
MyEnum defaultValue = MyEnum.None;
try {
defaultValue = request.EnumString.ToEnum<MyEnum>();
} catch {}
}
The other alternative is to completely remove it from the request DTO and get value manually from the IHttpRequest Context like:
public class RequestDto {}
public override object OnGet(RequestDto request)
{
MyEnum enumValue = MyEnum.DefaultValue;
try {
var enumStr = base.RequestContext.Get<IHttpRequest>().QueryString["EnumString"];
enumValue = enumStr.ToEnum<MyEnum>();
} catch {}
}
I generally discourage the use of enums on DTOs for many reasons, the primary one being on XML/SOAP endpoints the XSD treats them as a restricted set of values which is a pain in when trying iteratively to evolve your web services as you will need to re-gen the clients to add a new value.
By convention the way I deal with it is to have all enums as strings but provide some metadata on the DTO which points to the target type (which helps in VS.NET/R# navigation and metadata tools).
public class RequestDto
{
[References(typeof(MyEnum))]
public string EnumString { get; set; }
}

Resources