How can you expose a multi-layer model that has derived classes and members for OData services?

南笙酒味 提交于 2019-12-22 18:16:55

问题


I'm trying to expose a model to be available for OData services. The approach I'm currently taking is along the lines of:

1) Defining a class in the model to expose IQueryable collections such as:

public class MyEntities
{
    public IQueryable<Customer> Customers
    {
        get
        {
            return DataManager.GetCustomers().AsQueryable<Customer>();
        }
    }
    public IQueryable<User> Users
    {
        get
        {
            return DataManager.GetUsers().AsQueryable<User>();
        }
    }
}

2) Set up a WCF DataService with the queryable collection class such as:

public class MyDataService : DataService<MyEntities>
{
    public static void InitializeService(DataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule("Customers", EntitySetRights.All);
        config.SetEntitySetAccessRule("Users", EntitySetRights.All);
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
    }
}

I'm running into 3 issues and/or limitations with this approach:

1) I'm unable to add any derived class collections to the IQueryable lists.

2) I must apply the IgnoreProperties attribute to hide any members that are derived from a base type.

3) I'm unable to prevent unwanted entities from being accessed by the OData service and causing errors. For example, I only want BLL layer objects to be exposed, but it seems like the model is being reflected far beyond the members of the classes I added to the queryable list, and picking up all the DAL classes, causing errors being undefined and also having the same name as the BLL classes. There are no links to DAL classes from BLL class members. At the very least, I would like to have these classes ignored altogether.

Any pointers on how to address any of these issues would be greatly appreciated. Should I be doing a different approach on this? For example, should I implement IQueryable directly in my model collections?

Thanks.


回答1:


The reflection provider which you're using is designed to walk all public types/properties. So the #3 and probably even #2 (which I don't fully understand what's the problem) are by design because of that.

#1 is also by design but for a different reason - the reflection provider can only expose one entity set for each type hierarchy. It doesn't support so called "MEST" (Multiple Entity Sets per Type), because it would not know which one to pick. It needs a 1 to 1 mapping between entity types and entity sets.

The reflection provider is meant for simple services which are "Easy" to setup. It's definitely not designed for customizations.

If you want greater control, then you need custom provider, which can be either implemented directly (if it's based on existing CLR classes it's not that hard), or through some library, like the one suggested in the comments above.




回答2:


The reflection provider is not designed to handle rich data models with a fair amount of inheritance and other dependences. I ended up building a custom provider that could handle queries, updates, inheritance, and relationships based on Alex James' excellent blog post on Creating a Data Service Provider.

An example implementation with 3 CLR classes: ResidentialCustomer, Customer, and User is provided below. ResidentialCustomer extends Customer, Customer has a list of Users, and User has a reference back to Customer.

An interface for DataContext classes such as:

public interface IODataContext
{
    IQueryable GetQueryable(ResourceSet set); 
    object CreateResource(ResourceType resourceType);
    void AddResource(ResourceType resourceType, object resource);
    void DeleteResource(object resource);
    void SaveChanges();
}

A class to implement IDataServiceMetadataProvider such as:

public class ODataServiceMetadataProvider : IDataServiceMetadataProvider
{
    private Dictionary<string, ResourceType> resourceTypes = new Dictionary<string, ResourceType>();
    private Dictionary<string, ResourceSet> resourceSets = new Dictionary<string, ResourceSet>();
    private List<ResourceAssociationSet> _associationSets = new List<ResourceAssociationSet>(); 

    public string ContainerName
    {
        get { return "MyDataContext"; }
    }

     public string ContainerNamespace
     {
         get { return "MyNamespace"; }
     }

    public IEnumerable<ResourceSet> ResourceSets
    {
         get { return this.resourceSets.Values; }
    }

    public IEnumerable<ServiceOperation> ServiceOperations
    {
        get { yield break; }
    }

    public IEnumerable<ResourceType> Types
    {
        get { return this.resourceTypes.Values; }
    }

    public bool TryResolveResourceSet(string name, out ResourceSet resourceSet)
    {
        return resourceSets.TryGetValue(name, out resourceSet);
    }

