WS+MQ+WCF+EF(Code First)

前言


       有段时间没有更新博文了,一直在忙工做不多有时间静下心来继续研究点东西,说来也惭愧,归咎缘由最主要的仍是由于懒惰。空想也是无论用的,有时候不少想法被扼杀到了摇篮里,还没开始作就放弃了,这是多数人会有的恶习,世界上最不缺乏的就是空想家,而是实践者,有句俗话说的好不怕千招会,只怕一招绝,能踏踏实实作好一件事的人才是人生的赢家。另外在平时也有研究过不少有趣的技术,但每每是没有研究到最后,只是研究了如何使用它,而后想要写成文章就是很危险的事情,若是对某项技术研究的并不通透,这时候发表看法的话这样只会害人,不会帮助人,要知道只知其一;不知其二最后害的会是本身。数据库


1、架构浅析


      上文是写给本身的由于发现本身最近有懒惰的恶习,因此作一下小小的反省,好了言归正传。上文讨论了在分布式开发时会用的基本的技术,其中包括微软的WCF、Windows Service、Message Queue等,其中的Message Queue主要是讨论了微软的MQ,由于笔者如今是一名.net的程序猿,因此从最基本的微软MQ开始详细讨论了MQ的使用方法。在有了前几篇的技术基础以后就能够来看这篇文章了。
该篇文章将会使用前几篇文章讨论到的技术来搭一套小的框架,主要是实现Application(电脑或者移动端)和Web Service之间互相的通讯,中间的消息中介服务使用上文讨论到的MQ来实现,具体的架构以下图所示:
 
          关于上图的实现,本例中的Application只是使用了Computer端的WPF来作的一个小的应用,消息队列方是使用微软的Message Queue来开发,Server Service开发的是Windows Service,远程端的Web Service使用WCF来作的开发。具体开发的代码将会在下文中详细讨论。
         Note:这种架构下近端的实体、远程端的实体、WebService的实体以及数据契约的结构必须保持一致,这样在开发时能够避免写不少转换的中间代码。

2、架构代码


  2.1 近端App


        应用程序端作的是简单的WPF应用程序,模拟了近端应用程序在执行完成后发送的消息信息到消息队列中,本例中的消息队列存储的是xml格式的对象信息,因此在发送消息对象时须要首先指定消息队列中信息的存储方式,具体的模拟代码以下:编程

[csharp] view plain copy print ? 在CODE上查看代码片 派生到个人代码片
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Net.Security;  
  5. using System.Text;  
  6. using System.Threading.Tasks;  
  7. using System.Messaging;  
  8. using System.Xml.Serialization;  
  9.   
  10. namespace MQSend  
  11. {  
  12.     public class SendMQ  
  13.     {  
  14.         public void Send()  
  15.         {  
  16.             MessageQueue mq = null;  
  17.             if (!MessageQueue.Exists(".\\private$\\MSMQ"))  
  18.             {  
  19.                 mq = MessageQueue.Create(".\\private$\\MSMQ");  
  20.             }  
  21.             else  
  22.             {  
  23.                 mq = new MessageQueue(".\\private$\\MSMQ");  
  24.             }  
  25.             mq.Formatter = new XmlMessageFormatter(new Type[] { typeof(Student),typeof(Teacher) });  
  26.   
  27.             for (int i = 0; i < 6; i++)  
  28.             {  
  29.                 mq.Send(new Student(){Id =i,Age = "13",Name = "张三"+i.ToString(),  
  30.                                       Teachers = new List<Teacher>()  
  31.                                       {  
  32.                                           new Teacher() { Id = 2,   
  33.                                               Name = "李老师"+i.ToString() }  
  34.                                       }  
  35.                 });  
  36.             }  
  37.             mq.Close();  
  38.         }  
  39.     }  
  40.   
  41.     [Serializable]  
  42.     public class Student  
  43.     {  
  44.         public int Id { getset; }  
  45.         public string Name { getset; }  
  46.         public string Age { getset; }  
  47.         public List<Teacher> Teachers { getset; }   
  48.   
  49.     }  
  50.     [Serializable]  
  51.     public class Teacher  
  52.     {  
  53.         public int Id { getset; }  
  54.         public string Name { getset; }  
  55.         public List<Student> Students { getset; }  
  56.     }  
  57.   
  58. }  

        Note:上面的代码实体对象Student和Teacher存在多对多的关系,这种关系在序列化时每每会出现循环引用的问题,由于序列化其实是一种属性的遍历,会沿着对象一直向下循环序列化,因此在编程的时候必定要注意循环应用的
