zoukankan      html  css  js  c++  java
  • NopCommerce仓储模型解析

    概述

    关于数据访问这一块,C#/.Net目前有很多ORM框架可以使用,比如EntityFramework、Dapper、NHibernate等,而NopCommerce采用了微软的EntityFramework。

    阅读本文需要相关的一些知识:IOC、Autofac、EntityFramework、泛型等,并且对NopCommerce有少许了解

    NopCommerce代码地址:https://github.com/nopSolutions/nopCommerce

    EF访问数据,有几个必要条件,我们这里先列举一下:

    1. 实体模型:Nop.Core.Domain目录下
    2. 数据库和实体类Mapping关系,路径:Nop.Data.Mapping文件目录下
    3. Context类,访问数据库的上下文或者叫会话:Nop.Data.NopObjectContext
    4. 数据仓储管理类:Nop.Data.EfRepository

    下面我们就一步步解析Nop的仓储模型

    实体模型

    Nop的实体类都在Nop.Core.Domain目录下面,按业务分为不同的目录,比如分类:Nop.Core.Domain.Catalog.Category,这里为了避免干扰已经删除了一部分代码,具体可以参考项目代码

    public partial class Category : BaseEntity
    {
        public string Name { get; set; }
    
        public string Description { get; set; }
    }
    

    我们发现所有实体类都继承至:Nop.Core.BaseEntity

    public abstract partial class BaseEntity
    {
        public int Id { get; set; }
    }
    

    实体类实现跟数据字段的对应,实现数据从DB到程序的转换,如果我们发现基础类里面有个属性Id,如果自己数据库的主键不是Id或者是其它组合主键,这里可以删除

    映射关系

    Nop的映射关系都在Nop.Data.Mapping目录下面,比如:

    public partial class CategoryMap : NopEntityTypeConfiguration<Category>
    {
        public override void Configure(EntityTypeBuilder<Category> builder)
        {
            builder.ToTable(nameof(Category));
            builder.HasKey(category => category.Id);
    
            builder.Property(category => category.Name).HasMaxLength(400).IsRequired();
            builder.Property(category => category.MetaKeywords).HasMaxLength(400);
            builder.Property(category => category.MetaTitle).HasMaxLength(400);
            builder.Property(category => category.PriceRanges).HasMaxLength(400);
            builder.Property(category => category.PageSizeOptions).HasMaxLength(200);
            
            builder.Ignore(category => category.AppliedDiscounts);
    
            base.Configure(builder);
        }
    }
    

    里面包含表名、主键、长度等一些说明,这里注意下,我们Map类继承至:NopEntityTypeConfiguration,这里具体怎么使用,我们稍后解析

    访问数据库

    Nop采用仓储模型,基于泛型实现,为避免了大量代码,减少错误概率。我们先看下仓储接口,代码位置(Nop.Core.Data.IRepository):

    public partial interface IRepository<TEntity> where TEntity : BaseEntity
    {
        TEntity GetById(object id);
    
        void Insert(TEntity entity);
    
        void Insert(IEnumerable<TEntity> entities);
    
        void Update(TEntity entity);
    
        void Update(IEnumerable<TEntity> entities);
    
        void Delete(TEntity entity);
    
        void Delete(IEnumerable<TEntity> entities);
    
        IQueryable<TEntity> Table { get; }
    
        IQueryable<TEntity> TableNoTracking { get; }
    }
    

    通过上述接口,我们看到,仓储模型提供了基本的CRUD操作,并且提供了数据访问属性Table 和 TableNoTracking,可以方便我们使用Linq查询,TableNoTracking可以理解为NoLock查询,提高查询性能

    我们再看下具体的实现类:Nop.Data.EfRepository

    public partial class EfRepository<TEntity> : IRepository<TEntity> where TEntity : BaseEntity
    {
        #region Fields
    
        private readonly IDbContext _context;
    
        private DbSet<TEntity> _entities;
    
        #endregion
    
        #region Ctor
        
        public EfRepository(IDbContext context)
        {
            this._context = context;
        }
    
        #endregion
    
        #region Utilities
        
        /// <summary>
        /// Rollback of entity changes and return full error message
        /// </summary>
        /// <param name="exception">Exception</param>
        /// <returns>Error message</returns>
        protected string GetFullErrorTextAndRollbackEntityChanges(DbUpdateException exception)
        {
            //rollback entity changes
            if (_context is DbContext dbContext)
            {
                var entries = dbContext.ChangeTracker.Entries()
                    .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified).ToList();
    
                entries.ForEach(entry => entry.State = EntityState.Unchanged);
            }
    
            _context.SaveChanges();
            return exception.ToString();
        }
    
        #endregion
    
        #region Methods
    
        /// <summary>
        /// Get entity by identifier
        /// </summary>
        /// <param name="id">Identifier</param>
        /// <returns>Entity</returns>
        public virtual TEntity GetById(object id)
        {
            return Entities.Find(id);
        }
    
        /// <summary>
        /// Insert entity
        /// </summary>
        /// <param name="entity">Entity</param>
        public virtual void Insert(TEntity entity)
        {
            if (entity == null)
                throw new ArgumentNullException(nameof(entity));
    
            try
            {
                Entities.Add(entity);
                _context.SaveChanges();
            }
            catch (DbUpdateException exception)
            {
                //ensure that the detailed error text is saved in the Log
                throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
            }
        }
    
        /// <summary>
        /// Insert entities
        /// </summary>
        /// <param name="entities">Entities</param>
        public virtual void Insert(IEnumerable<TEntity> entities)
        {
            if (entities == null)
                throw new ArgumentNullException(nameof(entities));
    
            try
            {
                Entities.AddRange(entities);
                _context.SaveChanges();
            }
            catch (DbUpdateException exception)
            {
                //ensure that the detailed error text is saved in the Log
                throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
            }
        }
    
        /// <summary>
        /// Update entity
        /// </summary>
        /// <param name="entity">Entity</param>
        public virtual void Update(TEntity entity)
        {
            if (entity == null)
                throw new ArgumentNullException(nameof(entity));
    
            try
            {
                Entities.Update(entity);
                _context.SaveChanges();
            }
            catch (DbUpdateException exception)
            {
                //ensure that the detailed error text is saved in the Log
                throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
            }
        }
    
        /// <summary>
        /// Update entities
        /// </summary>
        /// <param name="entities">Entities</param>
        public virtual void Update(IEnumerable<TEntity> entities)
        {
            if (entities == null)
                throw new ArgumentNullException(nameof(entities));
    
            try
            {
                Entities.UpdateRange(entities);
                _context.SaveChanges();
            }
            catch (DbUpdateException exception)
            {
                //ensure that the detailed error text is saved in the Log
                throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
            }
        }
    
        /// <summary>
        /// Delete entity
        /// </summary>
        /// <param name="entity">Entity</param>
        public virtual void Delete(TEntity entity)
        {
            if (entity == null)
                throw new ArgumentNullException(nameof(entity));
    
            try
            {
                Entities.Remove(entity);
                _context.SaveChanges();
            }
            catch (DbUpdateException exception)
            {
                //ensure that the detailed error text is saved in the Log
                throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
            }
        }
    
        /// <summary>
        /// Delete entities
        /// </summary>
        /// <param name="entities">Entities</param>
        public virtual void Delete(IEnumerable<TEntity> entities)
        {
            if (entities == null)
                throw new ArgumentNullException(nameof(entities));
    
            try
            {
                Entities.RemoveRange(entities);
                _context.SaveChanges();
            }
            catch (DbUpdateException exception)
            {
                //ensure that the detailed error text is saved in the Log
                throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
            }
        }
    
        #endregion
    
        #region Properties
    
        /// <summary>
        /// Gets a table
        /// </summary>
        public virtual IQueryable<TEntity> Table => Entities;
    
        /// <summary>
        /// Gets a table with "no tracking" enabled (EF feature) Use it only when you load record(s) only for read-only operations
        /// </summary>
        public virtual IQueryable<TEntity> TableNoTracking => Entities.AsNoTracking();
    
        /// <summary>
        /// Gets an entity set
        /// </summary>
        protected virtual DbSet<TEntity> Entities
        {
            get
            {
                if (_entities == null)
                    _entities = _context.Set<TEntity>();
    
                return _entities;
            }
        }
    
        #endregion
    }
    

    这里面有几个设计比较精巧的地方,比如采用泛型实现,不用写很多仓储模型,其次DbSet的Entities,也不用随着模型的增多,写很多访问属性

    仓储模型的实现,基于IDbContext,这里采用了IOC的构造函数注入

    接下来我们看下IDbContext的具体实现NopObjectContext(Nop.Data.NopObjectContext),里面代码比较多,我们就注意几个点

    public partial class NopObjectContext : DbContext, IDbContext
    {
        // 数据库的访问属性
        public NopObjectContext(DbContextOptions<NopObjectContext> options) : base(options)
        {
    
        }
    
        //模型的执行
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            //dynamically load all entity and query type configurations
            var typeConfigurations = Assembly.GetExecutingAssembly().GetTypes().Where(type => 
                (type.BaseType?.IsGenericType ?? false) 
                    && (type.BaseType.GetGenericTypeDefinition() == typeof(NopEntityTypeConfiguration<>) 
                        || type.BaseType.GetGenericTypeDefinition() == typeof(NopQueryTypeConfiguration<>)));
    
            foreach (var typeConfiguration in typeConfigurations)
            {
                var configuration = (IMappingConfiguration)Activator.CreateInstance(typeConfiguration);
                configuration.ApplyConfiguration(modelBuilder);
            }
            
            base.OnModelCreating(modelBuilder);
        }
    }
    

    在构造函数里面,注入了数据库连接等信息,在OnModelCreating方法里面,通过反射类型NopEntityTypeConfiguration,找到上述所有的Map类,进行模型配置注册

    至此,我们已经看到,整个ORM的实现原理,项目启动的时候,启动IOC注册,Nop.Web.Framework.Infrastructure.DependencyRegistrar:

    builder.Register(context => new NopObjectContext(context.Resolve<DbContextOptions<NopObjectContext>>())).As<IDbContext>().InstancePerLifetimeScope();
    

    总结一下:
    Nop的仓储模型设计的很精巧,并且代码量非常少,如果想迁移到自己的项目中,只需要把上述相关的几个文件,拷贝到自己项目中,即可实现整个数据库访问层。

  • 相关阅读:
    linux动态库(.so)和静态库(.a)的区别
    LeetCode刷题笔记和想法(C++)
    tf-idf、朴素贝叶斯的短文本分类简述
    计算机操作系统(第三版)读书笔记
    react hook封装一个排序按钮,有效果图
    react使用fetch封装请求的方法-简单易懂
    react开发企业中后台产品、政务门户网站的一些总结
    git常见命令以及基本使用
    Linux系统下fd分配的方法
    netfilter-IPv4实现框架分析(一)
  • 原文地址:https://www.cnblogs.com/honzhez/p/12705652.html
Copyright © 2011-2022 走看看