    public bool TryResolveResourceType(string name, out ResourceType resourceType)
    {
        return resourceTypes.TryGetValue(name, out resourceType);
    }

    public bool TryResolveServiceOperation(string name, out ServiceOperation serviceOperation)
    {
        serviceOperation = null;
        return false;
    }

    public void AddResourceType(ResourceType type)
    {
        type.SetReadOnly();
        resourceTypes.Add(type.FullName, type);
    }

    public void AddResourceSet(ResourceSet set)
    {
        set.SetReadOnly();
        resourceSets.Add(set.Name, set);
    }

    public bool HasDerivedTypes(ResourceType resourceType)
    {
        if (resourceType.InstanceType == typeof(ResidentialCustomer))
        {
            return true;
        }
        return false;
    }

    public IEnumerable<ResourceType> GetDerivedTypes(ResourceType resourceType)
    {
        List<ResourceType> derivedResourceTypes = new List<ResourceType>();
        if (resourceType.InstanceType == typeof(ResidentialCustomer))
        {
            foreach (ResourceType resource in Types)
            {
                if (resource.InstanceType == typeof(Customer))
                {
                    derivedResourceTypes.Add(resource);
                }
            }
        }
        return derivedResourceTypes;
    }

    public void AddAssociationSet(ResourceAssociationSet associationSet) 
    {
        _associationSets.Add(associationSet); 
    }

    public ResourceAssociationSet GetResourceAssociationSet(ResourceSet resourceSet, ResourceType resourceType, ResourceProperty resourceProperty)
    {
        return resourceProperty.CustomState as ResourceAssociationSet;
    }

    public ODataServiceMetadataProvider() { }
}

A class to implement IDataServiceQueryProvider such as:

public class ODataServiceQueryProvider<T> : IDataServiceQueryProvider where T : IODataContext
{
    T _currentDataSource;
    IDataServiceMetadataProvider _metadata;

    public object CurrentDataSource
    {
        get
        {
            return _currentDataSource;
        }
        set
        {
            _currentDataSource = (T)value;
        }
    }

    public bool IsNullPropagationRequired
    {
        get { return true; }
    }

    public object GetOpenPropertyValue(object target, string propertyName)
    {
        throw new NotImplementedException();
    }

    public IEnumerable<KeyValuePair<string, object>> GetOpenPropertyValues(object target)
    {
        throw new NotImplementedException();
    }

    public object GetPropertyValue(object target, ResourceProperty resourceProperty)
    {
        throw new NotImplementedException();
    }

    public IQueryable GetQueryRootForResourceSet(ResourceSet resourceSet)
    {
        return _currentDataSource.GetQueryable(resourceSet);
    }

    public ResourceType GetResourceType(object target)
    {
        Type type = target.GetType();
        return _metadata.Types.Single(t => t.InstanceType == type);
    }

    public object InvokeServiceOperation(ServiceOperation serviceOperation, object[] parameters)
    {
        throw new NotImplementedException();
    }

    public ODataServiceQueryProvider(IDataServiceMetadataProvider metadata)
    {
        _metadata = metadata;
    }
}

A class to implement IDataServiceUpdateProvider such as:

public class ODataServiceUpdateProvider<T> : IDataServiceUpdateProvider where T : IODataContext
{
    private IDataServiceMetadataProvider _metadata;
    private ODataServiceQueryProvider<T> _query;
    private List<Action> _actions;

    public T GetContext()
    {
        return ((T)_query.CurrentDataSource);
    }

    public void SetConcurrencyValues(object resourceCookie, bool? checkForEquality, IEnumerable<KeyValuePair<string, object>> concurrencyValues)
    {
        throw new NotImplementedException();
    }

    public void SetReference(object targetResource, string propertyName, object propertyValue)
    {
        _actions.Add(() => ReallySetReference(targetResource, propertyName, propertyValue));
    }

    public void ReallySetReference(object targetResource, string propertyName, object propertyValue)
    {
        targetResource.SetPropertyValue(propertyName, propertyValue);
    }

