Move from WCF Data Services to Web API – Part 4

In Part 2 in the method QueryEntitySet I had a subtle bug.

                var process = (Func<IEdmEntityType, IQueryable>)
                              _typeToGeneric.GetOrAdd(new KeyValuePair<IEdmEntityType, Type>(edmType, entityType),
                                                      pair =>
                                                      {
                                                          var method = ((MethodInfo)
                                                                        MethodBase.GetMethodFromHandle(
                                                                            _processRequest, GetType().TypeHandle))
                                                              .MakeGenericMethod(pair.Value);
                                                          return
                                                              Delegate.CreateDelegate(
                                                                  typeof(Func<IEdmEntityType, IQueryable>), this,
                                                                  method);
                                                      });

In the Delegate.CreateDelegate I’m passing in “this”. That will create a closures around the first controller and will call into that controller object every time. This is not what we want because that controller would be disposed and plus we now have an object that’s just hanging around. I’m going to fix that with ExpressionTrees by passing in the current controller object.

        private Func, IEdmEntityType, IQueryable> SwitchToGenericMethod(
            IEdmEntityType edmEntityType, Type clrType)
        {
            return
                (Func, IEdmEntityType, IQueryable>)
                _typeToGeneric.GetOrAdd(new KeyValuePair(edmEntityType, clrType),
                                        pair =>
                                            {
                                                var method = ((MethodInfo)
                                                              MethodBase.GetMethodFromHandle(
                                                                  _processRequest, GetType().TypeHandle))
                                                    .MakeGenericMethod(pair.Value);
                                                var controller =
                                                    Expression.Parameter(
                                                        typeof (ODataServicesController),
                                                        "oDServiceController");
                                                var edmentityType =
                                                    Expression.Parameter(typeof (IEdmEntityType),
                                                                         "edmEntityType");

                                                var expr = Expression.Call(controller, method, edmentityType);
                                                return
                                                    Expression.Lambda,
                                                        IEdmEntityType, IQueryable>>(expr, controller,
                                                                                     edmentityType).Compile();
                                            });
        }

Now in QueryEntitySet we change the code to be the following

                // Switch from system Type to generic type
                var process = SwitchToGenericMethod(edmType, entityType);
                var query = process(this, edmType);

Also we need to map all the entities to the CLR type otherwise $expand and $link won’t work.

        private void MapIEdmEntitySetsToCLR()
        {
            var metaData = GetMetadata();
            var entitySets = Container.EntitySets();
            foreach (var entitySet in entitySets)
            {
                var entityType = GetIEdmTypeToCLRType(entitySet.ElementType);
                metaData.SetAnnotationValue(entitySet.ElementType, new ClrTypeAnnotation(entityType));
            }
        }

        private IEdmEntityContainer Container
        {
            get
            {
                var metadata = GetMetadata();
                return metadata.EntityContainers().First();
            }
        }

I removed in the ProcessRequest method this code “model.SetAnnotationValue(edmEntityType, new ClrTypeAnnotation(typeof (TEntity)));” and replaced it with a call into MapIEdmEntitySetsToCLR().

From my testing how I’m determining if we are returning an EntitySet or Entity isn’t correct in the ProcessRequest method. If I ask for something like \api\odata\Customer(100)\SalesOrders the request will be set as a EntitySet and just return all the SalesOrders. Not filtered down to the one customer. So I will need to create, or find if Web API, has something to help me out. I’m doubtful about Web API because they are using controllers and actions to handle this and we want to just generate the ExpressionTrees automatically. I will be looking at the source code for WCF Data Services to see what I can “borrow” from them. I don’t think they will be too upset since they are getting ready to open source it. Because of this it might be a couple of days before I get to Part 5. But I wanted to update everyone along the way of things I found I needed to change. That’s part of the fun of writing the blog while I’m still working out the kinks and not at the end where everything is working 🙂

Tags: , ,

Friday, April 18th, 2014 OData