CRUD全栈式编程架构之数据层的设计
CodeFirst
一直以来我们写应用的时候首先都是创建数据库
终于在orm支持codefirst之后,我们可以先建模。
通过模型去创建数据库,并且基于codefirst可以实现方便的
实现数据库迁移的工作.使用codefirst有以下几个技巧,
以EntityFramework为例,结合我这个设计做了以下改进
1.模型的识别
建立一个基类命名Entity,里面只有一个long类型的id字段。
所有需要映射到数据库的模型都继承自Entity,
1 2 3 4 | public class Entity { public long Id { get ; set ; } } |
2.模型的映射
选用fluntapi作为配置(可以保持模型类的整洁,并且和具体orm无关)
新建一个配置基类继承自EntityTypeConfiguration,并且添加泛型约束,
在构造函数中配置表名(和类名一致),和id作为主键,并且设置成由程序生成。
如果是一般单表的话,配合System.ComponentModel.DataAnnotations下的特性
即可完成数据库字段长度等等限制
1 2 3 4 5 6 7 8 9 10 | public class BaseEntityTypeConfig<TEntity> : EntityTypeConfiguration<TEntity>, IEntityConfiguration where TEntity : class , Entity { public BaseEntityTypeConfig() { HasKey(item => item.Id); Property(item => item.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); ToTable( typeof (TEntity).Name); } } |
3.模型的生成
重写DbContext的OnModelCreating方法,反射程序集所有需要映射的类型,
然后,查找对应配置,如果没有则构造出配置基类,即可完成模型的创建,
ef默认会在第一次访问的时候去创建或者校验模型,
模型的配置缓存在全局静态数据中。如果对应模型类有
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | protected override void OnModelCreating(DbModelBuilder modelBuilder) { //这里 获取所有已经存在的配置 var configs = MetaDataManager.Type.Find(item => { if (item.BaseType == null || !item.BaseType.IsGenericType) return false ; if (item.BaseType.GetGenericTypeDefinition() != typeof (BaseEntityTypeConfig<>)) return false ; var genericType = item.BaseType.GetGenericArguments() .FirstOrDefault(data => data.IsSubclassOf( typeof (Entity))); if (genericType == null ) return false ; return _context.Types.Contains(genericType); }).ToDictionary(item => item.BaseType.GetGenericArguments().FirstOrDefault(data => data.IsSubclassOf( typeof (Entity))), item => item); //如果对应的实体有配置则从配置生成,如果没有配置,那么默认给出配置 _context.Types.ForEach(item => { if (IgnoreAttribute.IsDefined(item)) return ; Type type; if (!configs.TryGetValue(item, out type)) type = typeof (BaseEntityTypeConfig<>).MakeGenericType(item); dynamic config = Activator.CreateInstance(type); modelBuilder.Configurations.Add(config); }); } |
Repository
关于Repository的文章很多,这里就不重复描述了。
我这里都是采用的接口编程,全部是采用构造函数来注入。
我这里需要为每个类型的CurdService注入一个默认Repository实现。
1 2 3 4 5 6 7 8 | Assembly.GetExecutingAssembly().GetTypes().Where(item => item.IsSubclassOf( typeof (Entity))) .ForEach(item => { var interfaceType = typeof (IRepository<>).MakeGenericType(item); var classType = typeof (Repository<>).MakeGenericType(item); UnityService.RegisterType(interfaceType, classType); }); |
其他一些技术
UnitOfWork(工作单元)
网上文章也很多,简单来说,把若干个数据库操作放在一起作为事务提交
得益于ef的设计,ef使用dbcontext.savechanges()方法等价于unitofwork.commit()方法
这部分的设计主要借鉴NLayerApp.
如果没有ef我建议是把每一个增删改类型的sql命令做成委托,然后左后commit的时候
使用事务提交。代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | List<Action<DbConnection>> works = new List<Action<DbConnection>>(); public void Excute(Action<DbConnection> work) { works.Add(work); } public void Commint() { using (TransactionScope ts = new TransactionScope()) { using ( var conn = new SqlConnection( "{链接字符串}" )) { works.ForEach(work => work(conn)); } ts.Complete(); } } |
Specification(规约)
同样网上的文章也很多,我这里只是把他作为查询实体来使用.
如果使用传统ado.net的方式,直接传表达式到Repository中的话
将导致解析表达式特别复杂,而用Specification的话,相当于查询
语句中的where部分由它来接管,这样解耦和Repository和具体orm的依赖
这部分的设计主要借鉴NLayerApp
多排序
在分页中我们经常遇到多查询的情况,核心就是构造表达式,等同于构造
sql语句中orderby的部分,同时它也支持内存中的多排序
这部分设计主要借鉴Apworks中多排序的设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using Coralcode.Framework.Models; namespace Coralcode.Framework.Domains { public class SortExpression<TEntity> where TEntity : class { /// <summary> /// key:属性名,value,true为升序,false为降序 /// </summary> private readonly List<EditableKeyValuePair<Expression<Func<TEntity, dynamic>>, bool >> _sortList; public SortExpression(List<EditableKeyValuePair<Expression<Func<TEntity, dynamic>>, bool >> sortList) { _sortList = sortList; } /// <summary> /// 如果为空则不需要排序 /// </summary> /// <returns></returns> public bool IsNeedSort() { return _sortList.Count != 0; } public IQueryable<TEntity> BuildSort(IQueryable<TEntity> query) { if (_sortList == null || _sortList.Count == 0) return query; _sortList.ForEach(item => { //获取表达式变量参数 item var parameter = item.Key.Parameters[0]; //解析属性名 Expression bodyExpression = null ; if (item.Key.Body is UnaryExpression) { UnaryExpression unaryExpression = item.Key.Body as UnaryExpression; bodyExpression = unaryExpression.Operand; } else if (item.Key.Body is MemberExpression) { bodyExpression = item.Key.Body; } else throw new ArgumentException( @"The body of the sort predicate expression should be either UnaryExpression or MemberExpression." , "sortPredicate" ); MemberExpression memberExpression = (MemberExpression)bodyExpression; string propertyName = memberExpression.Member.Name; //根据属性名获取属性 var property = typeof (TEntity).GetProperty(propertyName); //创建一个访问属性的表达式 item.property var propertyAccess = Expression.MakeMemberAccess(parameter, property); //创建表达式 item=>item.property var orderByExp = Expression.Lambda(propertyAccess, parameter); var resultExp = Expression.Call( typeof (Queryable), item.Value ? "OrderBy" : "OrderByDescending" , new [] { typeof (TEntity), property.PropertyType }, query.Expression, Expression.Quote(orderByExp)); query = query.Provider.CreateQuery<TEntity>(resultExp); }); return query; } public IEnumerable<TEntity> BuildSort(IEnumerable<TEntity> query) { if (_sortList == null || _sortList.Count == 0) return query; _sortList.ForEach(item => { //获取表达式变量参数 item var parameter = item.Key.Parameters[0]; //解析属性名 Expression bodyExpression = null ; if (item.Key.Body is UnaryExpression) { UnaryExpression unaryExpression = item.Key.Body as UnaryExpression; bodyExpression = unaryExpression.Operand; } else if (item.Key.Body is MemberExpression) { bodyExpression = item.Key.Body; } else throw new ArgumentException( @"The body of the sort predicate expression should be either UnaryExpression or MemberExpression." , "sortPredicate" ); MemberExpression memberExpression = (MemberExpression)bodyExpression; string propertyName = memberExpression.Member.Name; //根据属性名获取属性 var property = typeof (TEntity).GetProperty(propertyName); //创建一个访问属性的表达式 item.property var propertyAccess = Expression.MakeMemberAccess(parameter, property); //创建表达式 item=>item.property var orderByExp = Expression.Lambda(propertyAccess, parameter); //var resultExp = Expression.Call(typeof(IEnumerable), // item.Value ? "OrderBy" : "OrderByDescending", // new[] { typeof(TEntity), property.PropertyType }, query.Expression, Expression.Quote(orderByExp)); // query =resultExp.Method.Invoke(query,resultExp.Arguments.ToArray()) query.Provider.CreateQuery<TEntity>(resultExp); }); return query; } } } |
Ps:
比较零碎,如果有什么问题可以在下面给我留言。
其中模型创建代码中有一个_context.这个属于下个系列内容。
主要用来搭配模块化做业务垂直分库用.
重点是看设计思路,代码只是给一个演示,一般照搬是编译不过的.
文章系列的结尾会放出一个完整的设计代码和一个简单的示例.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步