    public void AddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded)
    {
        _actions.Add(() => ReallyAddReferenceToCollection(targetResource, propertyName, resourceToBeAdded));
    }

    public void ReallyAddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded)
    {
        var collection = targetResource.GetPropertyValue(propertyName);
        if (collection is IList)
        {
            (collection as IList).Add(resourceToBeAdded);
        }
    }

    public void RemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved)
    {
        _actions.Add(() => ReallyRemoveReferenceFromCollection(targetResource, propertyName, resourceToBeRemoved));
    }

    public void ReallyRemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved)
    {
        var collection = targetResource.GetPropertyValue(propertyName);
        if (collection is IList)
        {
            (collection as IList).Remove(resourceToBeRemoved);
        }
    }

    public void ClearChanges()
    {
        _actions.Clear();
    }

    public void SaveChanges()
    {
        foreach (var a in _actions)
            a();
        GetContext().SaveChanges();
    }

    public object CreateResource(string containerName, string fullTypeName)
    {
        ResourceType type = null;
        if (_metadata.TryResolveResourceType(fullTypeName, out type))
        {
            var context = GetContext();
            var resource = context.CreateResource(type);
            _actions.Add(() => context.AddResource(type, resource));
            return resource;
        }
        throw new Exception(string.Format("Type {0} not found", fullTypeName));
    }

    public void DeleteResource(object targetResource)
    {
        _actions.Add(() => GetContext().DeleteResource(targetResource));
    }

    public object GetResource(IQueryable query, string fullTypeName)
    {
        var enumerator = query.GetEnumerator();
        if (!enumerator.MoveNext())
            throw new Exception("Resource not found");
        var resource = enumerator.Current;
        if (enumerator.MoveNext())
            throw new Exception("Resource not uniquely identified");

        if (fullTypeName != null)
        {
            ResourceType type = null;
            if (!_metadata.TryResolveResourceType(fullTypeName, out type))
                throw new Exception("ResourceType not found");
            if (!type.InstanceType.IsAssignableFrom(resource.GetType()))
                throw new Exception("Unexpected resource type");
        }
        return resource;
   }

    public object ResetResource(object resource)
    {
        _actions.Add(() => ReallyResetResource(resource));
        return resource;
    }

    public void ReallyResetResource(object resource)
    {
        var clrType = resource.GetType();
        ResourceType resourceType = _metadata.Types.Single(t => t.InstanceType == clrType);
        var resetTemplate = GetContext().CreateResource(resourceType);

        foreach (var prop in resourceType.Properties
                 .Where(p => (p.Kind & ResourcePropertyKind.Key) != ResourcePropertyKind.Key))
        {
            var clrProp = clrType.GetProperties().Single(p => p.Name == prop.Name);
            var defaultPropValue = clrProp.GetGetMethod().Invoke(resetTemplate, new object[] { });
            clrProp.GetSetMethod().Invoke(resource, new object[] { defaultPropValue });
        }
    }

    public object ResolveResource(object resource)
    {
        return resource;
    }

    public object GetValue(object targetResource, string propertyName)
    {
        var value = targetResource.GetType().GetProperties().Single(p => p.Name == propertyName).GetGetMethod().Invoke(targetResource, new object[] { });
        return value;
    }

    public void SetValue(object targetResource, string propertyName, object propertyValue)
    {
        targetResource.GetType().GetProperties().Single(p => p.Name == propertyName).GetSetMethod().Invoke(targetResource, new[] { propertyValue });
     }

     public ODataServiceUpdateProvider(IDataServiceMetadataProvider metadata, ODataServiceQueryProvider<T> query)
     {
         _metadata = metadata;
         _query = query;
         _actions = new List<Action>();
    }
}

A class to implement IServiceProvider such as:

public class ODataService<T> : DataService<T>, IServiceProvider where T : IODataContext
{
    private ODataServiceMetadataProvider _metadata;
    private ODataServiceQueryProvider<T> _query;
    private ODataServiceUpdateProvider<T> _updater;