问题。
        Note:另外在引用属性集合时不要声明接口类型的集合,由于在发送消息队列进行序列化时不容许声明接口类型的集合,不然会发生相似于“XX是接口,所以没法将其序列化。”的问题。
        运行该应用程序后,消息会被发送到本机的消息队列中,具体的消息示意图以下:


   2.2 远程端Server Service


        远程端的Service本例开发的是Windows的服务,由于服务自开机以后能够时刻的在运行,也就是说能够时刻的获取消息队列中的消息,而后调用远程端的Web Service对消息进行处理。因为近端的消息发送的内容是xml序列化后的对象,因此在远程端server在操做消息对象时须要将对象首先反序列化为Service的实体对象(这里的实体对象是指Web Service的实体对象),而后对实体对象作全部的操做。

        另外在开发Service的时候最好中间能够记录Service的运行过程,也就是将Service的运行过程记录到日志文件中,由于Service在运行过程当中很容易出现问题,在出现问题时将问题的内容记录到日志文件中,这样可以较快的找到并修复问题。

        每一个Service都会有不少事件,其中使用最多的就是Service的开启事件OnStart和结束事件OnStop,该例中在这两个事件中分别添加了日志记录的功能,也就是在服务开启和关闭时都会将服务的运行情况写入日志。另外在该服务中添加了一个Timer控件,该控件的Interval时间设置为1000ms,这样能够实时的请求消息,而后对消息作操做,并保存日志信息。
         项目结构以下图所示,项目中添加了系统的WebService,并为该WebService添加了具体的代理类MQServiceClient以及工具类FileOperate。
架构


          Service的具体代码以下所示:

[csharp] view plain copy print ? 在CODE上查看代码片 派生到个人代码片
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.ComponentModel;  
  4. using System.Data;  
  5. using System.Diagnostics;  
  6. using System.IO;  
  7. using System.Linq;  
  8. using System.Messaging;  
  9. using System.ServiceModel;  
  10. using System.ServiceProcess;  
  11. using System.Text;  
  12. using System.Threading;  
  13. using System.Threading.Tasks;  
  14. using DistributeService.MQService;  
  15.   
  16. namespace DistributeService  
  17. {  
  18.     public partial class Service1 : ServiceBase  
  19.     {  
  20.         public Service1()  
  21.         {  
  22.             InitializeComponent();  
  23.         }  
  24.   
  25.         private ServiceHost host;  
  26.         private FileOperate fileOperate  
  27.         {  
  28.             get  
  29.             {  
  30.                 return FileOperate.GetFileOperate();  
  31.             }  
  32.         }  
  33.   
  34.         protected override void OnStart(string[] args)  
  35.         {  
  36.             try  
  37.             {  
  38.                 var str = "▌▌▌▌▌▌▌MQService start at " + DateTime.Now.ToString() + "▌▌▌▌▌▌▌\r\n";  
  39.                 fileOperate.WriteText(str, fileOperate.FileS);  
  40.             }  
  41.             catch (Exception ex)  
  42.             {  
  43.                 fileOperate.WriteText(ex.Message, fileOperate.FileS);  
  44.                 throw;  
  45.             }  
  46.               
  47.         }  
  48.   
  49.         protected override void OnStop()  
  50.         {  
  51.             Thread.Sleep(30000);  
  52.             var str = "▌▌▌▌▌▌▌MQService stop at " + DateTime.Now.ToString() + "▌▌▌▌▌▌▌\r\n"; ;  
  53.             fileOperate.WriteText(str,fileOperate.FileS);  
  54.   
  55.             if (this.host!=null)  
  56.             {  
  57.                 this.host.Close();  
  58.             }  
  59.         }  
  60.   
  61.         private void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e)  
  62.         {  
  63.             MessageQueue mq = null;  
  64.             if (MessageQueue.Exists(".\\private$\\MSMQ"))  
  65.             {  
  66.                 mq = new MessageQueue(".\\private$\\MSMQ");  
  67.                 try  
  68.                 {  
  69.                     mq.Formatter = new XmlMessageFormatter(new Type[] {typeof (Student)});  
  70.                     var me = mq.Receive();  
  71.                     var stu = me.Body;  
  72.   
  73.                     fileOperate.WriteText(stu.ToString() + "\r\n", fileOperate.FileM);  
  74.   
  75.                     var client = new MQHandlerClient();//.GetMqHandlerService();  
  76.                     client.Add((Student) stu);  
  77.                     client.Close();  
  78.                 }  
  79.                 catch (Exception ex)  
  80.                 {  
  81.                     fileOperate.WriteText(ex.ToString() + "\r\n", fileOperate.FileM);  
  82.                     throw;  
  83.                 }  
  84.                 finally  
  85.                 {  
  86.                     mq.Close();  
  87.                 }  
  88.   
  89.             }  
  90.               
  91.         }  
  92.     }  
  93. }  

      其中的 WebSerivceClient 的代码以下所示:

