Наследование не поддерживается даже при наличии собственного поставщика услуг OData? - PullRequest
3 голосов
/ 08 февраля 2012

РЕДАКТИРОВАТЬ : я создал небольшой проект для демонстрации этой проблемы (называемый RestfulTimesTest), доступный на SkyDrive .

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

Рассмотрим следующие 3 класса CLR: ResidentialCustomer, Customer и User. ResidentialCustomer extends Customer, Customer имеет список User с, а User имеет ссылку на Customer.

Проблема, с которой я сталкиваюсь, заключается в том, что метаданные могут включать ResidentialCustomer или связь между Customer и User, но не оба. Если я включаю оба, я получаю следующую ошибку при попытке отобразить или получить доступ к метаданным через DataService:

{System.NullReferenceException: Object reference not set to an instance of an object.
at System.Data.Services.Providers.DataServiceProviderWrapper.GetResourceAssociationSet(ResourceSetWrapper resourceSet, ResourceType resourceType, ResourceProperty resourceProperty)
at System.Data.Services.Serializers.MetadataSerializer.MetadataManager.GetAndValidateResourceAssociationSet(ResourceSetWrapper resourceSet, ResourceType resourceType, ResourceProperty navigationProperty)
at System.Data.Services.Serializers.MetadataSerializer.MetadataManager.PopulateAssociationsForSetAndType(ResourceSetWrapper resourceSet, ResourceType resourceType)
at System.Data.Services.Serializers.MetadataSerializer.MetadataManager.PopulateAssociationsForSet(ResourceSetWrapper resourceSet)
at System.Data.Services.Serializers.MetadataSerializer.MetadataManager..ctor(DataServiceProviderWrapper provider, IDataService service)
at System.Data.Services.Serializers.MetadataSerializer.GenerateMetadata(MetadataEdmSchemaVersion metadataEdmSchemaVersion, IDataService service)
at System.Data.Services.Providers.DataServiceProviderWrapper.WriteMetadataDocument(MetadataSerializer serializer, XmlWriter writer, IDataService service)
at System.Data.Services.Serializers.MetadataSerializer.WriteRequest(IDataService service)
at System.Data.Services.ResponseBodyWriter.Write(Stream stream)}

Проблема возникает, когда метод GetResourceAssociationSet в классе, который реализует IDataServiceMetadataProvider (подробности см. Ниже), вызывается с ResourceSet другого типа (ResidentialCustomer), чем передано в ResourceType (Customer):

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

Это вызывает сбой в методе класса .net библиотеки ResourceAssociationSet.GetResourceAssociationSetEnd, где он не может найти ResourceAssociationSetEnd, что впоследствии вызывает исключение нулевой ссылки.

Класс, реализующий IServiceProvider (подробнее см. Ниже), устанавливает метаданные. Он устанавливает связь между Customer и User следующим образом:

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

A ResidentialCustomer должен иметь доступ к своему списку User s, как и Customer. Объект, который наследует другой, должен иметь возможность использовать базовые ассоциации. Я не верю, что решение состоит в том, чтобы добавить еще одну связь между ResidentialCustomer и User, и попытка сделать это приводит к конфликтам свойств или неопределенным свойствам. Какая часть мне не хватает для установки ассоциаций между объектами, которые наследуются другими объектами?

Дополнительные сведения: Связанные классы для настраиваемого поставщика следующие:

Интерфейс для 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; 
    }
}

Ответы [ 3 ]

1 голос
/ 14 февраля 2012

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

1) A ResourceType не может расширить другой ResourceType (BaseType для ResourceType должно быть нулевым). В некоторой степени это ограничение может быть смягчено путем репликации всех свойств базового типа в расширенный тип в метаданных.

2) ResourceType или ResourceSet не могут быть вовлечены в ассоциацию, если его InstanceType расширяет другой InstanceType, указанный в метаданных, независимо от того, как установлены ResourceType s для каждого типа экземпляра вверх. Другими словами, в примере, если ResidentialCustomer расширяет Customer в модели CLR, я не могу использовать ResourceType или ResourceSet с InstanceType, равным ResidentialCustomer, в любой ассоциации. Я не знаю какого-либо обходного пути для этого, за исключением отсутствия предоставления этих необходимых связей через OData поставщика услуг.

Если я нарушу любое из двух указанных выше ограничений, я получаю исключение нулевой ссылки в методе класса библиотеки .net ResourceAssociationSet.GetResourceAssociationSetEnd().

Являются ли эти ограничения правильными, или есть какие-либо примеры, чтобы обойти эти ограничения или правильно настроить метаданные для этих сценариев?

1 голос
/ 16 июня 2017

У меня также была проблема наследования со службой odata v1, и я исправил ее с помощью неявного оператора:

class Example
{
    public class Object1
    {
        public int Property1 { get; set; }
        public string Property2 { get; set; }
    }

    public class Object2
    {
        private readonly Object1 _object1;

        public Object2()
        {
            _object1 = new Object1();
        }

        public int Property1
        {
            get { return _object1.Property1; }
            set { _object1.Property1 = value; }
        }
        public string Property2
        {
            get { return _object1.Property2; }
            set { _object1.Property2 = value; }
        }

        public static implicit operator Object1(Object2 o)
        {
            return o._object1;
        }
    }
}

Таким образом, вы можете привести переменную типа Object2 к Object1 и передать Object2всем методам, ожидающим Object1.

0 голосов
/ 11 февраля 2012

Проблема в том, что у вас есть 2 разных набора наборов - один для клиентов и один для ResidentialCustomer.

Проблема с точки зрения модели заключается в том, что для данного пользовательского экземпляра, который принадлежит набору «Пользователи», этот набор ссылается на свойство навигации «Клиент» - он может ссылаться на набор клиентов из клиентов или ResidentialCustomer.

Вопрос в том, почему вы хотите иметь 2 комплекта для клиентов и частных клиентов?Если вы хотите получить доступ к residentialCustomers из набора клиентов, вы всегда можете запросить /Customers/Namespace.ResidentialCustomer и получить все жилые клиенты из набора клиентов.

Если вы действительно хотите иметь 2 разных набора дляcustomer и residentialcustomer, затем имеют базовый класс (CustomerBase), который имеет все общие свойства между двумя типами, а затем определяют свойство навигации Users как для Customer, так и для ResidentialCustomer с различным набором ассоциаций.

Надеюсь, это поможет.

...