    public object GetService(Type serviceType)
    {
        if (serviceType == typeof(IDataServiceMetadataProvider))
        {
            return _metadata;
        }
        else if (serviceType == typeof(IDataServiceQueryProvider))
        {
            return _query;
        }
        else if (serviceType == typeof(IDataServiceUpdateProvider))
        {
            return _updater;
        }
        else
        {
            return null;
        }
    }

    public ODataServiceMetadataProvider GetMetadataProvider(Type dataSourceType)
    {
        ODataServiceMetadataProvider metadata = new ODataServiceMetadataProvider();
        ResourceType customer = new ResourceType(
            typeof(Customer),
            ResourceTypeKind.EntityType,
            null,
            "MyNamespace",
            "Customer",
            false
        );
        ResourceProperty customerCustomerID = new ResourceProperty(
            "CustomerID",
            ResourcePropertyKind.Key |
            ResourcePropertyKind.Primitive,
            ResourceType.GetPrimitiveResourceType(typeof(Guid))
        );
        customer.AddProperty(customerCustomerID);
        ResourceProperty customerCustomerName = new ResourceProperty(
            "CustomerName",
            ResourcePropertyKind.Primitive,
            ResourceType.GetPrimitiveResourceType(typeof(string))
        );
        customer.AddProperty(customerCustomerName);
        ResourceType residentialCustomer = new ResourceType(
            typeof(ResidentialCustomer),
            ResourceTypeKind.EntityType,
            customer,
            "MyNamespace",
            "ResidentialCustomer",
            false
        );
        ResourceType user = new ResourceType(
            typeof(User),
            ResourceTypeKind.EntityType,
            null,
            "MyNamespace",
            "User",
            false
        );
        ResourceProperty userUserID = new ResourceProperty(
            "UserID",
            ResourcePropertyKind.Key |
            ResourcePropertyKind.Primitive,
            ResourceType.GetPrimitiveResourceType(typeof(Guid))
        );
        user.AddProperty(userUserID);
        ResourceProperty userCustomerID = new ResourceProperty(
            "CustomerID",
            ResourcePropertyKind.Primitive,
            ResourceType.GetPrimitiveResourceType(typeof(Guid))
        );
        user.AddProperty(userCustomerID);
        ResourceProperty userEmailAddress = new ResourceProperty(
            "EmailAddress",
            ResourcePropertyKind.Primitive,
            ResourceType.GetPrimitiveResourceType(typeof(string))
        );
        user.AddProperty(userEmailAddress);

        var customerSet = new ResourceSet("Customers", customer);
        var residentialCustomerSet = new ResourceSet("ResidentialCustomers", residentialCustomer);
        var userSet = new ResourceSet("Users", user);

        var userCustomer = new ResourceProperty(
            "Customer",
            ResourcePropertyKind.ResourceReference,
            customer
        );
        user.AddProperty(userCustomer);

        var customerUserList = new ResourceProperty(
            "UserList",
            ResourcePropertyKind.ResourceSetReference,
            user
        );
        customer.AddProperty(customerUserList);

        metadata.AddResourceType(customer);
        metadata.AddResourceSet(customerSet);
        metadata.AddResourceType(residentialCustomer);
        metadata.AddResourceSet(residentialCustomerSet);
        metadata.AddResourceType(user);
        metadata.AddResourceSet(userSet);

        ResourceAssociationSet customerUserListSet = new ResourceAssociationSet(
            "CustomerUserList",
            new ResourceAssociationSetEnd(
                customerSet,
                customer,
                customerUserList
            ),
            new ResourceAssociationSetEnd(
                userSet,
                user,
                userCustomer
            )
        );
        customerUserList.CustomState = customerUserListSet;
        userCustomer.CustomState = customerUserListSet;
        metadata.AddAssociationSet(customerUserListSet);

        return metadata;
    }

    public ODataServiceQueryProvider<T> GetQueryProvider(ODataServiceMetadataProvider metadata)
    {
        return new ODataServiceQueryProvider<T>(metadata);
    }

    public ODataServiceUpdateProvider<T> GetUpdateProvider(ODataServiceMetadataProvider metadata, ODataServiceQueryProvider<T> query)
    {
        return new ODataServiceUpdateProvider<T>(metadata, query);
    }

