locked
Projection Not Working on Reflection Provider with EF Context RRS feed

  • Question

  • Hi, i have a use case where there is an existing database and EF DBContext but i dont have control over the model. I want to create WCF Data Service for it but i need control over the properties being exposed and probably add some custom properties as well. Since i can't update the EDM I decided to create my own entity container class and just use Reflection Provider for the Service. I want both flexibility to expose whole Entity as if like in EF ObjectContext and also a custom class which will be derived from an Entity with limited properties exposed.

    I have the following code

        /// <summary>
        /// Custom Provider
        /// </summary>
        public partial class CustomDataServiceProvider
        {
            //This is my EF Context
            private static MyEFEntities context = new MyEFEntities();
    
    
            public CustomDataServiceProvider()
            {
                //Constructor
            }
    
            /// <summary>
            /// I Expose
            /// </summary>
            public IQueryable<Company> DerivedCompanies
            {
                get
                {
                    var query = (from m in context.Companies
                               select new CompanyDerived
                                {
                                    CompanyCd = m.CompanyCd,
                                    ComanyDescr = m.CompanyDescr
                                });
                    return query;
                }
            }
    
            /// <summary>
            /// I want to expose the Accounts DbSet of my EF Context as it is
            /// </summary>
            public IQueryable<Account> Accounts
            {
                get
                {
                    var query = (from m in context.Accounts
                                      select m
                    return query;
                }
            }
    
        }
    
    
        [DataServiceKeyAttribute("CompanyCd")]
        [DataServiceEntity]
        public class Company
        {
            public string ComanyDescr { get; set; }
            public string CompanyCd { get; set; }
        }
    
        //I've Reused the DbSet created by ADO .Net DBContext Generator
        [DataServiceKeyAttribute("AccountNbr")]
        [DataServiceEntity]
        partial class Account { }
    
    
        /// <summary>
        /// Data Service
        /// </summary>
      [System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)]
        [JSONPSupportBehavior]
        public class Service : DataService<CustomDataServiceProvider>
        {
            // This method is called only once to initialize service-wide policies.
            public static void InitializeService(DataServiceConfiguration config)
            {
                // TODO: set rules to indicate which entity sets and service operations are visible, updatable, etc.
                // Examples:
                // config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead);
                // config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All);
                config.EnableTypeConversion = true;
                config.SetEntitySetPageSize("*", 100); //Server Based Paging. Best Practice
                config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
                config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
                config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
            }
        }
    


    It seems everything works fine (e.g. $filter, $top) but the projection ($select) is not working which is a huge factor for us:

    e.g.

    http://localhost/Service.svc/Account?$select=AccountNbr

    http://localhost/Service.svc/Companies?$select=CompanyCd

     

    I've found out if i first convert the resultset to array\list then return as IQueryable (e.g. return query.ToArray().AsQueryable<Account>(); ), it works just fine. But then performance will surely suffer since it will query the whole table in the database and if have million records it'll be disaster. I've confirmed this with SQL Profiler.

    I wonder why its not working? The $top and $filter seems to be working just fine, even in the SQL profiler it execute queries such as TOP(1) and with WHERE clause. But using $select, nothing happens, it didn't even throw an error on debug mode.

    Anyone have any idea, work around or much better alternative?

    Thanks in advance for your help.

     

    Thursday, December 1, 2011 2:59 AM

Answers

  • Hi,

    This is a known limitation. The reason is that the EF's LINQ provider doesn't handle certain LINQ expressions. On the other hand the LINQ to Objects provider doesn't handle certain other LINQ expressions. And as it turns out, it's not possible to generate one expression tree for the $select where both of these providers would be able to handle it. (The technical detail is that LINQ to Objects requires an explicit cast to Object when assigning a value to property of type Object, while EF provider fails on the cast, but if the cast is removed EF works just fine, but LINQ to Objects fails).

    The short answer is unfortunately that using reflection provider over EF context is not currently supported.

    The long answer migth be that if you're willing to implement some dirty magic expression visitor and "fixup" the LINQ expression trees before it gets to EF, you might be able to get this to work.

    Maybe a better solution would be to copy the EF model you have (and can't modify) and create your own version which will connect to the same DB. Not sure if that's possible in your setup though. Note that you will not lose maintainability, since by adding your own reflection provider, the classes in that code must match the EF model classes anyway.

    Thanks,


    Vitek Karas [MSFT]
    Thursday, December 1, 2011 10:14 AM
    Moderator