zoukankan      html  css  js  c++  java
  • 用于MongoDB的WCF Data Services Provider的实现尝试

    WCF Data Services以前称之为ADO.NET Data Services,在.NET 4.0中发布了第二个版本。通过WCF Data Services可以发布符合OData标准的数据接口,让各种各样的Client来消费这些数据,而且也可以通过一些谓词来操纵数据。

    关于WCF Data Services的相关介绍,可以参看:http://www.cnblogs.com/shanyou/archive/2010/02/14/1668210.html

    作为一个数据暴露服务,当然可以支持后端各种数据源的展示,WCF Data Services默认支持实体框架(Entity Framework),同时提供了一些扩展接口以支持大家实现自己的Provider。

    我们首先要确定,是否需要自己实现Provider:

    • 如果你的数据可以用EF来访问,那么就不用实现
    • 如果你的数据通过LINQtoSQL来访问,那么也有一个现成的Provider使用:ADO.NET Data Services IUpdateable implementation for Linq to Sql
    • 如果你的数据是一些自定义实体对象,和一些对象集合构成,那么使用Reflection Provider(反射提供者),如果需要编辑数据,就同时实现IUpdatable。实际上LINQtoSQL的Provider就是这样实现的。
    • 实现诸多接口,来完全进行自定义。

    对于大部分情况,Reflection Provider已经足够,比如对于对象数据库(dbo4),对于现在火热的NoSQL,也即Document(-based) DataBase都可以轻易的实现。

    通过阅读http://www.cnblogs.com/tansm/archive/2010/06/10/1755250.html的译文,大家可以自动如何自定义Provider。

    最近几天我就尝试实现了MongoDB的WCF Data Services Provider。

    首先,我尝试了MongoDB的NoRM驱动,介绍见:http://www.cnblogs.com/shanyou/archive/2010/06/04/1751734.html

    但是由于这个驱动,需要你在实体对象中显式添加一个ObjectId _id的属性,才能方便地进行保存,即方便调用Collection.Save方法;如果没有这个属性,就只能通过Update方法来更新数据,但是Update方法在Data Services中会遇到莫名其妙的问题,尝试多种技巧后,最终放弃这个驱动。

    然后转而使用mongodb-csharp driver,这个驱动也有诸多不完美,尤其对LINQ的支持没有NoRM好,不过最终几经周转还是实现了基本功能。要使用这个驱动来实现Provider的最简单方法就是此文说的方法:http://blog.dynamicprogrammer.com/2009/11/10/UsingMongoDBFromC.aspx,在实体类中内含一个Document InternalDocument { get; set; },不过这样对于实体类有很大的入侵性。我没有采用这种方式。我的最终实现方法,可以对实体类影响做到最小。

    比如:

    public class Team
    {
       public int ID { get; set; }//使用代码约定来设置Document Id
       public string Name { get; set; }
       public string Odds { get; set; }
    }
    public class Group
    {
       [MongoId]
       public string Name { get; set; }//使用特性标记来设置Document Id
       public List<Team> Teams { get; set; }
    }

    整个Provider就是一个实现了IUpdatable接口的类,这个类有个静态方法来获得对应的数据库,如:

    public class MongoDBDataContext : IUpdatable
    {
        private static IMongoDatabase _db;
        public IMongoDatabase DB
        {
        get
        {
            if (_db == null)
            {
                Mongo mongo = new Mongo();
                mongo.Connect();
                _db = mongo.GetDatabase(this.DataBaseName);
            }
            return _db;
        }
        }
        
        public virtual string DataBaseName 
        { 
            get { return "temp"; } 
        }
    }

    要实现这个Provider,只需继承这个MongoDBDataContext,并重写DataBaseName属性,以设置自己的数据库名称,然后在安装Data Services的规范添加IQueryable的属性,如:

    public class WorldCupData : MongoDBDataContext
    {
       public override string DataBaseName
       {
           get
           {
               return "WorldCup";
           }
       }
    
       public IQueryable<Team> Teams
       {
           get
           {
               return DB.GetCollection<Team>("Team").Linq();
           }
       }
    
       public IQueryable<Group> Groups
       {
           get
           {
               return DB.GetCollection<Group>("Group").Linq();
           }
       }
    }

    实现过程中,最难的就是如何正确获取到Document,并正确保存。我采取了一种折衷的方案,通过反射来动态对Document和Entity进行转换,并把Entity的ID(或标记为MongoId)的属性的ToString设置为Document的_id,而_id也总是为字符串。核心代码如下:

    private static BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.SetProperty;
    private static Document GetDocumentFromEntity(object entity,Type entityType)
    {
      if (entity == null) return null;
      Document doc = new Document();
      var properties = entityType.GetProperties(flags);
      foreach (var pro in properties)
      {
          object proGetValue = pro.GetValue(entity, null);
          if (pro.Name.ToLower() == "id" || pro.Name.ToLower() == "_id")//使用代码约定来设置Document Id
              doc.Id = proGetValue.ToString();
          else if (Attribute.IsDefined(pro, typeof(MongoIdAttribute)))//使用特性标记来设置Document Id
              doc.Id = proGetValue.ToString();
          doc[pro.Name] = proGetValue;
      }
      return doc;
    }
    
    private static object GetEntityFromDocument(Document doc, string fullTypeName)
    {
      if (doc == null) return null;
      Type t = Type.GetType(fullTypeName, true);
      object entity = Activator.CreateInstance(t);
      var properties = t.GetProperties(flags);
      foreach (var pro in properties)
      {
          if (doc.ContainsKey(pro.Name))
              pro.SetValue(entity, doc[pro.Name], null);
      }
      return entity;
    }
    
    public object GetResource(IQueryable query, string fullTypeName)
    {
      object resource = null;
    
      //query没有返回任何结果,只好用下面的取巧方式,感觉不太保险
      //foreach (var item in query)
      //{
      //    resource = item;
      //}
    
      var exp = query.Expression as MethodCallExpression;
      var arg1 = exp.Arguments[1] as UnaryExpression;
      var expStr = arg1.Operand.ToString();//element => (element.ID == 0)
    
      string[] fullTypeNameSplit = fullTypeName.Split('.');
      var col= DB.GetCollection(fullTypeNameSplit[fullTypeNameSplit.Length - 1]);
      
      //like to translate (element.ID == 0) to { ID : 0 }
      string expStrSplit1 = expStr.Split('>')[1].Trim();//(element.ID == 0)
      var valueStr = expStrSplit1.Split(new string[] { "==" }, StringSplitOptions.RemoveEmptyEntries)[1].Trim();//0)
      valueStr = valueStr.TrimEnd(')');
      
      var selector = new Document { Id = valueStr };
      var doc = col.FindOne(selector);
      
      resource = GetEntityFromDocument(doc, fullTypeName);
      
      
      // fullTypeName can be null for deletes
      if (resource != null && fullTypeName != null && resource.GetType().FullName != fullTypeName)
          throw new Exception("Unexpected type for resource");
      return resource;
    }

    注意在GetResource方法中,去使用了一点不太保险的小技巧。由于这个驱动的LINQ不完善,query不起作用,只好获取到表达式,并解析从条件值。更加我前面的约定,这个条件值就是Document的_id。


    整个Provider的源代码和测试客户端,大家可以到http://code.msdn.microsoft.com/DSPforNoSQL下载。

    分享到: 更多
  • 相关阅读:
    Windows XP下 Android开发环境 搭建
    Android程序的入口点
    在eclipse里 新建android项目时 提示找不到proguard.cfg
    64位WIN7系统 下 搭建Android开发环境
    在eclipse里 新建android项目时 提示找不到proguard.cfg
    This Android SDK requires Android Developer Toolkit version 20.0.0 or above
    This Android SDK requires Android Developer Toolkit version 20.0.0 or above
    Android requires compiler compliance level 5.0 or 6.0. Found '1.4' instead
    Windows XP下 Android开发环境 搭建
    Android程序的入口点
  • 原文地址:https://www.cnblogs.com/redmoon/p/1770152.html
Copyright © 2011-2022 走看看