.Net泛型+反射(超实用的文章)包含:泛型缓存,特性,简单工厂
一.引言
仅供学习参考使用
要求:
1 建立一个数据库,然后执行下面的数据库脚本,会增加两张表 User Company,大家可以去表里面自己多加入一些数据 2 建立数据库表映射的基类BaseModel,包括 Id属性 建立两个子类Model:公司和用户,按照表结构来 3 提供两个泛型的数据库访问方法,用 BaseModel约束 一个是用id去查询单个实体,(只有这一个参数) 一个是查询出数据表的全部数据列表查询(没有参数) 提示:用DataReader去访问数据库,将得到的结果通过反射生成实体对象/集合返回; 4 封装一个方法,能控制台输出任意实体的全部属性和属性值; 5 进阶需求:提供泛型的数据库实体插入、实体更新、ID删除数据的数据库访问方法; 6 进阶需求(可选):欢迎小伙伴儿写个实体自动生成器; 7 进阶需求(可选):将数据访问层抽象,使用简单工厂+配置文件+反射的方式,来提供对数据访问层的使用 8 进阶需求(可选):每个实体类的基础增删改查SQL语句是不变的,用泛型缓存试试! 1 封装一个方法,能控制台输出任意实体的全部属性和属性值 Name:Eleven; 升级一下,属性名称希望用具体中文描述,而不是实体的属性名,也就是 名称:Eleven; 2 如果数据库的表/字段名称和程序中实体不一致,尝试用特性提供,解决增删改查; (数据库是user 程序得是UserModel) (数据库是state 程序得是status) 3 通用数据验证,Required(非空) Mobile(手机号格式) Email(格式) 字符串长度(最大最小)等常见规则; 支持一个属性多重验证; 支持返回错误信息; 支持返回全部错误(一个对象多个属性,可能多个条件都不满足); 4 委托优化,项目中的各种重复代码 5 进阶需求(可选):反射特性的很多东西缓存一下,字典缓存OR泛型缓存
二.代码部分
1.项目框架如下:
.Net 5.0控制台项目
刚开始可能代码断断续续,后期全部完成后会整理
CommonTool下的类BaseModel
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Training.CommonTool { public class BaseModel { public int Id { get; set; } } }
CommonTool下的文件夹AttributeExtend下的类
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Training.CommonTool.AttributeExtend { public class MattAbstractAttribute:Attribute { protected string Name; public MattAbstractAttribute(string name) { this.Name = name; } public string GetName() { return this.Name; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Training.CommonTool.AttributeExtend { [AttributeUsage(AttributeTargets.Class)] public class TableAttribute: MattAbstractAttribute { public TableAttribute(string tableName):base(tableName) { } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Training.CommonTool.AttributeExtend { [AttributeUsage(AttributeTargets.Property)] public class ColumnAttribute: MattAbstractAttribute { public ColumnAttribute(string columnName):base(columnName) { } } }
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace Training.CommonTool.AttributeExtend { /// <summary> /// 特性帮助类 /// </summary> public static class AttributeHelper { /// <summary> /// 获取特性名称(实体name以及字段name) /// </summary> /// <param name="memberInfo"></param> /// <returns></returns> //this代表扩展方法 public static string GetAttributeName(this MemberInfo memberInfo) { //判断是否存在 if (memberInfo.IsDefined(typeof(MattAbstractAttribute),true)) { //GetCustomAttribute获取自定义特性 MattAbstractAttribute mattAbstract =memberInfo.GetCustomAttribute<MattAbstractAttribute>(); return mattAbstract.GetName(); } //如果未标记特性,就返回原有名称(tablename/Property) return memberInfo.Name; } } }
IService下的IBaseService接口
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Training.CommonTool; namespace Training.IService { /// <summary> /// 服务层接口 /// </summary> public interface IBaseService { /// <summary> /// 根据id找到某个实体的某一行的数据 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="id"></param> /// <returns></returns> T Find<T>(int id) where T : BaseModel; /// <summary> /// 查找到对应实体的全部数据 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> List<T> FindAll<T>() where T : BaseModel; /// <summary> /// 新增接口 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> /// <returns></returns> bool Add<T>(T t) where T : BaseModel; /// <summary> /// 修改接口 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> /// <returns></returns> bool Update<T>(T t) where T : BaseModel; /// <summary> /// 删除接口 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> /// <returns></returns> bool Delete<T>(T t) where T : BaseModel; } }
Model层下的类
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Training.Model { public class Company { public string Name { get; set; } public DateTime CreateTime { get; set; } public int CreatorId { get; set; } public int? LastModifierId { get; set; } public DateTime? LastModifyTime { get; set; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Training.CommonTool; using Training.CommonTool.AttributeExtend; namespace Training.Model { [Table("User")] public class UserModel : BaseModel { public string Name { get; set; } [Column("Account")] public string AccountName { get; set; } public string Password { get; set; } public string Email { get; set; } public string Mobile { get; set; } public int? CompanyId { get; set; } public string CompanyName { get; set; } public int State { get; set; } public int UserType { get; set; } public DateTime? LastLoginTime { get; set; } public DateTime CreateTime { get; set; } public int CreatorId { get; set; } public int? LastModifierId { get; set; } public DateTime? LastModifyTime { get; set; } } }
Service下的BaseService类
using System; using System.Collections.Generic; using System.Data.SqlClient; using System.Linq; using System.Text; using System.Threading.Tasks; using Training.CommonTool; using Training.CommonTool.AttributeExtend; using Training.IService; namespace Training.Service { public class BaseService : IBaseService { /// <summary> /// 通用的动作:创建、打开、释放连接 sqlcommand创建 /// </summary> /// <param name="sql"></param> /// <param name="func"></param> /// <returns></returns> private T Command<T>(string sql, Func<SqlCommand, T> func) { using SqlConnection conn = new(ConfigrationManager.SqlConnectionString); SqlCommand sqlCommand = new(sql, conn); conn.Open(); //Invoke是调用一个委托,包含一个入参及出参(两个类型参数不同), //我们这里调用的是func委托Func<SqlCommand, T>,入参是SqlCommand,出参是T return func.Invoke(sqlCommand); } public bool Add<T>(T t) where T : BaseModel { Type type = typeof(T); //1.首先获取实体的所有字段 //2.将这些字段拼接成sqlparameter需要的参数 //3.存在部分字段是空值,所以需要用??判断,如果是空值就返回DBnull.Value,不是空值就返回原本的值 //4.将添加语句从TSqlHelper类中读取过来 //5.调用封装好的command方法,将参数及值添加进去 //6.判断最终的结果成功与否 var prop = type.GetProperties().Where(s => !s.Name.Equals("Id")); var param = prop.Select(s => new SqlParameter($@"{s.GetAttributeName()}", s.GetValue(t) ?? DBNull.Value)).ToArray(); string sql = $"{TSqlHelper<T>.Addsql}"; //因为需要写不止一句代码,所以得带方法体{} //??表示为空就是DBNull.Value,不为空就返回原来的值 //因为AddRange方法需要参数SqlParameter[]数组类型的,所以需要调用.ToArray()将返回值转为数组 //SqlParameter需要传入两个参数,一个是参数名称,另一个是参数值 //因为我们最终返回true,所以编译器推测返回值是bool,所以就不需要写command返回值类型 return Command(sql, sqlcommand => { sqlcommand.Parameters.AddRange(param); int result = sqlcommand.ExecuteNonQuery(); if (result == 0) throw new Exception("新增异常"); return true; }); } public T Find<T>(int id) where T : BaseModel { throw new NotImplementedException(); } public List<T> FindAll<T>() where T : BaseModel { throw new NotImplementedException(); } public bool Update<T>(T t) where T : BaseModel { throw new NotImplementedException(); } public bool Delete<T>(T t) where T : BaseModel { throw new NotImplementedException(); } } }
Training下的ConfigrationManager类
using Microsoft.Extensions.Configuration; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Training { /// <summary> /// 获取配置文件帮助类 /// </summary> public static class ConfigrationManager { //有了IOC再去注入--容器单例 static ConfigrationManager() { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsetting.json"); IConfigurationRoot configuration = builder.Build(); _SqlConnectionString = configuration["ConnectionString"]; } private static string _SqlConnectionString = null; public static string SqlConnectionString { get { return _SqlConnectionString; } } } }
Training下的appsetting.json文件
{ "ConnectionString": "server=.;uid=sa;pwd=010806wpz.;database=Work;MultipleActiveResultSets=True" }
泛型缓存
Service下的类TSqlHelper(泛型类)
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using Training.CommonTool; using Training.CommonTool.AttributeExtend; namespace Training.Service { /// <summary> /// crud语句泛型类 /// </summary> /// <typeparam name="T"></typeparam> public static class TSqlHelper<T> where T:BaseModel { //泛型约束是为了保证传进来的类型是我们需要的,以免报错类型错误的bug //当类与构造函数都为静态时,在调用这个静态类时,会先执行静态构造函数,保存到内存中,在下次调用时直接取,不需要执行,这样就加快了程序的执行效率,提升性能, //这个类是泛型静态类时,传入什么类型就会保存什么类型,下次调用其他类型时,会继续执行静态构造函数,无影响 //因为crud语句需要从这个类里面取,所以需要将他们访问限制设置成公有,方便其他类调用(public) public static string Addsql = null; public static string Selectsql = null; public static string Updatesql = null; public static string Deletesql = null; //因为tablename以及类型只在内部类中使用,所以不需要公开,访问限制就写为private private static Type type = typeof(T); //根据泛型传进来的类获取表名 private static string TableName = type.GetAttributeName(); //泛型缓存 static TSqlHelper() { //string.Join是用来分隔的,第一个以及最后一个字段不会加',',其他都会加',' //用[]是为了避免表名及字段是关键字 //一个实体的所有字段 string columnnName = string.Join(",", type.GetProperties().Where(s=>!s.Name.Equals("Id")).Select(s=>$"[{s.GetAttributeName()}]")); //columnValue用@是为了防止sql注入 //where作用是为了排除id字段,因为id字段数据库设为自增,所以参数和值都将id字段去掉 //字段值(真正的值会在调用时传入) string columnValue = string.Join(",", type.GetProperties().Where(s => !s.Name.Equals("Id")).Select(s=>$"@{s.GetAttributeName()}")); string editNameValue = string.Join(",",type.GetProperties() .Where(s=>!s.Name.Equals("Id")) .Select(s=>$"[{s.GetAttributeName()}]=@{s.GetAttributeName()}")); Selectsql = $"select [{TableName}] from {columnnName}"; Addsql = $"insert [{TableName}]({columnnName}) values ({columnValue})"; Deletesql = $"delete from [{TableName}]"; Updatesql = $"update [{TableName}] set {editNameValue}"; } } }
会不断更新,具体解释都在代码注释里了,一起进步啊!
3-5天内更新完,最近自己也在学习
越是无知的人越是觉得自己无所不知(之前的自己)
越是学习的人越是觉得自己会的太少了(现在的自己)
共勉