Как вы можете представить многоуровневую модель, которая имеет производные классы и члены для служб OData? - PullRequest
2 голосов
/ 01 февраля 2012

Я пытаюсь выставить модель, которая будет доступна для OData услуг.Подход, который я сейчас использую, следующий:

1) Определение класса в модели для показа IQueryable коллекций, таких как:

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

2) НастройкаWCF DataService с классом запрашиваемой коллекции, таким как:

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;
    }
}

При таком подходе я сталкиваюсь с 3 проблемами и / или ограничениями:

1) Я не могу добавитьлюбые коллекции производных классов в списках IQueryable.

2) Я должен применить атрибут IgnoreProperties, чтобы скрыть все члены, производные от базового типа.

3) Яневозможно предотвратить доступ к нежелательным объектам со стороны службы OData и вызвать ошибки.Например, я хочу, чтобы отображались только объекты слоя BLL, но похоже, что модель отражается далеко за пределами членов классов, которые я добавила в список с запросом, и выбирает все классы DAL, что приводит к неопределенности ошибок, а такжес тем же именем, что и классы BLL.Нет ссылок на классы DAL от членов класса BLL.По крайней мере, я бы хотел, чтобы эти классы полностью игнорировались.

Буду очень признателен за любые указания о том, как решить любую из этих проблем.Должен ли я сделать другой подход к этому?Например, я должен внедрить IQueryable непосредственно в мои коллекции моделей?

Спасибо.

Ответы [ 2 ]

2 голосов
/ 07 февраля 2012

Поставщик отражений не предназначен для работы с моделями данных с достаточным количеством наследования и других зависимостей. Я закончил тем, что создал собственного провайдера, который мог бы обрабатывать запросы, обновления, наследование и отношения на основе превосходного сообщения в блоге Алекса Джеймса о Создание поставщика услуг данных .

Пример реализации с 3 классами CLR: ResidentialCustomer, Customer и User представлен ниже. ResidentialCustomer extends Customer, Customer имеет список User s, а User имеет ссылку на Customer.

Интерфейс для DataContext классов, таких как:

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

Класс для реализации IDataServiceMetadataProvider, такой как:

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() { }
}

Класс для реализации IDataServiceQueryProvider, такой как:

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;
    }
}

Класс для реализации IDataServiceUpdateProvider, такой как:

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>();
    }
}

Класс для реализации IServiceProvider, такой как:

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);
    }
}

Класс DataContext содержит коллекции CLR и связывает сервисные операции, такие как:

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();
    }
}

Затем создайте службу данных, используя пользовательский класс службы данных и контекст данных, например:

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; 
    }
}

Много проводов, но довольно просто, как только вы это поняли.

2 голосов
/ 01 февраля 2012

Поставщик отражений, который вы используете, предназначен для обхода всех открытых типов / свойств. Так что №3 и, возможно, даже №2 (который я не до конца понимаю, в чем проблема) из-за этого придуманы.

# 1 также задуман, но по другой причине - поставщик отражений может предоставлять только один набор сущностей для каждой иерархии типов. Он не поддерживает так называемый «MEST» (множественные наборы сущностей для каждого типа), потому что он не знает, какой из них выбрать. Требуется отображение 1: 1 между типами сущностей и наборами сущностей.

Поставщик услуг отражения предназначен для простых услуг, которые «просты» в настройке. Он определенно не предназначен для настройки.

Если вам нужен больший контроль, то вам нужен пользовательский поставщик, который может быть реализован либо напрямую (если он основан на существующих классах CLR, это не так сложно), либо с помощью некоторой библиотеки, как тот, который предложен в комментариях выше.

...