zoukankan      html  css  js  c++  java
  • 深度剖析Byteart Retail案例:基于MongoDB的仓储实现

    今天花了半天时间,向Byteart Retail案例加入了基于MongoDB的仓储实现,读者朋友可以直接从Byteart Retail的代码库克隆最新代码来使用基于MongoDB的仓储实现。

    实现步骤

    1、重构ByteartRetail.Domain.Repositories目录结构

    本来这一步是不需要做的,但是因为之前没有把结构规划好,所以所有基于Entity Framework的仓储实现都放在了根目录下。现在把这些仓储的实现都移到了EntityFramework目录中,同时修改了命名空间和ByteartRetail.Services项目的web.config文件。改完后结构如下:

    image

    2、实现基于MongoDB的仓储和上下文

    在《深度剖析Byteart Retail案例:仓储(Repository)及其上下文(Repository Context)》一文中,我已经详细介绍了Byteart Retail案例中仓储及其上下文的设计和实现,因此,在已有的框架上再实现一个MongoDB的仓储是非常容易的事情,具体实现方式在此也不多做说明了,可以结合这篇文章并参考源代码,整个过程只花了我不到一个小时的时间。实现后的目录结构如下:

    image

    3、将SQL LocalDB中的数据迁移到MongoDB中

    这部分花了我一些时间,为了简单起见,我还是自己写了一些控制台代码,基本思路是:先用EntityFrameworkRepository将对象读入,然后以聚合根为单位,使用MongoDBRepository依次写入MongoDB。写入的时候遇到了一些小问题,其中最需要注意的就是,在我们的SalesLine对象中聚合了SalesOrder,而SalesOrder本身又聚合了SalesLine,这就造成了循环引用,因此MongoDB会报错的(EF不会报错,因为EF采用了延迟加载功能来获取SalesLine信息),为了解决这个问题,需要配置MongoDB的Class Map,如下:

    BsonClassMap.RegisterClassMap<SalesLine>(s =>
    {
        s.AutoMap();
        s.SetIgnoreExtraElements(true);
        s.UnmapProperty<SalesOrder>(p => p.SalesOrder); // bypass circular reference.
    });

    此外,我们需要用聚合根的ID作为MongoDB的objectID,并希望日期时间以本地时间格式存储,因此,我另外开发了两个Convention Profile,并在MongoDBRepositoryContext中增加了两个静态方法,以便应用程序在启动的时候能够调用这个静态方法完成相关设置:

    public class UseLocalDateTimeConvention : IMemberMapConvention
    {
        public void Apply(BsonMemberMap memberMap)
        {
            IBsonSerializationOptions options = null;
            switch (memberMap.MemberInfo.MemberType)
            {
                case MemberTypes.Property:
                    PropertyInfo propertyInfo = (PropertyInfo)memberMap.MemberInfo;
                    if (propertyInfo.PropertyType == typeof(DateTime) ||
                        propertyInfo.PropertyType == typeof(DateTime?))
                        options = new DateTimeSerializationOptions(DateTimeKind.Local);
                    break;
                case MemberTypes.Field:
                    FieldInfo fieldInfo = (FieldInfo)memberMap.MemberInfo;
                    if (fieldInfo.FieldType == typeof(DateTime) ||
                        fieldInfo.FieldType == typeof(DateTime?))
                        options = new DateTimeSerializationOptions(DateTimeKind.Local);
                    break;
                default:
                    break;
            }
            memberMap.SetSerializationOptions(options);
        }
        public string Name
        {
            get { return this.GetType().Name; }
        }
    }
    
    public class GuidIDGeneratorConvention : IPostProcessingConvention
    {
        public void PostProcess(BsonClassMap classMap)
        {
            if (typeof(IEntity).IsAssignableFrom(classMap.ClassType) && 
                classMap.IdMemberMap != null)
            {
                classMap.IdMemberMap.SetIdGenerator(new GuidGenerator());
            }
        }
        public string Name
        {
            get { return this.GetType().Name; }
        }
    }
    
    public static void RegisterConventions(bool autoGenerateID = true, bool localDateTime = true)
    {
        RegisterConventions(autoGenerateID, localDateTime, null);
    }
    
    public static void RegisterConventions(bool autoGenerateID, bool localDateTime, 
        IEnumerable<IConvention> additionConventions)
    {
        var conventionPack = new ConventionPack();
        conventionPack.Add(new NamedIdMemberConvention("id", "Id", "ID", "iD"));
        if (autoGenerateID)
            conventionPack.Add(new GuidIDGeneratorConvention());
        if (localDateTime)
            conventionPack.Add(new UseLocalDateTimeConvention());
        if (additionConventions != null)
            conventionPack.AddRange(additionConventions);
    
        ConventionRegistry.Register("DefaultConvention", conventionPack, t => true);
    }

    4、修改Global.asax.cs文件

    在Global.asax.cs文件中,找到Application_Start方法,在这个方法中加入bootstrapper的调用:MongoDBBootstrapper.Bootstrap();。

    当然,为了简单起见,我还引入了一个MongoDBBootstrapper类,用来调用上面的RegisterConventions方法对Convention Profile进行注册,同时还包含了对Class Map的注册,在此就不贴代码了。完成以上四步以后,准备工作就做好了。

    启用MongoDB仓储及其上下文

    准备工作做好以后,就让我们开始启用MongoDB仓储吧。由于我上面已经完成了数据迁移,因此,在我的MongoDB数据库中已经有了ByteartRetail数据库。读者朋友在完成最新版本代码克隆之后,请先自行安装MongoDB服务,然后,在Byteart Retail源代码目录的demo_data目录下,有个ByteartRetail.zip的压缩包,将其解压到你的本地磁盘,然后,进入MongoDB的bin目录,使用mongorestore.exe程序恢复ByteartRetail数据库。比如:

    mongorestore -d ByteartRetail c:\ByteartRetail

    完成数据库恢复之后,你将可以在mongoDB的提示符下列出ByteartRetail数据库:

    image

    下一步,修改ByteartRetail.Services下的web.config文件,将unity配置部分中的Entity Framework仓储及其上下文的配置部分注释掉,然后启用MongoDB仓储及其上下文的配置,如下:

    image

    OK,现在启动站点,从前台的角度我们基本上看不到任何变化,但此时Byteart Retail已经在使用MongoDB作为数据持久化机制了。

    image

    最后说明一下:如果你安装MongoDB的时候不是使用的默认配置(比如你改过MongoDB的端口等设置),那么,你可以修改ByteartRetail.Domain.Repositories.MongoDB.MongoDBRepositoryContextSettings 类,在这个类中根据你的具体情况对MongoDB进行配置。配置的详细说明请参考该类中的注释。

    总结

    本文也算是《深度剖析Byteart Retail案例》系列文章的一个题外篇,主要目的就是为了验证在Byteart Retail案例中,仓储及其上下文的可扩展性。实验证明,目前的框架设计能够在不改动任何已有组件的基础上,直接新增对其它数据持久化方案的支持,我们所要做的仅仅就是继承几个类、实现几个接口,然后修改一下配置信息。整个过程花了我一个人不到半天的时间,当然,Byteart Retail项目本身规模也不大,但仍然给了我们很好的启示:良好的架构设计能够大幅度降低团队资源的浪费,并且减小出错的几率,同时还对系统运营提供了一定的支持:我们可以让某个团队单独地开发一个组件,在完成开发和测试之后,可以在不大范围影响系统运行的基础上将新的实现替换进去。因此,良好的架构设计将会对项目的管理带来重大影响。

  • 相关阅读:
    Codeforces Gym 100571A A. Cursed Query 离线
    codeforces Gym 100500 J. Bye Bye Russia
    codeforces Gym 100500H H. ICPC Quest 水题
    codeforces Gym 100500H A. Potion of Immortality 简单DP
    Codeforces Gym 100500F Problem F. Door Lock 二分
    codeforces Gym 100500C D.Hall of Fame 排序
    spring data jpa 创建方法名进行简单查询
    Spring集成JPA提示Not an managed type
    hibernate配置文件中的catalog属性
    SonarLint插件的安装与使用
  • 原文地址:https://www.cnblogs.com/daxnet/p/3053256.html
Copyright © 2011-2022 走看看