【1.用反射数据提供程序实现的DataService】
在前面的文章里直接定义了 WcfService 继承于 DataService<T> , 它使用的是:ReflectionServiceProvider 提供程序通过使用反射来推断数据模型。
以下列表显示了反射提供程序如何推断数据模型:
- 实体容器 – 将数据作为可返回 IQueryable 实例的属性公开的类。对基于反射的数据模型进行寻址时,实体容器表示服务的根。对给定命名空间仅支持一个实体容器类。
- 实体集 – 返回 IQueryable 实例的属性视为实体集。在查询中,将把实体集作为资源直接进行寻址。实体容器中只有一个属性才能返回给定类型的 IQueryable 实例。
- 实体类型 – 实体集返回的 IQueryable 的类型 T。反射提供程序将作为继承层次结构一部分的类转换为等效的实体类型层次结构
- 实体键 – 作为实体类型的每个数据类必须具有一个键属性。该属性具有 DataServiceKeyAttribute 特性 ([DataServiceKeyAttribute])。
【2.自定义Provider实现的DataService】
如何自定义一个 ServiceProvider 来实现 DataService (具体参考: http://blogs.msdn.com/b/alexj/archive/tags/dsp/)
DSPDataService<T> 继承于 DataService<T> 实现 IServiceProvider 接口,而 ProductDataService 继承于它,返回 IDataServiceMetadataProvider, IDataServiceQueryProvider, IDataServiceUpdateProvider 的实例。它是 DataService 的入口。而这3个接口顾名思义分别提供:元数据,查询,更新的功能。
DSPDataService.cs
public abstract class DSPDataService<T> : DataService<T>, IServiceProvider where T : DSPContext
{
private IDataServiceMetadataProvider _metadata;
private IDataServiceQueryProvider _query;
private IDataServiceUpdateProvider _update;
public DSPDataService()
{
_metadata = GetMetadataProvider(typeof(T));
_query = GetQueryProvider(_metadata);
_update = GetUpdateProvider(_metadata, _query);
}
public object GetService(Type serviceType)
{
if (serviceType == typeof(IDataServiceMetadataProvider))
return _metadata;
else if (serviceType == typeof(IDataServiceQueryProvider))
return _query;
else if (serviceType == typeof(IDataServiceUpdateProvider))
return _update;
else
return null;
}
public abstract IDataServiceMetadataProvider GetMetadataProvider(Type dataSourceType);
public abstract IDataServiceQueryProvider GetQueryProvider(IDataServiceMetadataProvider metadata);
public abstract IDataServiceUpdateProvider GetUpdateProvider(IDataServiceMetadataProvider metadata, IDataServiceQueryProvider query);
}
DSPContext.cs
public abstract class DSPContext
{
public abstract IQueryable GetQueryable(ResourceSet set);
public abstract object CreateResource(ResourceType resourceType);
public abstract void AddResource(ResourceType resourceType, object resource);
public abstract void DeleteResource(object resource);
public abstract void SaveChanges();
}
可以看到上面两个都是抽象类,具体实现类是:ProductDataService.svc 和 ProductsContext.cs。这儿 ProductsContext.cs 就是自定义数据模型的容器。
ProductDataService.svc.cs
(1) 重写了 DataService.CreateDataSource 返回 ProductsContext 实例
(2) 重写了 DSPDataService.GetMetadataProvider 返回 DSPMetadataProvider 实例
(3) 重写了 DSPDataService.GetQueryProvider 返回 DSPQueryProvider 实例
(4) 重写了 DSPDataService.GetUpdateProvider 返回 DSPUpdateProvider 实例
注意:GetMetadataProvider 部分的代码,这里手动添加了对应的 ResourceType(及包含的 ResourceProperty)
using System;
using System.Collections.Generic;
using System.Data.Services;
using System.Data.Services.Common;
using System.Linq;
using System.ServiceModel.Web;
using System.Web;
using System.Data.Services.Providers;
namespace WcfCustomerDataProviderDataService
{
public class ProductDataService : DSPDataService<ProductsContext>
{
// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}
protected override ProductsContext CreateDataSource()
{
ProductsContext context = new ProductsContext();
context.Products.Add(
new Product
{
ProdKey = 1,
Name = "Bovril",
Cost = 4.35M,
Price = 6.49M
});
context.Products.Add(
new Product
{
ProdKey = 2,
Name = "Marmite",
Cost = 4.97M,
Price = 7.21M
});
return context;
}
public override IDataServiceMetadataProvider GetMetadataProvider(Type dataSourceType)
{
DSPMetadataProvider metadata = new DSPMetadataProvider();
var productType = new ResourceType(
typeof(Product), // CLR type backing this Resource
ResourceTypeKind.EntityType, // Entity, ComplexType etc
null, // BaseType
"Namespace", // Namespace
"Product", // Name
false // Abstract?
);
var prodKey = new ResourceProperty(
"ProdKey",
ResourcePropertyKind.Key |
ResourcePropertyKind.Primitive,
ResourceType.GetPrimitiveResourceType(typeof(int))
);
var prodName = new ResourceProperty(
"Name",
ResourcePropertyKind.Primitive,
ResourceType.GetPrimitiveResourceType(typeof(string))
);
var prodPrice = new ResourceProperty(
"Price",
ResourcePropertyKind.Primitive,
ResourceType.GetPrimitiveResourceType(typeof(Decimal))
);
productType.AddProperty(prodKey);
productType.AddProperty(prodName);
productType.AddProperty(prodPrice);
metadata.AddResourceType(productType);
metadata.AddResourceSet(new ResourceSet("Products", productType));
return metadata;
}
public override IDataServiceQueryProvider GetQueryProvider(IDataServiceMetadataProvider metadata)
{
return new DSPQueryProvider<ProductsContext>(metadata);
}
public override IDataServiceUpdateProvider GetUpdateProvider(IDataServiceMetadataProvider metadata, IDataServiceQueryProvider query)
{
return new DSPUpdateProvider<ProductsContext>(metadata, query);
}
}
}
ProductsContext.cs实现对数据的增,删,改,查
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Services.Providers;
namespace WcfCustomerDataProviderDataService
{
public class ProductsContext : DSPContext
{
private List<Product> _products = new List<Product>();
public override IQueryable GetQueryable(ResourceSet set)
{
if (set.Name == "Products")
return Products.AsQueryable();
throw new NotSupportedException(string.Format("{0} not found", set.Name));
}
public List<Product> Products
{
get { return _products; }
}
public override object CreateResource(ResourceType resourceType)
{
if (resourceType.InstanceType == typeof(Product))
{
return new Product();
}
throw new NotSupportedException(string.Format("{0} not found", resourceType.FullName));
}
public override void AddResource(ResourceType resourceType, object resource)
{
if (resourceType.InstanceType == typeof(Product))
{
Product p = resource as Product;
if (p != null)
{
Products.Add(p);
return;
}
}
throw new NotSupportedException("Type not found");
}
public override void DeleteResource(object resource)
{
if (resource.GetType() == typeof(Product))
{
Products.Remove(resource as Product);
return;
}
throw new NotSupportedException("Type not found");
}
public override void SaveChanges()
{
var prodKey = Products.Max(p => p.ProdKey);
foreach (var prod in Products.Where(p => p.ProdKey == 0))
prod.ProdKey = ++prodKey;
}
}
}
DSPMetadataProvider.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Services.Providers;
using System.Reflection;
using System.Data.Services;
namespace WcfCustomerDataProviderDataService
{
public class DSPMetadataProvider : IDataServiceMetadataProvider
{
private Dictionary<string, ResourceType> _resourceTypes
= new Dictionary<string, ResourceType>();
private Dictionary<string, ResourceSet> _resourceSets
= new Dictionary<string, ResourceSet>();
//private Dictionary<string, ServiceOperation> serviceOperations
// = new Dictionary<string, ServiceOperation>();
public DSPMetadataProvider() { }
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 string ContainerName
{
get { return "Container"; }
}
public string ContainerNamespace
{
get { return "Namespace"; }
}
public IEnumerable<ResourceType> GetDerivedTypes(ResourceType resourceType)
{
// We don't support type inheritance yet
yield break;
}
public ResourceAssociationSet GetResourceAssociationSet(
ResourceSet resourceSet,
ResourceType resourceType,
ResourceProperty resourceProperty)
{
throw new NotImplementedException("No relationships.");
}
public bool HasDerivedTypes(ResourceType resourceType)
{
// We don’t support inheritance yet
return false;
}
public IEnumerable<ResourceSet> ResourceSets
{
get { return this._resourceSets.Values; }
}
public IEnumerable<ServiceOperation> ServiceOperations
{
// No service operations yet
get { yield break; }
}
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)
{
// No service operations are supported yet
serviceOperation = null;
return false;
}
public IEnumerable<ResourceType> Types
{
get { return this._resourceTypes.Values; }
}
}
}
DSPQueryProvider.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Services.Providers;
using System.Reflection;
using System.Globalization;
namespace WcfCustomerDataProviderDataService
{
public class DSPQueryProvider<T> : IDataServiceQueryProvider where T : DSPContext
{
private T _currentDataSource;
private IDataServiceMetadataProvider _metadata;
public DSPQueryProvider(IDataServiceMetadataProvider metadata)
{
// TODO: Complete member initialization
_metadata = metadata;
}
public object CurrentDataSource
{
get { return _currentDataSource; }
set { _currentDataSource = value as T; }
}
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)
{
return null;
}
public bool IsNullPropagationRequired
{
get { throw new NotImplementedException(); }
}
}
}
DSPUpdateProvider.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Services.Providers;
namespace WcfCustomerDataProviderDataService
{
public class DSPUpdateProvider<T> : IDataServiceUpdateProvider where T : DSPContext
{
private IDataServiceMetadataProvider _metadata;
private IDataServiceQueryProvider _query;
private List<Action> _actions;
public DSPUpdateProvider(IDataServiceMetadataProvider metadata, IDataServiceQueryProvider query)
{
_metadata = metadata;
_query = query;
_actions = new List<Action>();
}
public T GetContext()
{
return (_query.CurrentDataSource as T);
}
public void SetConcurrencyValues(object resourceCookie, bool? checkForEquality, IEnumerable<KeyValuePair<string, object>> concurrencyValues)
{
throw new NotImplementedException();
}
public void AddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded)
{
throw new NotImplementedException();
}
public void ClearChanges()
{
_actions.Clear();
}
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 GetValue(object targetResource, string propertyName)
{
var value = targetResource
.GetType()
.GetProperties()
.Single(p => p.Name == propertyName)
.GetGetMethod()
.Invoke(targetResource, new object[] { });
return value;
}
public void RemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved)
{
throw new NotImplementedException();
}
public object ResetResource(object resource)
{
_actions.Add(() => ReallyResetResource(resource));
return resource;
}
public void ReallyResetResource(object resource)
{
// Create an new 'blank' instance of the resource
var clrType = resource.GetType();
ResourceType resourceType = _metadata.Types.Single(t => t.InstanceType == clrType);
var resetTemplate = GetContext().CreateResource(resourceType);
// Copy non-key property values from the 'blank' resource
foreach (var prop in resourceType.Properties.Where(p => (p.Kind & ResourcePropertyKind.Key)
!= ResourcePropertyKind.Key))
{
// Obviously for perf reasons you could might want to
// cache the result of these reflection calls.
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 void SaveChanges()
{
_actions.ForEach(a => a());
GetContext().SaveChanges();
}
public void SetReference(object targetResource, string propertyName, object propertyValue)
{
throw new NotImplementedException();
}
public void SetValue(object targetResource, string propertyName, object propertyValue)
{
_actions.Add(
() => ReallySetValue(
targetResource,
propertyName,
propertyValue)
);
}
public void ReallySetValue(object targetResource, string propertyName, object propertyValue)
{
targetResource
.GetType()
.GetProperties()
.Single(p => p.Name == propertyName)
.GetSetMethod()
.Invoke(targetResource, new[] { propertyValue });
}
}
}
(2011/7/25 Updated)
遗留问题:通过自定义Provider并不能实现类似使用反射数据提供程序实现的DataService的完整功能,至少比如像 http://localhost:51396/ProductDataService.svc/Products() ?$filter=Name eq 'fx'这样的查询功能都不支持。根据 DataService<T> 源码分析还需要实现比如:TryResolveServiceOperation,InvokeServiceOperation 以及 IDataServicePagingProvider 接口 等等。DataService<T> 水深啊,需要进一步研究。