五、组合ORM映射生成insert语句。
我们仔细思考下看到上面的我们会怎么想才可以生成一条sql语句并且执行。
首先我们是不是应该分开两部分
第一步负责生成插入语句。
第二步是负责执行插入语句得。
我们继续思考?生成一条插入语句我们要考虑哪些问题?
a、是不是返回值
b、是不是要判断表中是否有不需要组合为插入语句的字段(如自增字段)
c、而且这个插入语句是针对对象的插入语句而不是固定的某个或者已知的某个实体。所以我们会考虑到泛型的使用。
这样我们基本确定了insert语句的参数和结构。
我们再回到第一步如何根据实体生成插入语句? 我们第四部也只是说了根据实体映射出和数据库字段一样的名字,这有什么用呢?
肯定根据这些字段名我们要想办法组合个sql语句。继续分析、、、
如何分工 :肯定要给个执行的sql语句
分工一:是不是获得属性名称组合sql。
分工二:是不是做个参数的对应表。
分工三:组合这些东西,并把其属性类型和数据库字段类型对应起来。
上面说了那么多,只是帮大家打开思路,其实只要你理解了映射(第四项),用自己的思路去写那些组合sql也可以得,
我这个地方写的也不见得完美,只是给大家做个例子,嘿嘿,一起加油!
有几个地方用到了枚举首先我列出枚举的方法:
第一个属性标识是否为主键或者读写的标识 [Serializable] [Flags] public enum ColumnKeyType { /// /// 默认状态 /// Default = 1, /// /// 标识为主键 /// Identity = 2, /// /// Extend状态下,不参与读取、增加、修改 /// Extend = 4, /// /// Read状态下不参与增加、修改 /// Read = 8 } 返回值做了枚举: public enum DBReturnType { /// /// 返回受影响的行数 /// EffectRow, /// /// 返回最后插入的主键值 /// Identity } |
插入语句的代码:
#region 把对象内容保存到数据库中 Insert /// /// 把对象内容保存到数据库中 /// /// /// <param name="model"></param> /// <param name="isIncludeKeyColumn">插入语句中是否包含对主键的插入, 当主键值为自动增加时为false</param> /// <param name="returnType">返回的数据类型:DBReturnType.EffectRow 为返回受影响行数; DBReturnType.IdEntity 返回最新插入主键值(isIncludeKeyColumn == false时有效)</param> public static int Insert(T model, bool isIncludeKeyColumn, DBReturnType returnType) where T : class { int i = 0; Type type = typeof(T); //获取表名 string tableName = EntityHelper.GetTableName(type); PropertyInfo[] pis = type.GetProperties(); //获取所有字段和主键名称 List columns = null; //处理是否包含主键插入 if (isIncludeKeyColumn == false) { columns = EntityHelper.GetTableColumns(pis, ColumnKeyType.Identity | ColumnKeyType.Extend, null); } else { columns = EntityHelper.GetTableColumns(pis, ColumnKeyType.Extend, null); } //生成INSERT语句 StringBuilder sqlText = new StringBuilder(); sqlText.Append("INSERT INTO "); sqlText.Append(tableName); sqlText.Append(" ("); //第一个字段 sqlText.Append(columns[0]); //第二个起所有字段 int loop = columns.Count; for (i = 1; i < loop; i++) { sqlText.Append(","); sqlText.Append(columns[i]); } sqlText.Append(") VALUES ("); //第一个字段 sqlText.Append("@"); sqlText.Append(columns[0]); //第二个起所有字段 for (i = 1; i < loop; i++) { sqlText.Append(",@"); sqlText.Append(columns[i]); } sqlText.Append(");"); //生成MySqlParamter PropertyInfo propertyInfo = null; SqlParameter[] paras = new SqlParameter[loop]; for (i = 0; i < loop; i++) { propertyInfo = type.GetProperty(columns[i]); paras[i] = new SqlParameter(columns[i], GetMySqlDbType(propertyInfo.PropertyType), -1); paras[i].Value = propertyInfo.GetValue(model, null); } //根据两种情况返回不同的值 if (isIncludeKeyColumn == false && returnType == DBReturnType.Identity) { sqlText.Append(" SELECT @@identity AS RetId"); SqlDataReader sdr = DataReader(sqlText.ToString(), CommandType.Text, paras); int keyId = 0; if (sdr.Read()) { keyId = Convert.ToInt32(sdr["RetId"]); } sdr.Close(); return keyId; } else { return NonQuery(sqlText.ToString(), CommandType.Text, paras); } } #endregion #region 根据Type类型获取SQL的数据类型 /// /// 根据Type类型获取MySQL的数据类型 /// /// <param name="type"></param> /// private static SqlDbType GetMySqlDbType(Type type) { SqlDbType dbtype = SqlDbType.VarChar; if (type.Equals(typeof(string))) { } else if (type.Equals(typeof(int))) { dbtype = SqlDbType.Int; } else if (type.Equals(typeof(bool))) { dbtype = SqlDbType.Bit; } else if (type.Equals(typeof(DateTime))) { dbtype = SqlDbType.DateTime; } else if (type.Equals(typeof(decimal))) { dbtype = SqlDbType.Decimal; } else if (type.Equals(typeof(float))) { dbtype = SqlDbType.Float; } else if (type.Equals(typeof(double))) { dbtype = SqlDbType.Float; } return dbtype; } #endregion |
下面我们简单定义一个描述数据库字段信息的Attribute 包括表名 属性字段获得
从Model模型中获取数据表名、主键名、 获取需要的读取数据源的字段集
(忘了说明表的表名写在那个地方,其实表名只需要定义在类的上面就可以了)为了简单起见我还是把目前的model放到代码里面
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace FuzhuKeji { [Serializable] [Property("TB_People")] public class M_People { string _Pl_ID; /// /// 主键 /// [Property(ColumnKeyType.Identity)] public string Pl_ID { get { return _Pl_ID; } set { _Pl_ID = value; } } int _PL_Age; public int PL_Age { get { return _PL_Age; } set { _PL_Age = value; } } string _Pl_Sex; public string Pl_Sex { get { return _Pl_Sex; } set { _Pl_Sex = value; } } string _Pl_LoginName; public string Pl_LoginName { get { return _Pl_LoginName; } set { _Pl_LoginName = value; } } string _Pl_TrueName; public string Pl_TrueName { get { return _Pl_TrueName; } set { _Pl_TrueName = value; } } string _PL_Pwd; public string PL_Pwd { get { return _PL_Pwd; } set { _PL_Pwd = value; } } } } |
好吧这样就不用担心了映射表名获得字段的代码在下面:
#region 下面我们简单定义一个描述数据库字段信息的Attribute 包括表名 属性字段获得 public class PropertyAttribute : Attribute { public string tableName; public ColumnKeyType columnKeyType; /// /// 重构方法默认值 /// public PropertyAttribute() { this.columnKeyType = ColumnKeyType.Default; } /// /// /// /// <param name="tableName"></param> public PropertyAttribute(string tableName) { this.tableName = tableName; } public PropertyAttribute(ColumnKeyType columnKeyType) { this.columnKeyType = columnKeyType; } } #endregion #region 从Model模型中获取数据表名、主键名、 获取需要的读取数据源的字段集 public class EntityHelper { /// /// 从Model模型中获取数据表名 /// public static string GetTableName(Type type) { PropertyAttribute property = (PropertyAttribute)(type.GetCustomAttributes(false)[0]); return property.tableName; } /// /// 从Model模型中获取数据主键名 /// public static PropertyInfo GetTableIdentity(PropertyInfo[] pis) { object[] infos = null; PropertyAttribute attribute = null; foreach (PropertyInfo pi in pis) { infos = pi.GetCustomAttributes(false); if (infos.Length > 0) { attribute = (PropertyAttribute)(infos[0]); if (attribute.columnKeyType == ColumnKeyType.Identity) { return pi; } } } return null; } /// /// 获取需要的读取数据源的字段集 /// /// <param name="pis">Model模型所有属性集合</param> /// <param name="filter"></param> /// <param name="customColumns">自定义查询列名集合,使用逗号分隔。如不需要则为null</param> /// public static List GetTableColumns(PropertyInfo[] pis, ColumnKeyType filter, string customColumns) { string col = ""; return GetTableColumns(pis, filter, customColumns, ref col); } /// /// 获取需要的读取数据源的字段集 /// /// <param name="pis">Model模型所有属性集合</param> /// <param name="filter"></param> /// <param name="customColumns">自定义查询列名集合,使用逗号分隔。如不需要则为null</param> /// public static List GetTableColumns(PropertyInfo[] pis, ColumnKeyType filter, string customColumns, ref string outCol) { List columns = new List(); if (customColumns != null && customColumns.Length > 0) { /* * 需要安全处理 * 限制字段不包含空格 */ customColumns = customColumns.Trim(); string[] strs = customColumns.Split(','); foreach (string str in strs) { if (IsRegexMatch(str, @"^(\w[^\s';]+)$")) { columns.Add(str); } } outCol = customColumns; } else { object[] infos = null; PropertyAttribute attribute = null; foreach (PropertyInfo pi in pis) { //删除外部扩展对象项 infos = pi.GetCustomAttributes(false); if (infos.Length > 0) { attribute = (PropertyAttribute)(infos[0]); if (attribute.columnKeyType == (filter & attribute.columnKeyType)) { continue; } } outCol += string.Concat(",", pi.Name); columns.Add(pi.Name); } outCol = outCol.Remove(0, 1); } return columns; } /// /// 检查是否满足某种正则表达式 /// private static bool IsRegexMatch(string str, string Express) { if (string.IsNullOrEmpty(str)) { return false; } return Regex.IsMatch(str, Express); } } #endregion |
上面就完成了sql语句的生成:下面我就定义一个sql语句的执行就可以了。其实你到insert方法里会发现 在生产完sql语句就调用执行方法了。
/// /// 配置字符串参数 /// private static void PrepareCommand(SqlConnection conn, SqlTransaction trans, SqlCommand sqlCommand, string sqlText, CommandType commandType, SqlParameter[] parms) { if (conn.State != ConnectionState.Open) { conn.Open(); } sqlCommand.Connection = conn; sqlCommand.CommandText = sqlText; sqlCommand.CommandType = commandType; if (trans != null) { sqlCommand.Transaction = trans; } if (parms != null) { foreach (SqlParameter parm in parms) { sqlCommand.Parameters.Add(parm); } } } /// /// 执行SQL语句,返回数据集 /// public static SqlDataReader DataReader(string sqlText, CommandType commandType, SqlParameter[] parms) { SqlConnection conn = new SqlConnection(@"Data Source=HAOFUQI\SQLEXPRESS; Initial Catalog=Fukusuke;Persist Security Info=True;User ID=sa;pwd=123"); SqlCommand sqlCommand = new SqlCommand(); PrepareCommand(conn, null, sqlCommand, sqlText, commandType, parms); SqlDataReader reader = sqlCommand.ExecuteReader(CommandBehavior.CloseConnection); sqlCommand.Dispose(); return reader; } /// /// 执行SQL语句,并返回影响行数 /// public static int NonQuery(string sqlText, CommandType commandType, SqlParameter[] parms) { int reVal = 0; using (SqlConnection conn = new SqlConnection(@"Data Source=HAOFUQI\SQLEXPRESS; Initial Catalog=Fukusuke;Persist Security Info=True;User ID=sa;pwd=123")) { SqlCommand sqlCommand = new SqlCommand(); PrepareCommand(conn, null, sqlCommand, sqlText, commandType, parms); reVal = sqlCommand.ExecuteNonQuery(); sqlCommand.Parameters.Clear(); sqlCommand.Dispose(); } return reVal; |
六、测试ORM的插入映射。
/// /// 执行插入语句 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button3_Click(object sender, EventArgs e) { M_People mp = new M_People(); mp.PL_Age = 26; mp.Pl_ID = "001"; mp.Pl_LoginName = "Test1"; mp.PL_Pwd = "123"; mp.Pl_Sex = "男"; mp.Pl_TrueName = "张三"; int Insert_Key= DBHelper.Insert(mp,false ,DBReturnType.Identity); MessageBox.Show("添加成功! 插入数据的主键为:"+Insert_Key.ToString()); } |
测试结果如下:
数据库插入效果:
这个地方就插入语句成功了。
七、总结。
这篇文章写得不是很好,我只是想描述,却没有一层层去剥开,也许这篇文章太长了吧。
这篇文章的核心应该是说明白如何映射如何到sql。没有分层的概念。看起来混乱,但是如果分层我就要更多篇幅讲解。
写的错误或者不好的地方还请多多批评指导。
上一篇写的是:为初学者写三层.为初学者写三层,三层的搭建和测试例子
下面准备写ORM框架,然后把生成器集成到框架中。(伪三层+ORM)+生成器+常用类库 用一个财务报销系统作为案例。
好了就写到这吧!