[csharp] view plain copy print ? 在CODE上查看代码片 派生到个人代码片
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Management.Instrumentation;  
  5. using System.Text;  
  6. using System.Threading.Tasks;  
  7. using DistributeService.MQService;  
  8.   
  9. namespace DistributeService  
  10. {  
  11.     public class MQServiceClient  
  12.     {  
  13.         private static MQHandlerClient instance;  
  14.         private static object _object = new object();  
  15.   
  16.         private MQServiceClient()  
  17.         {  
  18.         }  
  19.   
  20.         public static MQHandlerClient GetMqHandlerService()  
  21.         {  
  22.             if (instance == null)  
  23.             {  
  24.                 lock (_object)  
  25.                 {  
  26.                     if (instance==null)  
  27.                     {  
  28.                         instance=new MQHandlerClient();  
  29.                     }  
  30.                 }  
  31.             }  
  32.   
  33.             return instance;  
  34.         }  
  35.   
  36.         ~MQServiceClient()  
  37.         {  
  38.             if (instance!=null)  
  39.             {  
  40.                 instance.Close();  
  41.                 instance.Abort();  
  42.             }  
  43.         }  
  44.     }  
  45. }  

         Note:这里的代码主要是作的消息处理,可是每每会出现错误,因此要记录运行日志,推荐使用开源的log4net或者Nlog,可是本例中是本身将堆栈信息写入到文本文件当中,能够方便查看。app


   2.3 远程端Web Service

         远程端Web Service公开了对消息的处理接口,主要是作的DB的增删改查的操做。该例的Web Service使用的是WCF来开发的,后台使用了EF 的Code First做为系统的ORM框架,并使用FluentAPI来搭建了系统的映射部分,系统对外公布了数据库的增删改查接口,具体的结构图以下所示:
框架

         整个远程端的Web Service为其它端提供了数据的操做,其中的WCF在部署到host后对外公开对数据库的CRUD操做接口,向下的DAL层封装了数据库的上下文(DbContext)以及数据库的表实体,DAL层实体对象到数据库的映射规则被封装到了Mapping层中,该层的映射使用的是FluentAPI来实现的,最终对数据库的操做是EF框架作的操做。接下来将会展现下几个主要层的代码。

    2.3.1  WCF层增删改查

        下面的代码是使用WCF来开发的Service,该Service在建立客户端对象时会同时建立数据库的一个上下文对象,最后将数据同步到Context中,由EF管理并同步到DB中。

[csharp] view plain copy print ? 在CODE上查看代码片 派生到个人代码片
  1. using System;  
  2. using System.Collections.Generic;  
  3. using DAL;  
  4. using Entitys;  
  5.   
  6. namespace DistributeWCF  
  7. {  
  8.     // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "MQHandler" in code, svc and config file together.  
  9.     // NOTE: In order to launch WCF Test Client for testing this service, please select MQHandler.svc or MQHandler.svc.cs at the Solution Explorer and start debugging.  
  10.     public class MQHandler: IMQHandler  
  11.     {  
  12.         private TestContext context;  
  13.         public MQHandler()  
  14.         {  
  15.             this.context=new TestContext();  
  16.         }  
  17.   
  18.         public bool Add(Student stu)  
  19.         {  
  20.             this.context.Students.Add(new Entitys.Student(){Name = stu.Name,Teachers = new List<Entitys.Teacher>()});  
  21.             this.context.SaveChanges();  
  22.             return true;  
  23.         }  
  24.   
  25.         public bool Delete(int stuId)  
  26.         {  
  27.             var stu = this.context.Students.Find(new {stuId});  
  28.             this.context.Students.Remove(stu);  
  29.             this.context.SaveChanges();  
  30.             return true;  
  31.         }  
  32.   
  33.         public bool Update(Student stu) { return true; }  
  34.   
  35.         public List<Student> FindAll()  
  36.         {  
  37.             var lists = this.context.Students.SqlQuery("select * from student");  
  38.             var liststu = new List<Student>();  
  39.             foreach (var student in lists)  
  40.             {  
  41.                 var stu = new Student();  
  42.                 stu.StudentId = student.Id;  
  43.                 stu.Name = student.Name;  
  44.                 stu.Sex = student.Age;  
  45.                 stu.Teachers = new List<Teacher>();  
  46.                 foreach (var teacher in student.Teachers)  
  47.                 {  
  48.                     stu.Teachers.Add(new Teacher()  
  49.                     {  
  50.                         Id = teacher.Id,  
  51.                         Name = teacher.Name,  
  52.                     });  
  53.                 }  
  54.             }  
  55.             return liststu;  
  56.         }  
  57.     }  
  58.   
  59.       
  60. }  

    2.3.2 Fluent API的Mapping映射代码

        该例使用的是Fluent API来作的映射,由于这种映射在修改时会很是的方便,而且在开发时控制对象关系编译的过程,因此使用此种方法映射功能,该种映射很是的简单,只要熟悉映射的规则就能够很容易操做。以下代码演示了该例中学生和老师的多对多关系之间的映射。
        对应的Teacher的Entity以及Mapping代码以下:

