This project has moved. For the latest updates, please go here.

Entity Framework Table per Type example

Aug 28, 2015 at 3:31 PM
Is it possible to make Remote Linq work with the following example?

http://weblogs.asp.net/manavi/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-2-table-per-type-tpt

I modified your RemoteQueryableToEntityFramework sample app, I added the new classes in the WcfContracts namespace
public abstract class BillingDetail
    {
        public int BillingDetailId { get; set; }
        public string Owner { get; set; }
        public string Number { get; set; }
    }

    [Table("BankAccounts")]
    public class BankAccount : BillingDetail
    {
        public string BankName { get; set; }
        public string Swift { get; set; }
    }

    [Table("CreditCards")]
    public class CreditCard : BillingDetail
    {
        public int CardType { get; set; }
        public string ExpiryMonth { get; set; }
        public string ExpiryYear { get; set; }
    }
the new properties in the EFContext class
public virtual DbSet<BillingDetail> BillingDetails { get; set; }
public virtual DbSet<CreditCard> CreditCards { get; set; }
the new property in the RemoteRepository class
public IQueryable<BillingDetail> BillingDetails { get { return RemoteQueryable.Create<BillingDetail>(_dataProvider); } }
but when I try to run the query it
var repo = new RemoteRepository("http://localhost:50105/QueryService.svc");
foreach (var item in repo.BillingDetails)
            {
                Console.WriteLine(item.Number);
            }