    public ODataService()
    {
        _metadata = GetMetadataProvider(typeof(T));
        _query = GetQueryProvider(_metadata);
        _updater = GetUpdateProvider(_metadata, _query);
    }
}

The DataContext class holds the CLR collections and wires up the service operations such as:

public partial class MyDataContext: IODataContext
{
    private List<Customer> _customers = null;
    public List<Customer> Customers
    {
        get
        {
            if (_customers == null)
            {
                _customers = DataManager.GetCustomers);
            }
            return _customers;
        }
    }

    private List<ResidentialCustomer> _residentialCustomers = null;
    public List<ResidentialCustomer> ResidentialCustomers
    {
        get
        {
            if (_residentialCustomers == null)
            {
                _residentialCustomers = DataManager.GetResidentialCustomers();
            }
            return _residentialCustomers;
        }
    }

    private List<User> _users = null;
    public List<User> Users
    {
        get
        {
            if (_users == null)
            {
                _users = DataManager.GetUsers();
            }
            return _users;
        }
    }

    public IQueryable GetQueryable(ResourceSet set)
    {
        if (set.Name == "Customers") return Customers.AsQueryable();
        if (set.Name == "ResidentialCustomers") return ResidentialCustomers.AsQueryable();
        if (set.Name == "Users") return Users.AsQueryable();
        throw new NotSupportedException(string.Format("{0} not found", set.Name));
    }

    public object CreateResource(ResourceType resourceType)
    {
        if (resourceType.InstanceType == typeof(Customer))
        {
            return new Customer();
        }
        if (resourceType.InstanceType == typeof(ResidentialCustomer))
        {
            return new ResidentialCustomer();
        }
        if (resourceType.InstanceType == typeof(User))
        {
            return new User();
        }
        throw new NotSupportedException(string.Format("{0} not found for creating.", resourceType.FullName));
    }

    public void AddResource(ResourceType resourceType, object resource)
    {
        if (resourceType.InstanceType == typeof(Customer))
        {
            Customer i = resource as Customer;
            if (i != null)
            {
                Customers.Add(i);
                return;
            }
        }
        if (resourceType.InstanceType == typeof(ResidentialCustomer))
        {
            ResidentialCustomeri = resource as ResidentialCustomer;
            if (i != null)
            {
                ResidentialCustomers.Add(i);
                return;
            }
        }
        if (resourceType.InstanceType == typeof(User))
        {
            Useri = resource as User;
            if (i != null)
            {
                Users.Add(i);
                return;
            }
        }
        throw new NotSupportedException(string.Format("{0} not found for adding.", resourceType.FullName));
    }

    public void DeleteResource(object resource)
    {
        if (resource.GetType() == typeof(Customer))
        {
            Customers.Remove(resource as Customer);
            return;
        }
        if (resource.GetType() == typeof(ResidentialCustomer))
        {
            ResidentialCustomers.Remove(resource as ResidentialCustomer);
            return;
        }
        if (resource.GetType() == typeof(User))
        {
            Users.Remove(resource as User);
            return;
        }
        throw new NotSupportedException(string.Format("{0} not found for deletion.", resource.GetType().FullName));
    }

    public void SaveChanges()
    {
        foreach (var item in Customers.Where(i => i.IsModified == true))
            item.Save();
        foreach (var item in ResidentialCustomers.Where(i => i.IsModified == true))
            item.Save();
        foreach (var item in Users.Where(i => i.IsModified == true))
            item.Save();
    }
}

Then, create your data service using the custom data service class and your data context, such as:

public class MyDataService : ODataService<MyDataContext>
{
    public static void InitializeService(DataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule("Customers", EntitySetRights.All);
        config.SetEntitySetAccessRule("ResidentialCustomers", EntitySetRights.All);
        config.SetEntitySetAccessRule("Users", EntitySetRights.All);
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
        config.DataServiceBehavior.AcceptProjectionRequests = true; 
    }
}

Lots of wiring up, but pretty straightforward once you've got the hang of it.



来源:https://stackoverflow.com/questions/9091034/how-can-you-expose-a-multi-layer-model-that-has-derived-classes-and-members-for

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!