[csharp] view plain copy print ? 在CODE上查看代码片 派生到个人代码片
  1. using System.Data.Entity.ModelConfiguration;  
  2. using Entitys;  
  3. namespace Mapping  
  4. {  
  5.     public class TeacherMapping:EntityTypeConfiguration<Teacher>  
  6.     {  
  7.         public TeacherMapping()  
  8.         {  
  9.             this.ToTable("Teacher");  
  10.             this.HasKey(x => x.Id);  
  11.             this.Property(x => x.Id).HasColumnName("Id");  
  12.             this.Property(x => x.Name);  
  13.             this.HasMany(x=>x.Students)  
  14.                 .WithMany(x=>x.Teachers)  
  15.                 .Map(x => x.ToTable("StuTeahcer").MapLeftKey("TeacherId")  
  16.                     .MapRightKey("StudentId")  
  17.                     );  
  18.         }  
  19.     }  
  20.   
  21.     public class Teacher  
  22.     {  
  23.         public int Id { getset; }  
  24.         public string Name { getset; }  
  25.         public IList<Student> Students { getset; }   
  26.     }  
  27.       
  28. }  

    对应的StudentEntity以及Mapping代码以下:分布式

[csharp] view plain copy print ? 在CODE上查看代码片 派生到个人代码片
  1. using System.ComponentModel.DataAnnotations.Schema;  
  2. using System.Data.Entity.ModelConfiguration;  
  3. using Entitys;  
  4. namespace Mapping  
  5. {  
  6.     public class StudentMapping:EntityTypeConfiguration<Student>  
  7.     {  
  8.         public StudentMapping()  
  9.         {  
  10.             this.ToTable("Student");  
  11.             this.HasKey(x => x.Id);  
  12.             this.Property(x => x.Id)  
  13.                 .HasColumnName("Id")  
  14.                 .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)  
  15.                 .HasColumnType("int");  
  16.             this.Property(x => x.Name);  
  17.             this.Property(x => x.Age);  
  18.         }  
  19.     }  
  20.   
  21.     public class Student  
  22.     {  
  23.         public int Id { getset; }  
  24.         public string Name { getset; }  
  25.         public string Age { getset; }  
  26.         public IList<Teacher> Teachers { getset; }   
  27.     }  
  28.       
  29. }  

        Note:EF的实体到对象的映射有多种方式,最经常使用的是DataAnnotations数据注解以及Fluent API两种方式来编写映射。该例中的学生和老师属于多对多的关系,在使用Fluent API添加映射的时候只须要在对象的一端添加相互之间的多对多关系,由于此种关系要生成中间表因此要使用ToTable来指定中间表的代表,以及使用MapLeftKey和MapRightKey指定表的主键。
        上面的代码就是远程端的主要代码,在运行代码后会在数据库中添加对应的表结构,开发时很简单,生成的数据库的表结构以下图所示:

结语


        由于本文只是提出了一种分布式开发的方法,这种方法须要在项目中接受考验才能评价架构的好坏,另外在开发时还须要作严格的管理,并统一编码规范和验收标准,还有不少须要注意的地方,本文提出的内容不必定所有正确,可是通过了笔者的测试,有什么问题还请留言一块探讨。

版权声明:本文为博主原创文章,未经博主容许不得转载。ide

相关文章
相关标签/搜索