编辑:我继续并创建了一个小项目来演示此问题(称为RestfulTimesTest),该项目可在SkyDrive上获得。
我基于Creating a Data Service Provider上的OData
优秀博客文章,构建了一个自定义的Alex James'
服务提供商,以将查询和更新提供给自定义模型。
请考虑以下3个CLR类:ResidentialCustomer
,Customer
和User
。 ResidentialCustomer
扩展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
():public ResourceAssociationSet GetResourceAssociationSet(ResourceSet resourceSet, ResourceType resourceType, ResourceProperty resourceProperty)
{
return resourceProperty.CustomState as ResourceAssociationSet;
}
这会导致.net库类方法
ResidentialCustomer
失败,无法找到ResourceType
,随后导致空引用异常。实现
Customer
的类(请参见下面的更多详细信息)设置元数据。设置ResourceAssociationSet.GetResourceAssociationSetEnd
和ResourceAssociationSetEnd
之间的关联,如下所示: ResourceAssociationSet customerUserListSet = new ResourceAssociationSet(
"CustomerUserList",
new ResourceAssociationSetEnd(
customerSet,
customer,
customerUserList
),
new ResourceAssociationSetEnd(
userSet,
user,
userCustomer
)
);
customerUserList.CustomState = customerUserListSet;
userCustomer.CustomState = customerUserListSet;
metadata.AddAssociationSet(customerUserListSet);
IServiceProvider
应该像Customer
一样有权访问其User
列表。继承另一个对象的对象应该能够使用基本关联。我不认为解决方案是在ResidentialCustomer
和User
之间添加另一个关联,并且尝试这样做会导致属性冲突或未定义的属性。在其他对象继承的对象之间建立关联时,我缺少什么?额外细节:
自定义提供程序的关联类如下:
Customer
类的接口,例如:public interface IODataContext
{
IQueryable GetQueryable(ResourceSet set);
object CreateResource(ResourceType resourceType);
void AddResource(ResourceType resourceType, object resource);
void DeleteResource(object resource);
void SaveChanges();
}
一个实现
ResidentialCustomer
的类,例如: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() { }
}
一个实现
User
的类,例如: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;
}
}
一个实现
DataContext
的类,例如: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>();
}
}
一个实现
IDataServiceMetadataProvider
的类,例如: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);
}
}
IDataServiceQueryProvider
类保存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;
}
}
最佳答案
经过进一步测试,似乎目前对元数据有两个严格的限制,以便使用自定义(或任何?)OData
服务提供者公开它:
1)ResourceType
不能扩展另一个ResourceType
(BaseType
的ResourceType
必须为空)。在某种程度上,可以通过将基本类型的所有属性复制到元数据中的扩展类型中来缓解此约束。
2)如果ResourceType
或ResourceSet
扩展了元数据中引用的另一个InstanceType
,则无论该InstanceType
或每种实例类型的ResourceType
如何设置,都不能参与该关联。换句话说,在示例中,如果ResidentialCustomer
在CLR模型中扩展了Customer
,则在任何关联中都不能将ResourceType
或ResourceSet
与InstanceType
的ResidentialCustomer
一起使用。除了不通过OData
服务提供商提供这些必要的关联外,我不知道有任何解决方法。
如果违反以上2个约束中的任何一个,则在.net库类方法ResourceAssociationSet.GetResourceAssociationSetEnd()
中会得到空引用异常。
这些约束是否正确,或者是否有任何示例可解决这些约束或针对这些情况正确设置元数据?
关于c# - 即使使用自定义OData服务提供程序,也不支持继承?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/9182999/