Exception:
Failed to pick matching contructor for type WcfContracts.BillingDetail
StackTrace:
at Remote.Linq.Dynamic.DynamicObjectMapper.MapInternal[T](IEnumerable`1 objects)
at Remote.Linq.Dynamic.DynamicObjectMapper.MapInternal[T](DynamicObject obj)
Is there a solution for this?
Coordinator
Aug 31, 2015 at 8:24 AM
Edited Aug 31, 2015 at 8:51 AM
Thanks for your request.

There is an optimization in place to omit type information in query results which is not required in most scenarios. In your case you actually need this type info since the records contained in the result may be of different types.

I'll improve the API to change the default and make it easier to override. For now you can make the following change on the server-side to make it work:
public IEnumerable<DynamicObject> ExecuteQuery(Expression queryExpression)
{
    // replace this line
    //return queryExpression.Execute(_queryableResourceProvider);

    // with the following code
    var query = queryExpression.PrepareForExecution(_queryableResourceProvider);

    var result = query.Execute();

    var mapper = new DynamicObjectMapper();
    var dynamicObjects = mapper.MapCollection(result, setTypeInformation: true);
    return dynamicObjects;
}
Aug 31, 2015 at 8:56 AM
If I change the code as you mentioned I get the following exception.

Exception:
Object reference not set to an instance of an object.
StackTrace:
at Aqua.TypeSystem.Emit.TypeEmitter.InternalEmitType(TypeInfo typeInfo)
at Aqua.TypeSystem.Emit.TypeEmitter.TypeCache.<>c__DisplayClass1e.<GetOrCreate>b__1d(Object x)
at Aqua.TransparentCache2.GetOrCreate(TKey key, Func2 factory)
at Aqua.TypeSystem.Emit.TypeEmitter.TypeCache.GetOrCreate(TypeInfo typeInfo, Func2 factory)
at Aqua.TypeSystem.Emit.TypeEmitter.EmitType(TypeInfo typeInfo)
at Aqua.TypeSystem.TypeResolver.EmitType(TypeInfo typeInfo)
at Aqua.TypeSystem.TypeResolver.ResolveTypeInternal(TypeInfo typeInfo)
at Aqua.TransparentCache
2.GetOrCreate(TKey key, Func2 factory)
at Aqua.TypeSystem.TypeResolver.ResolveType(TypeInfo typeInfo)
at Aqua.Dynamic.DynamicObjectMapper.MapFromDynamicObjectIfRequired(Object obj, Type targetType)
at Aqua.Dynamic.DynamicObjectMapper.MapFromDynamicObjectGraph(Object obj, Type targetType)
at Aqua.Dynamic.DynamicObjectMapper.<>c__DisplayClassb.<Map>b__a(DynamicObject x)
at System.Linq.Enumerable.WhereSelectArrayIterator
2.MoveNext()
at System.Linq.Enumerable.<CastIterator>d__11.MoveNext()
at System.Linq.Buffer
1..ctor(IEnumerable1 source)
at System.Linq.Enumerable.ToArray[TSource](IEnumerable
1 source)
Coordinator
Aug 31, 2015 at 10:16 AM
Yes, this fix only works for know types like BillingDetail, BankAccount, CreditCard etc.
Queries returning anonymous types (i.e. when using select new { ... }) will fail with this quick fix from above.
I need to fix the library to make it work in all situations and will let you know once it's ready.
Coordinator
Aug 31, 2015 at 10:20 AM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.
Coordinator
Sep 1, 2015 at 7:51 PM
The limitations discussed above should have been resolved by release 5.1.0
Please give it a try and let me know in case of any issues.

You can now revert your server-side code back to it's original state:
public IEnumerable<DynamicObject> ExecuteQuery(Expression queryExpression)
{
    return queryExpression.Execute(_queryableResourceProvider);
}
Sep 2, 2015 at 5:27 PM
Thank you. It works for this example now. But I think there is still an issue.

If you change you BillingDetails class like this:
public abstract class BillingDetail
    {
        public int BillingDetailId { get; set; }
        public virtual Owner Owner { get; set; }
        public string Number { get; set; }
    }

    public class Owner
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
The same BillingDetails query what we had before, throws an exception (I think this was working in the previous version):
Object reference not set to an instance of an object.

at Aqua.TypeSystem.Emit.TypeEmitter.InternalEmitType(TypeInfo typeInfo)
at Aqua.TypeSystem.Emit.TypeEmitter.TypeCache.<>c__DisplayClass1e.<GetOrCreate>b__1d(Object x)
at Aqua.TransparentCache2.GetOrCreate(TKey key, Func2 factory)
at Aqua.TypeSystem.Emit.TypeEmitter.TypeCache.GetOrCreate(TypeInfo typeInfo, Func2 factory)
at Aqua.TypeSystem.Emit.TypeEmitter.EmitType(TypeInfo typeInfo)
at Aqua.TypeSystem.TypeResolver.EmitType(TypeInfo typeInfo)
at Aqua.TypeSystem.TypeResolver.ResolveTypeInternal(TypeInfo typeInfo)
at Aqua.TransparentCache
2.GetOrCreate(TKey key, Func2 factory)
at Aqua.TypeSystem.TypeResolver.ResolveType(TypeInfo typeInfo)
at Aqua.Dynamic.DynamicObjectMapper.MapFromDynamicObjectIfRequired(Object obj, Type targetType)
at Aqua.Dynamic.DynamicObjectMapper.MapFromDynamicObjectGraph(Object obj, Type targetType)
at Aqua.Dynamic.DynamicObjectMapper.<>c__DisplayClassb.<Map>b__a(DynamicObject x)
at System.Linq.Enumerable.WhereSelectArrayIterator
2.MoveNext()
at System.Linq.Enumerable.<CastIterator>d__11.MoveNext()
at System.Linq.Buffer
1..ctor(IEnumerable1 source)
at System.Linq.Enumerable.ToArray[TSource](IEnumerable
1 source)
I think this is because Entity Framework proxy creation. It creates a proxy for each class which contains a virtual member. If I turn it off in the EFContext (base.Configuration.ProxyCreationEnabled = false;) it works. But then I have to include the related objects manually in the service.
Coordinator
Sep 3, 2015 at 7:23 AM
I need to have a look at it, since I never worked with transparent proxies in my scenarios.

However, I would highly recommend disabling lazy loading whenever working in n-tier environments, i.e. exposing the data via some form of service endpoints and therefore have to serialize it. Otherwise you always suffer the n+1 query problem!

Use Include either on server or on client to explicitly define eager loading. In case you define includes on the client-side, you can make use of Remote.Linq.EntityFramework on server-side to properly apply the include to the EF-query.
Coordinator
Sep 4, 2015 at 9:18 PM
I've release a new version (Remote.Linq 5.2.0) which supports working with dynamic proxies.

1) In case you're not working with table inheritance and/or never query abstract types, you can just suppress type-information to be sent back to the client by using the following code:
var dynamicObjects = mapper.MapCollection(result, setTypeInformation: false);
2) If you want to return type information back to client you may now define custom mapping to replace dynamic proxy type info by actual entity type:
public class QueryService : IQueryService
{
    public IEnumerable<DynamicObject> ExecuteQuery(Expression queryExpression)
    {
        var efContext = new EFContext();

        var dynamicObjectMapper = new DynamicObjectMapper(dynamicObjectTypeInfoMapper: MapTypeInfoForClient);

        var result = queryExpression.ExecuteWithEntityFramework(efContext, mapper: dynamicObjectMapper);

        return result;
    }

    private static Type MapTypeInfoForClient(Type type)
    {
        if (type.BaseType != null && type.Namespace == "System.Data.Entity.DynamicProxies")
        {
            return type.BaseType;
        }

        return type;
    }
}
Sep 16, 2015 at 11:58 AM
Thank you, it works now.

But I just noticed another issue:

If I try to call Count or FirstOrDefault I get StackOverflowException
repo.ProductCategories.Count();
repo.ProductCategories.FirstOrDefault();
I get the following exception:
An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll
It works if I pass in an expression as a parameter:
repo.ProductCategories.Count(p => p.Name == "test");
Sep 16, 2015 at 12:04 PM
And if I make the following call:
repo.ProductCategories.FirstOrDefault(p => p.Name == "test");
And there is not ProductCategory with this Name in the DB, instead of getting a null back I get this exception.

Exception:
Value cannot be null.
Parameter name: objects
StackTrace:
at Aqua.Dynamic.DynamicObjectMapper.Map(IEnumerable1 objects, Type type)
at Remote.Linq.DynamicQuery.DynamicResultMapper.MapToType[T](IEnumerable
1 dataRecords, IDynamicObjectMapper mapper)
at Remote.Linq.DynamicQuery.DynamicResultMapper.MapResult[TResult](IEnumerable1 source)
at Remote.Linq.DynamicQuery.RemoteQueryProvider
1.Execute[TResult](Expression expression)
at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable1 source, Expression1 predicate)
at WcfClient.Program.Run() in C:\Users\tamas.huj\Documents\Visual Studio 2015\Projects\RemoteLinqLib\samples\RemoteQueryableToEntityFramework\WcfClient\Program.cs:line 57
at WcfClient.Program.Main(String[] args) in C:\Users\tamas.huj\Documents\Visual Studio 2015\Projects\RemoteLinqLib\samples\RemoteQueryableToEntityFramework\WcfClient\Program.cs:line 14
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
Coordinator
Sep 16, 2015 at 11:55 PM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.
Coordinator
Sep 16, 2015 at 11:57 PM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.
Sep 17, 2015 at 10:37 AM
I have one more issue, I'm not sure if it is a Remote.Linq issue, or I'm doing something wrong.

I added a new operation contract to the IQueryService and implemented it
[OperationContract]
IEnumerable<Product> GetProducts(LambdaExpression filterExpression, string someAdditionalParameter);
And If I try to call it, it fails:
var binding = new BasicHttpBinding()
            {
                CloseTimeout = TimeSpan.FromMinutes(10),
                ReceiveTimeout = TimeSpan.FromMinutes(10),
                SendTimeout = TimeSpan.FromMinutes(10),
                MaxReceivedMessageSize = 640000L
            };

            ChannelFactory<IQueryService> channelFactory = new ChannelFactory<IQueryService>(binding, "http://localhost:50105/QueryService.svc");
            var channel = channelFactory.CreateChannel();

            var productName = "Apple";
            Expression<Func<Product, bool>> filter = p => p.Name == productName;
            var products = channel.GetProducts(filter.ToRemoteLinqExpression(), "someExtraParameter");
Exception:
There was an error while trying to serialize parameter http://tempuri.org/:filterExpression. The InnerException message was 'Type 'Remote.Linq.VariableQueryArgument`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]' with data contract name 'VariableQueryArgumentOfstring:http://schemas.datacontract.org/2004/07/Remote.Linq' is not expected. Consider using a DataContractResolver if you are using DataContractSerializer or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to the serializer.'. Please see InnerException for more details.
The weird thing is, if the product name is not coming from a variable it works, but this is not really a real life scenario.
Expression<Func<Product, bool>> filter = p => p.Name == "Apple";
Coordinator
Sep 18, 2015 at 8:54 PM
According the inner exception your LambdaExpression contains an instance of Remote.Linq.VariableQueryArgument`1 which is a closed generic type your WCF data contract is not aware of.
If the expression contains a constant sting instead of a variable (p => p.Name == "Apple") there is no VariableQueryArgument to be transmitted to the server and therefor it just works.

Remote.linq can help with this. Just add usings for Remote.Linq and Remote.Linq.ExpressionVisitors

Client side code:
Expression<Func<Product, bool>> filter = p => p.Name == "Apple";
 
LambdaExpression lambdaExpression = filter
    .ToRemoteLinqExpression()
    .ReplaceGenericQueryArgumentsByNonGenericArguments();
Server side code:
Expression<Func<Product, bool>> filter = lambdaExpression
    .ReplaceNonGenericQueryArgumentsByGenericArguments()
    .ToLinqExpression<Product, bool>();
 
IQueryable<Product> result = products.Where(filter);
Sep 28, 2015 at 5:29 PM
Thank you, this works.

But, I think I found another issue, if I use a provider and try to include references like this, it doesn't loads them:
private Func<EFContext, Type, IQueryable> _provider = (dataStore, type) =>
        {
            if (type == typeof(BillingDetail))
            {
                return dataStore.BillingDetails
                    .Include(b => b.Owner);
            }
            
            return dataStore.Set(type);
        };
If I pass in a string in as a parameter it works fine:
return dataStore.BillingDetails.Include("Owner");
But I think the first example is better because if you rename a property, you don't have to check the strings in your code.
Coordinator
Sep 29, 2015 at 7:21 PM
If I understand correctly you’re defining includes server-side, i.e. clients don’t specify includes.

I’ve modified the QueryService of the sample application RemoteQueryableToEntityFrameworkWithNavigationProperties as following, and it worked perfectly fine:
public class QueryService : IQueryService
{
  private Func<EFContext, Type, IQueryable> _provider = (dataStore, type) =>
  {
    if (type == typeof(Product))
    {
      return dataStore.Products.Include(p => p.ProductCategory);
    }
 
    return dataStore.Set(type);
  };
 
 
  public IEnumerable<DynamicObject> ExecuteQuery(Expression queryExpression)
  {
    var efContext = new EFContext();
    return queryExpression.Execute(t => _provider(efContext, t));
  }
}
My assumption is you’re using the include method provided by remote linq. However, in your scenario you should be using the one for entity framework. In this case just replace the using Remote.Linq by using System.Data.Entity:
using Remote.Linq; <-- remove
using System.Data.Entity; <-- add
Nov 12, 2015 at 3:16 PM
Yes, you are right. Thanks.
Nov 12, 2015 at 4:40 PM
One more question. Is it possible to filter by type? I tried to do something like this:
repo.BillingDetails.Where(b => b is BankAccount);
Exception:
Member Expression in type Remote.Linq.Expressions.LambdaExpression cannot be serialized. This exception is usually caused by trying to use a null value where a null value is not allowed. The 'Expression' member is set to its default value (usually null or zero). The member's EmitDefault setting is 'false', indicating that the member should not be serialized. However, the member's IsRequired setting is 'true', indicating that it must be serialized. This conflict cannot be resolved. Consider setting 'Expression' to a non-default value. Alternatively, you can change the EmitDefaultValue property on the DataMemberAttribute attribute to true, or changing the IsRequired property to false.
Coordinator
Nov 12, 2015 at 9:37 PM
repo.BillingDetails.OfType<BankAccount>();