zoukankan      html  css  js  c++  java
  • 自定义Data Service Providers —(9)关系

    完整教程目录请参见:《自定义Data Service Providers — 简介

    在前面的章节中,我们创建了可以读/写的Data Service Provider,虽然看起来比较简陋,因为他缺少一个重要概念:关系。

    那么,让我们重新修订一下我们的程序,来建立一个跟“真实”世界更相近的模型。

    修改之前的类

    还记得在之前的第三章节中,我们建立了一个Product类,现在我们再添加一个Category类,用来阐述一对多的关系。

    public class Category

    {

        private List<Product> _products = new List<Product>();

     

        public int ID { get; set; }

        public string Name { get; set; }

        public List<Product> Products { get { return _products; } }

    }

     

    public class Product

    {

        public int ProdKey { get; set; }

        public string Name { get; set; }

        public Decimal Price { get; set; }

        public Decimal Cost { get; set; }

        public Category Category { get; set; }

    }

    修改元数据定义

    通过元数据建立关系

    现在我们已经定义好了实体类,下一步我们需要向Data Services暴露ProductCategory之间的关系。

    处理描述关系外,大部分还是比较简单的。

    我们开始创建“导航”属性:

    var productType = …; // ProductData Services中的ResourceType

    var categoryType = …; // Category Data Services中的ResourceType

     

    // 通知 Data ServicesProduct包含Category 的引用

    var prodCategory = new ResourceProperty(

        "Category",

        ResourcePropertyKind.ResourceReference,

        categoryType

    );

    productType.AddProperty(prodCategory);

     

    // 通知 Data Services Category包含Products 集合属性

    var catProducts = new ResourceProperty(

        "Products",

        ResourcePropertyKind.ResourceSetReference,

        productType

    );

    categoryType.AddProperty(catProducts);

    由于API设计原理的原因,你必须在创建完所有的专用属性后,再创建和添加导航属性到属性集的末尾部分。

    这意味这你需要建立两个方法,一个方法用于添加专用属性(Primitive Properties),一个用于添加导航属性(Navigation Properties)

    你还需要注意到,Product.Category是一个引用属性(ResourceReference),相当于 1…0,1的关系,他指向一个(或0个)Category。而Catrgory.Products是一个集合属性(ResourceSetReference),相当于 1…0,n的关系,他指向一批Product

    现在我们还需要为CategoryProduct建立两个资源集:

    var productsSet = new ResourceSet("Products", productType);

    var categoriesSet = new ResourceSet("Categories", categoryType);

    做完这些后,我们还需要创建AssociationSet,用来将两个导航属性和资源集联系在一起。

    ResourceAssociationSet productCategoryAssociationSet = new ResourceAssociationSet(

        "ProductCategory",

        new ResourceAssociationSetEnd(

           productsSet,

           productType, 

           prodCategory

        ),

        new ResourceAssociationSetEnd(

           categoriesSet,

           categoryType, 

           catProducts

        )

    );

    上面的代码意思是告诉Data Services

    product.Category = category;

    等同于:

    category.Products.Add(product);

    下一步,我们需要找个地方存放ResourceAssociationSet的值,我选择放在CustomState属性上:

    prodCategory.CustomState = productCategoryAssociationSet;

    catProducts.CustomState = productCategoryAssociationSet;

    如果你以前做过VB开发,你应该记得Form表单上有个Tag属性可以存放任何类型的临时数据,这个ResourceProperty.CustomState也是相同的意思。

    其实将ResourceAssociationSet存储到ResourceProperty.CustomState属性上并不是必须的,这里我们只是为了简化我们的代码,方便后面的访问。

    最后,我们还需要将这些元数据定义提交给DSPMetadataProvider,包括两个资源类型(ResourceType),两个资源集(ResourceSet),以及一个关系(ResourceAssociationSet)

    metadata.AddResourceType(productType);

    metadata.AddResourceSet(productsSet);

    metadata.AddResourceType(categoryType);

    metadata.AddResourceSet(categoriesSet);

    metadata.AddAssociationSet(productCategoryAssociationSet);

    修改IDataServiceMetadataProvider定义

    我们还需要修改DSPMetadataProvider代码,让其支持关系。

    首先定义一个变量记录关系的定义。

    List<ResourceAssociationSet> _associationSets 

       = new List<ResourceAssociationSet>();

    然后我们添加一个方法用于加入关系定义。

    public void AddAssociationSet(ResourceAssociationSet aset)

    {

        _associationSets.Add(aset);

    }

    最后,我们还需要更新IDataServiceMetadataProvider.GetResourceAssociationSet(..)方法:

    public virtual ResourceAssociationSet GetResourceAssociationSet(

       ResourceSet resourceSet,

       ResourceType resourceType,

       ResourceProperty resourceProperty

    )

    {

        return resourceProperty.CustomState as ResourceAssociationSet

    }

    你应该还记得上面我们将关系放在CustomState上,在这里就派上用场了。

    当然你如果不喜欢这种方法,你还可以使用LINQ_associationSets上查询,但是显然这样会慢而且复杂了些。

    看看元数据是否工作

    我们可以通过在浏览器中键入: http://localhost/sample.svc来查看服务契约,或者http://localhost/samplesvc/$metadata来查看元数据信息。(在VS调试环境下需要加入对应的动态端口)。

    下一步,我们开始修改IDataServiceQueryProvider,来让查询和更新可以正常工作。

    查询

    更新数据源

    现在,我们需要在ProductsContext上添加一个Categories属性用来存放分类列表,对应的属性也要添加。

    public class ProductsContext: DSPContext

    {

        private static List<Product> _products 

           = new List<Product>();

     

        private static List<Category> _categories

           = new List<Category>();

     

        public override IQueryable GetQueryable(

           ResourceSet resourceSet)

        {

            if (resourceSet.Name == "Products")

               return Products.AsQueryable();

            else if (resourceSet.Name == "Categories")

               return Categories.AsQueryable();

     

            throw new NotSupportedException(

               string.Format("{0} not found", resourceSet.Name));

        }

     

        public static List<Product> Products{

           get { return _products; }

        }

     

        public static List<Category> Categories {

           get { return _categories; }

        }

     

        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;

                }

            }

            else if (resourceType.InstanceType == typeof(Category))

            {

                Category c = resource as Category;

                if (c != null)

                {

                    Categories.Add(c);

                    return;

                }

            }

            throw new NotSupportedException(

               string.Format("{0} not found", resourceType.FullName));

        }

     

        public override void DeleteResource(object resource)

        {

            if (resource.GetType() == typeof(Product))

            {

                Products.Remove(resource as Product);

                return;

            }

            else if (resource.GetType() == typeof(Category))

            {

                Categories.Remove(resource as Category);

                return;

            }

            throw new NotSupportedException("Resource Not Found");

        }

     

        public override object CreateResource(

           ResourceType resourceType)

        {

            if (resourceType.InstanceType == typeof(Product))

                return new Product();

            else if (resourceType.InstanceType == typeof(Category))

                return new Category();

            throw new NotSupportedException(

               string.Format("{0} not found", resourceType.FullName));

        }

     

        public override void SaveChanges()

        {

            var prodKey = Products.Max(p => p.ProdKey);

            foreach (var prod in Products.Where(p => p.ProdKey == 0))

                prod.ProdKey = ++prodKey;

     

            var catKey = Categories.Max(p => p.ID);

            foreach (var cat in Categories.Where(c => c.ID == 0))

                cat.ID = ++catKey;

        }

    }

    正如你所看见的,我只是很机械的增加了对应的代码。在真实的环境下,我肯定会重构这部分代码,要是再加入一个新的类型我就会更痛苦了。

    但是,在这里我就不管这么多了,让我们继续其他的内容。

    现在就剩下添加一些测试数据了。

    protected override ProductsContext CreateDataSource()

    {

        if (ProductsContext.Products.Count == 0)

        {

            var bovril = new Product

            {

                ProdKey = 1,

                Name = "Bovril",

                Cost = 4.35M,

                Price = 6.49M

            };

            var marmite = new Product

            {

                ProdKey = 2,

                Name = "Marmite",

                Cost = 4.97M,

                Price = 7.21M

            };

            var food = new Category

            {

                ID = 1,

                Name = "Food"

            };

            food.Products.Add(bovril);

            bovril.Category = food;

            food.Products.Add(marmite);

            marmite.Category = food;

            ProductsContext.Categories.Add(food);

            ProductsContext.Products.Add(bovril);

            ProductsContext.Products.Add(marmite);

        }

        return base.CreateDataSource();

    }

    你需要注意的是,在上面的代码中ProductCategory使用了两段代码建立了双向的关系,但是在诸如Entity Framework这样的系统中,不需要两边都建立关系,仅建立一边的关系,那么EF会自动建立另外一边的关系。

    不管咋样,我们运行代码就能够看见这些关系已经开始工作了。

    例如:获取某个物料的分类

    以及获取一个分录下的所有物料

    有趣的是,我们并没有对IDataServiceQueryProvider进行任何修改,其查询就已经正常工作了。

    更新部分的修改

    最后,我们将完成难度比较大的更新部分。

    基本上就是实现之前未实现的三个方法,还记得之前的这段代码吗?

    public void SetReference(

       object targetResource,

       string propertyName,

       object propertyValue)

    {

        throw new NotImplementedException();

    }

    public void AddReferenceToCollection(

       object targetResource,

       string propertyName,

       object resourceToBeAdded)

    {

        throw new NotImplementedException();

    }

    public void RemoveReferenceFromCollection(

       object targetResource,

       string propertyName,

       object resourceToBeRemoved)

    {

        throw new NotImplementedException();

    }

    你应该还记得,在之前关于更新的代码中,我们都是将所有的操作延迟到IDataServiceUpdateProvider.SaveChanges()上批量执行的,在当前的SetReference方法上我们也是这么干。

    为方便起见,我这里先罗列一部分方法,实际真实的代码在后面:

    public void SetReference(

       object targetResource,

       string propertyName,

       object propertyValue)

    {

        _actions.Add(() => ReallySetReference(

                         targetResource,

                         propertyName, 

                         propertyValue));

    }

     

     

    public void AddReferenceToCollection(

       object targetResource,

       string propertyName,

       object resourceToBeAdded)

    {

        _actions.Add(() => ReallyAddReferenceToCollection(

                         targetResource,

                         propertyName,

                         resourceToBeAdded));

    }

     

    public void RemoveReferenceFromCollection(

       object targetResource,

       string propertyName,

       object resourceToBeRemoved)

    {

        _actions.Add(() => ReallyRemoveReferenceFromCollection(

                          targetResource,

                          propertyName,

                          resourceToBeRemoved));

    }

    那么这些ReallyXXX()这样的方法是啥样子的呢?

    在我们的实体类(ProductCategory)中,关系是双向的,但是我们的类并没有自动维护其双向的关系,所以这里我们必须自己手工修复这种关系。

    ReallySetReference(…)看起来像这样的。

    public void ReallySetReference(

       object targetResource,

       string propertyName,

       object propertyValue)

    {

        // 获取资源的CLR类型.

        var targetType = targetResource.GetType();

     

        // 找到CLR Type对应的ResourceType

        var targetResourceType =

             _metadata.Types.Single(t => t.InstanceType==targetType);

     

        var targetResourceTypeProperty = targetResourceType

            .Properties

            .Single(p => p.Name == propertyName);

     

        var associationSet = targetResourceTypeProperty

            .CustomState as ResourceAssociationSet

     

        // 获取关系

        var relatedAssociationSetEnd = associationSet.End1

            .ResourceProperty == targetResourceTypeProperty ? 

            associationSet.End2 : associationSet.End1;

     

        var relatedResourceTypeProperty = relatedAssociationSetEnd

                                             .ResourceProperty;

     

        // 获取关联的类型和属性

        Type relatedType = relatedAssociationSetEnd

             .ResourceType

             .InstanceType;

     

        var targetTypeProperty = targetType

            .GetProperties()

            .Single(p => p.Name == propertyName);

     

        var relatedTypeProperty = 

            relatedResourceTypeProperty == null ?

            null :

            relatedType.GetProperties()

            .Single(p => p.Name == relatedResourceTypeProperty.Name);

     

        // 我们需要修正关系的另外一段

        if (relatedResourceTypeProperty != null)

        {

            // 获取另外一段的关系和值

            object originalValue = targetTypeProperty

               .GetPropertyValueFromTarget(targetResource);

     

            if (originalValue != null)

            {

                if (relatedAssociationSetEnd.ResourceProperty.Kind ==

                    ResourcePropertyKind.ResourceReference)

                {

                    // the other end is a reference so we check

                    // that it is pointing to the targetResource

                    // before nulling out.

                    var backPointer =

                       relatedTypeProperty

                       .GetPropertyValueFromTarget(originalValue);

     

                    if (object.ReferenceEquals(

                           backPointer, 

                           targetResource)

                    )

                        relatedTypeProperty.SetPropertyValueOnTarget(

                            originalValue, null);

                }

                else if (relatedAssociationSetEnd

                         .ResourceProperty.Kind == 

                         ResourcePropertyKind.ResourceSetReference)

                {

                    // the other end is a collection

                    // (i.e. a List in our implementation) so we can

                    // safely remove targetResource without checking

                    // whether it is in that list or not

                    (relatedTypeProperty

                       .GetPropertyValueFromTarget(originalValue)

                       as IList

                    ).Remove(targetResource);

                }

            }

            // Add the relationship to the other end...

            if (propertyValue != null)

            {

                if (relatedAssociationSetEnd.ResourceProperty.Kind == 

                    ResourcePropertyKind.ResourceReference)

                {

                    relatedTypeProperty.SetPropertyValueOnTarget(

                       propertyValue, targetResource);

                }

                else if (relatedAssociationSetEnd

                         .ResourceProperty.Kind ==

                          ResourcePropertyKind.ResourceSetReference)

                {

                    (relatedTypeProperty

                        .GetPropertyValueFromTarget(propertyValue) 

                        as IList

                    ).Add(targetResource);

                }

            }

        }

        // actually set the reference !

        targetTypeProperty.SetPropertyValueOnTarget(

           targetResource, propertyValue

        );

    }

    看起来很复杂,但是如果你仔细看这些代码应该能够找到修复关系的逻辑。事实上,如果ProductCategory是很固定的关系,使用下面的代码就足够了:

    public void ReallySetReference(

       object targetResource,

       string propertyName,

       object propertyValue)

    {

        // Get the resource type.

        var targetType = targetResource.GetType();

     

        var targetTypeProperty = targetType

            .GetProperties()

            .Single(p => p.Name == propertyName);

     

        // actually set the reference !

        targetTypeProperty.SetPropertyValueOnTarget(

           targetResource, propertyValue

        );

    }

    正如你所看见的,这要好多了,如果你使用自己的DSP扩展不妨可以使用这段代码。

    在这段程序中,我还使用了扩展方法功能,这让代码书写起来更平滑。

    public static void SetPropertyValueOnTarget(

       this PropertyInfo property, 

       object target,

       object value)

    {

        property.GetSetMethod()

                .Invoke(target,new object[] { value });

    }

    public static object GetPropertyValueFromTarget(

       this PropertyInfo property,

       object target)

    {

        return property.GetGetMethod()

                       .Invoke(target, new object[] { });

    }

    下一步,我们还需要实现ReallyRemoveReferenceFromCollection(..) ReallyAddReferenceToCollection(..),已实现双向的同步。

    但是!我想把这些内容留给聪明的作者。

    当我们完成这些实现,我们就完成了“强类型,支持关系的Data Service”了。

    让我们庆祝吧!

  • 相关阅读:
    Python 以指定列宽格式化字符串
    Windows环境下QWT安装及配置
    iOS用户体验之-modal上下文
    android-调用系统的ContentPrivder获取单张图片实现剪切做头像及源代码下载
    Codeforces Round #253 (Div. 1) A Borya and Hanabi
    剑指offer 面试题9
    Memcache应用场景介绍
    [Unity-22] Coroutine协程浅析
    ZOJ 2706 Thermal Death of the Universe (线段树)
    为什么不建议用Table布局
  • 原文地址:https://www.cnblogs.com/tansm/p/DSP9.html
Copyright © 2011-2022 走看看