一、原理與環境
在生成數據表的實體類時,利用自定義特性,給它打上表及字段的特性,然后使用反射原理,將自定義特性拼接成增、刪、改、查對應的SQL,即可完成一個簡單的ORM。
本示例的執行環境:
1)數據庫:SQL Server。(可根據自己的需要,建立不同的數據庫工廠。)
2)數據表:需使用自增類型(identity)作為數據表的主鍵。主鍵名字可以隨便起,如ID。
3)實體類:實體類需提供無參構造函數。
二、演示數據表
Person表,包含主鍵(ID)、姓名(Name)、年齡(Age)、性別(Gender)。
CREATE TABLE [dbo].[Person]( [ID] [BIGINT] IDENTITY(1,1) NOT NULL, [Name] [NVARCHAR](50) NULL, [Age] [INT] NULL, [Gender] [NVARCHAR](10) NULL, CONSTRAINT [PK_Person] PRIMARY KEY CLUSTERED ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
二、自定義特性
定義兩個自定義特性:
2.1、DataTableAttribute
此為數據表特性,包含表名(TableName)、主鍵(Key)。
[Serializable] public class DataTableAttribute : Attribute { /// <summary> /// 數據表 /// </summary> public string TableName { get; } /// <summary> /// 主鍵 /// </summary> public string Key { get; } /// <summary> /// 構造函數 /// </summary> /// <param name="tableName"></param> /// <param name="key"></param> public DataTableAttribute(string tableName, string key) { TableName = tableName; Key = key; } }
2.2、DataFieldAttribute
此為字段特性,包含字段名(FieldName)、字段類型(FieldType)、長度(Length)、是否自增(IsIdentity)。
[Serializable] public class DataFieldAttribute : Attribute { public string FieldName { get; set; } public string FieldType { get; set; } public int Length { get; set; } public bool IsIdentity { get; set; } /// <summary> /// 構造函數 /// </summary> /// <param name="fieldName">字段名</param> /// <param name="fieldType">字段類型</param> /// <param name="length">長度</param> /// <param name="isIdentity">是否自增長</param> public DataFieldAttribute(string fieldName, string fieldType, int length, bool isIdentity) { FieldName = fieldName; FieldType = fieldType; Length = length; IsIdentity = isIdentity; } /// <summary> /// 構造函數 /// </summary> /// <param name="fieldName"></param> /// <param name="length"></param> /// <param name="isIdentity"></param> public DataFieldAttribute(string fieldName, string fieldType, int length) : this(fieldName, fieldType, length, false) { } /// <summary> /// 構造函數 /// </summary> /// <param name="fieldName"></param> /// <param name="fieldtype"></param> public DataFieldAttribute(string fieldName, string fieldType) : this(fieldName, fieldType, 0, false) { } /// <summary> /// 構造函數 /// </summary> /// <param name="fieldName"></param> /// <param name="isIdentity"></param> public DataFieldAttribute(string fieldName, bool isIdentity) : this(fieldName, "", 0, isIdentity) { } /// <summary> /// 構造函數 /// </summary> /// <param name="fieldName"></param> public DataFieldAttribute(string fieldName) : this(fieldName, false) { } }
三、生成實體類
3.1、實體類樣式
依照前面的規划,Person表需要生成下面這個樣子:
using System; using System.Collections.Generic; using System.Text; using LinkTo.ORM.CustomAttribute; namespace LinkTo.ORM.Model { [DataTable("Person","ID")] [Serializable] public class Person { public Person() { } [DataField("ID","bigint",19,true)] public long? ID {get; set;} [DataField("Name","nvarchar",50,false)] public string Name {get; set;} [DataField("Age","int",10,false)] public int? Age {get; set;} [DataField("Gender","nvarchar",10,false)] public string Gender {get; set;} } }
3.2、使用T4模板生成實體類
3.2.1、T4Code文件夾的文本模板

<#@ assembly name="System.Core" #> <#@ assembly name="System.Data" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.Data"#> <#@ import namespace="System.Data.SqlClient"#> <#+ #region T4Code /// <summary> /// 數據庫架構接口 /// </summary> public interface IDBSchema : IDisposable { List<string> GetTableList(); DataTable GetTableMetadata(string tableName); } /// <summary> /// 數據庫架構工廠 /// </summary> public class DBSchemaFactory { static readonly string DatabaseType = "SqlServer"; public static IDBSchema GetDBSchema() { IDBSchema dbSchema; switch (DatabaseType) { case "SqlServer": { dbSchema =new SqlServerSchema(); break; } default: { throw new ArgumentException("The input argument of DatabaseType is invalid."); } } return dbSchema; } } /// <summary> /// SqlServer /// </summary> public class SqlServerSchema : IDBSchema { public string ConnectionString = "Server=.;Database=Test;Uid=sa;Pwd=********;"; public SqlConnection conn; public SqlServerSchema() { conn = new SqlConnection(ConnectionString); conn.Open(); } public List<string> GetTableList() { List<string> list = new List<string>(); string commandText = "SELECT NAME TABLE_NAME FROM SYSOBJECTS WHERE XTYPE='U' ORDER BY NAME"; using(SqlCommand cmd = new SqlCommand(commandText, conn)) { using (SqlDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection)) { while (dr.Read()) { list.Add(dr["TABLE_NAME"].ToString()); } } } return list; } public DataTable GetTableMetadata(string tableName) { string commandText=string.Format ( "SELECT A.NAME TABLE_NAME,B.NAME FIELD_NAME,C.NAME DATATYPE,ISNULL(B.PREC,0) LENGTH, "+ "CONVERT(BIT,CASE WHEN NOT F.ID IS NULL THEN 1 ELSE 0 END) ISKEY, "+ "CONVERT(BIT,CASE WHEN COLUMNPROPERTY(B.ID,B.NAME,'ISIDENTITY') = 1 THEN 1 ELSE 0 END) AS ISIDENTITY, "+ "CONVERT(BIT,B.ISNULLABLE) ISNULLABLE "+ "FROM SYSOBJECTS A INNER JOIN SYSCOLUMNS B ON A.ID=B.ID INNER JOIN SYSTYPES C ON B.XTYPE=C.XUSERTYPE "+ "LEFT JOIN SYSOBJECTS D ON B.ID=D.PARENT_OBJ AND D.XTYPE='PK' "+ "LEFT JOIN SYSINDEXES E ON B.ID=E.ID AND D.NAME=E.NAME "+ "LEFT JOIN SYSINDEXKEYS F ON B.ID=F.ID AND B.COLID=F.COLID AND E.INDID=F.INDID "+ "WHERE A.XTYPE='U' AND A.NAME='{0}' "+ "ORDER BY A.NAME,B.COLORDER", tableName ); using(SqlCommand cmd = new SqlCommand(commandText, conn)) { SqlDataAdapter da = new SqlDataAdapter(cmd); DataSet ds = new DataSet(); da.Fill(ds,"Schema"); return ds.Tables[0]; } } public void Dispose() { if (conn != null) { conn.Close(); } } } #endregion #>

<#@ assembly name="System.Core" #> <#@ assembly name="System.Data" #> <#@ assembly name="EnvDTE" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.Data"#> <#@ import namespace="System.IO"#> <#@ import namespace="Microsoft.VisualStudio.TextTemplating"#> <#+ // T4 Template Block manager for handling multiple file outputs more easily. // Copyright (c) Microsoft Corporation.All rights reserved. // This source code is made available under the terms of the Microsoft Public License (MS-PL) // Manager class records the various blocks so it can split them up class Manager { public struct Block { public string Name; public int Start, Length; } public List<Block> blocks = new List<Block>(); public Block currentBlock; public Block footerBlock = new Block(); public Block headerBlock = new Block(); public ITextTemplatingEngineHost host; public ManagementStrategy strategy; public StringBuilder template; public string OutputPath { get; set; } public Manager(ITextTemplatingEngineHost host, StringBuilder template, bool commonHeader) { this.host = host; this.template = template; OutputPath = string.Empty; strategy = ManagementStrategy.Create(host); } public void StartBlock(string name) { currentBlock = new Block { Name = name, Start = template.Length }; } public void StartFooter() { footerBlock.Start = template.Length; } public void EndFooter() { footerBlock.Length = template.Length - footerBlock.Start; } public void StartHeader() { headerBlock.Start = template.Length; } public void EndHeader() { headerBlock.Length = template.Length - headerBlock.Start; } public void EndBlock() { currentBlock.Length = template.Length - currentBlock.Start; blocks.Add(currentBlock); } public void Process(bool split) { string header = template.ToString(headerBlock.Start, headerBlock.Length); string footer = template.ToString(footerBlock.Start, footerBlock.Length); blocks.Reverse(); foreach(Block block in blocks) { string fileName = Path.Combine(OutputPath, block.Name); if (split) { string content = header + template.ToString(block.Start, block.Length) + footer; strategy.CreateFile(fileName, content); template.Remove(block.Start, block.Length); } else { strategy.DeleteFile(fileName); } } } } class ManagementStrategy { internal static ManagementStrategy Create(ITextTemplatingEngineHost host) { return (host is IServiceProvider) ? new VSManagementStrategy(host) : new ManagementStrategy(host); } internal ManagementStrategy(ITextTemplatingEngineHost host) { } internal virtual void CreateFile(string fileName, string content) { File.WriteAllText(fileName, content); } internal virtual void DeleteFile(string fileName) { if (File.Exists(fileName)) File.Delete(fileName); } } class VSManagementStrategy : ManagementStrategy { private EnvDTE.ProjectItem templateProjectItem; internal VSManagementStrategy(ITextTemplatingEngineHost host) : base(host) { IServiceProvider hostServiceProvider = (IServiceProvider)host; if (hostServiceProvider == null) throw new ArgumentNullException("Could not obtain hostServiceProvider"); EnvDTE.DTE dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE)); if (dte == null) throw new ArgumentNullException("Could not obtain DTE from host"); templateProjectItem = dte.Solution.FindProjectItem(host.TemplateFile); } internal override void CreateFile(string fileName, string content) { base.CreateFile(fileName, content); ((EventHandler)delegate { templateProjectItem.ProjectItems.AddFromFile(fileName); }).BeginInvoke(null, null, null, null); } internal override void DeleteFile(string fileName) { ((EventHandler)delegate { FindAndDeleteFile(fileName); }).BeginInvoke(null, null, null, null); } private void FindAndDeleteFile(string fileName) { foreach(EnvDTE.ProjectItem projectItem in templateProjectItem.ProjectItems) { if (projectItem.get_FileNames(0) == fileName) { projectItem.Delete(); return; } } } } #>
DBSchema.ttinclude主要實現了數據庫工廠的功能。注:請將數據庫連接字符串改成您自己的。
MultiDocument.ttinclude主要實現了多文檔的功能。
3.2.2、生成實體類的文本模板

<#@ template debug="true" hostspecific="true" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".cs" #> <#@ include file="T4Code/DBSchema.ttinclude"#> <#@ include file="T4Code/MultiDocument.ttinclude"#> <# var manager = new Manager(Host, GenerationEnvironment, true) { OutputPath = Path.GetDirectoryName(Host.TemplateFile)}; #> <# //System.Diagnostics.Debugger.Launch();//調試 var dbSchema = DBSchemaFactory.GetDBSchema(); List<string> tableList = dbSchema.GetTableList(); foreach (string tableName in tableList) { manager.StartBlock(tableName+".cs"); DataTable table = dbSchema.GetTableMetadata(tableName); //獲取主鍵 string strKey = string.Empty; foreach (DataRow dataRow in table.Rows) { if ((bool)dataRow["ISKEY"] == true) { strKey = dataRow["FIELD_NAME"].ToString(); break; } } #> //------------------------------------------------------------------------------- // 此代碼由T4模板MultModelAuto自動生成 // 生成時間 <#= DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") #> // 對此文件的更改可能會導致不正確的行為,並且如果重新生成代碼,這些更改將會丟失。 //------------------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Text; using LinkTo.ORM.CustomAttribute; namespace LinkTo.ORM.Model { [DataTable("<#= tableName #>","<#= strKey #>")] [Serializable] public class <#= tableName #> { public <#= tableName #>() { } <# foreach (DataRow dataRow in table.Rows) { //獲取數據類型 string dbDataType = dataRow["DATATYPE"].ToString(); string dataType = string.Empty; switch (dbDataType) { case "decimal": case "numeric": case "money": case "smallmoney": dataType = "decimal?"; break; case "char": case "nchar": case "varchar": case "nvarchar": case "text": case "ntext": dataType = "string"; break; case "uniqueidentifier": dataType = "Guid?"; break; case "bit": dataType = "bool?"; break; case "real": dataType = "Single?"; break; case "bigint": dataType = "long?"; break; case "int": dataType = "int?"; break; case "tinyint": case "smallint": dataType = "short?"; break; case "float": dataType = "float?"; break; case "date": case "datetime": case "datetime2": case "smalldatetime": dataType = "DateTime?"; break; case "datetimeoffset ": dataType = "DateTimeOffset?"; break; case "timeSpan ": dataType = "TimeSpan?"; break; case "image": case "binary": case "varbinary": dataType = "byte[]"; break; default: break; } #> [DataField("<#= dataRow["FIELD_NAME"].ToString() #>","<#= dataRow["DATATYPE"].ToString() #>",<#= dataRow["LENGTH"].ToString() #>,<#= dataRow["ISIDENTITY"].ToString().ToLower() #>)] public <#= dataType #> <#= dataRow["FIELD_NAME"].ToString() #> {get; set;} <# } #> } } <# manager.EndBlock(); } dbSchema.Dispose(); manager.Process(true); #>
注:由於ORM拼接SQL時使用的是表特性及字段特性,可以看出表特性上使用的表名、字段特性上使用的字段名,都是與數據庫一致的。有了這個保障,數據表生成實體類的時候,類名是可以更改的,因為我只需要保證表特性與數據庫一致即可。舉個例子,我有個數據表Person_A,在生成實體類時,類名可以生成為Class PersonA {...},但是表特性依然是[DataTable("Person_A","...")]。相同的原理,屬性名也是可以更改的。
四、ORM實現
數據表的CURD,主要是通過反射來實現SQL拼接,實現如下:

using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using LinkTo.ORM.CustomAttribute; namespace LinkTo.ORM { public static class DBHelper { public static readonly string ConnectionString = "Server=.;Database=Test;Uid=sa;Pwd=********;"; private static readonly Hashtable _HashTableName = new Hashtable(); //表名緩存 private static readonly Hashtable _HashKey = new Hashtable(); //主鍵緩存 /// <summary> /// 數據庫連接 /// </summary> /// <returns></returns> public static SqlConnection GetConnection() { SqlConnection conn = new SqlConnection(ConnectionString); return conn; } /// <summary> /// 新增 /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="entity"></param> /// <returns></returns> public static int Insert<TEntity>(TEntity entity) where TEntity : class { string strTableName = ""; //表名 string strInsertSQL = "INSERT INTO {0} ({1}) VALUES ({2})"; //SQL拼接語句 //獲取表名 strTableName = GetTableName(entity); //獲取字段列表及值列表 StringBuilder strFields = new StringBuilder(); StringBuilder strValues = new StringBuilder(); List<SqlParameter> paraList = new List<SqlParameter>(); PropertyInfo[] infos = entity.GetType().GetProperties(); DataFieldAttribute dfAttr = null; object[] dfAttrs; int i = 0; foreach (PropertyInfo info in infos) { dfAttrs = info.GetCustomAttributes(typeof(DataFieldAttribute), false); if (dfAttrs.Length > 0) { dfAttr = dfAttrs[0] as DataFieldAttribute; if (dfAttr is DataFieldAttribute) { //自增字段不作處理 if (dfAttr.IsIdentity) continue; strFields.Append(i > 0 ? "," + dfAttr.FieldName : dfAttr.FieldName); strValues.Append(i > 0 ? "," + "@" + dfAttr.FieldName : "@" + dfAttr.FieldName); i++; paraList.Add(new SqlParameter("@" + dfAttr.FieldName, info.GetValue(entity, null))); } } } //格式化SQL拼接語句 string[] args = new string[] { strTableName, strFields.ToString(), strValues.ToString() }; strInsertSQL = string.Format(strInsertSQL, args); //執行結果 int result = 0; try { using (SqlConnection conn = GetConnection()) { conn.Open(); using (SqlCommand cmd = new SqlCommand()) { cmd.CommandText = strInsertSQL; cmd.CommandType = CommandType.Text; cmd.Connection = conn; if (paraList != null) { foreach (SqlParameter param in paraList) { cmd.Parameters.Add(param); } } result = cmd.ExecuteNonQuery(); } } } catch (Exception ex) { throw new Exception(ex.ToString()); } //返回影響行數 return result; } /// <summary> /// 刪除 /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="condition"></param> /// <returns></returns> public static int Delete<TEntity>(string condition) where TEntity : class, new() { string strTableName = ""; //表名 string strDeleteSQL = "DELETE FROM {0} WHERE {1}"; //SQL拼接語句 //獲取表名 strTableName = GetTableName(new TEntity()); //格式化SQL拼接語句 string[] args = new string[] { strTableName, condition }; strDeleteSQL = string.Format(strDeleteSQL, args); //執行結果 int result = 0; try { using (SqlConnection conn = GetConnection()) { conn.Open(); using (SqlCommand cmd = new SqlCommand()) { cmd.CommandText = strDeleteSQL; cmd.CommandType = CommandType.Text; cmd.Connection = conn; result = cmd.ExecuteNonQuery(); } } } catch (Exception ex) { throw new Exception(ex.ToString()); } //返回影響行數 return result; } /// <summary> /// 更新 /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="entity"></param> /// <returns></returns> public static int Update<TEntity>(TEntity entity) where TEntity : class { string strTableName = ""; //表名 string strUpdateSQL = "UPDATE {0} SET {1} WHERE {2}"; //SQL拼接語句 string strKey = ""; //主鍵 string strWhere = ""; //條件 //獲取表名及主鍵 strTableName = GetTableName(entity); strKey = GetKey(entity); //獲取更新列表 StringBuilder strSET = new StringBuilder(); List<SqlParameter> paraList = new List<SqlParameter>(); PropertyInfo[] infos = entity.GetType().GetProperties(); DataFieldAttribute dfAttr = null; object[] dfAttrs; int i = 0; foreach (PropertyInfo info in infos) { dfAttrs = info.GetCustomAttributes(typeof(DataFieldAttribute), false); if (dfAttrs.Length > 0) { dfAttr = dfAttrs[0] as DataFieldAttribute; if (dfAttr is DataFieldAttribute) { //條件處理 if (dfAttr.FieldName == strKey) { strWhere = strKey + "=" + info.GetValue(entity, null); } //自增字段不作處理 if (dfAttr.IsIdentity) continue; strSET.Append(i > 0 ? "," + dfAttr.FieldName + "=@" + dfAttr.FieldName : dfAttr.FieldName + "=@" + dfAttr.FieldName); i++; paraList.Add(new SqlParameter("@" + dfAttr.FieldName, info.GetValue(entity, null))); } } } //格式化SQL拼接語句 string[] args = new string[] { strTableName, strSET.ToString(), strWhere }; strUpdateSQL = string.Format(strUpdateSQL, args); //執行結果 int result = 0; try { using (SqlConnection conn = GetConnection()) { conn.Open(); using (SqlCommand cmd = new SqlCommand()) { cmd.CommandText = strUpdateSQL; cmd.CommandType = CommandType.Text; cmd.Connection = conn; if (paraList != null) { foreach (SqlParameter param in paraList) { cmd.Parameters.Add(param); } } result = cmd.ExecuteNonQuery(); } } } catch (Exception ex) { throw new Exception(ex.ToString()); } //返回影響行數 return result; } /// <summary> /// 查詢 /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="condition"></param> /// <returns></returns> public static List<TEntity> Query<TEntity>(string condition) where TEntity : class, new() { string strTableName = ""; //表名 string strSelectSQL = "SELECT * FROM {0} WHERE {1}"; //SQL拼接語句 List<TEntity> list = new List<TEntity>(); //實體列表 //獲取表名 strTableName = GetTableName(new TEntity()); //格式化SQL拼接語句 string[] args = new string[] { strTableName, condition }; strSelectSQL = string.Format(strSelectSQL, args); //獲取實體列表 PropertyInfo[] infos = typeof(TEntity).GetProperties(); DataFieldAttribute dfAttr = null; object[] dfAttrs; try { using (SqlConnection conn = GetConnection()) { conn.Open(); using (SqlCommand cmd = new SqlCommand(strSelectSQL, conn)) { using (SqlDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection)) { while (dr.Read()) { TEntity entity = new TEntity(); foreach (PropertyInfo info in infos) { dfAttrs = info.GetCustomAttributes(typeof(DataFieldAttribute), false); if (dfAttrs.Length > 0) { dfAttr = dfAttrs[0] as DataFieldAttribute; if (dfAttr is DataFieldAttribute) { info.SetValue(entity, dr[dfAttr.FieldName]); } } } list.Add(entity); } } } } } catch (Exception ex) { throw new Exception(ex.ToString()); } //返回實體列表 return list; } /// <summary> /// 根據實體返回表名 /// </summary> /// <param name="entity"></param> /// <returns></returns> public static string GetTableName<TEntity>(TEntity entity) where TEntity : class { Type entityType = entity.GetType(); string strTableName = Convert.ToString(_HashTableName[entityType.FullName]); if (strTableName == "") { if (entityType.GetCustomAttributes(typeof(DataTableAttribute), false)[0] is DataTableAttribute dtAttr) { strTableName = dtAttr.TableName; } else { throw new Exception(entityType.ToString() + "未設置DataTable特性。"); } _HashTableName[entityType.FullName] = strTableName; } return strTableName; } /// <summary> /// 根據實體返回主鍵 /// </summary> /// <param name="entity"></param> /// <returns></returns> public static string GetKey<TEntity>(TEntity entity) where TEntity : class { Type entityType = entity.GetType(); string strKey = Convert.ToString(_HashKey[entityType.FullName]); if (strKey == "") { if (entityType.GetCustomAttributes(typeof(DataTableAttribute), false)[0] is DataTableAttribute dtAttr) { strKey = dtAttr.Key; } else { throw new Exception(entityType.ToString() + "未設置DataTable特性。"); } _HashKey[entityType.FullName] = strKey; } return strKey; } } }
五、運行測試
新建一個控制台程序:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using LinkTo.ORM.Model; namespace LinkTo.ORM.Client { class Program { static void Main() { //新增 Person insertPerson = new Person { Name = "Hello", Age = 18, Gender = "male" }; int insertResult = DBHelper.Insert(insertPerson); Console.WriteLine($"共新增了 {insertResult} 條記錄。"); //更新 List<Person> updateList = DBHelper.Query<Person>("Name='Hello'"); int updateResult = 0; if (updateList.Count > 0) { foreach (var item in updateList) { Person updatePerson = item; updatePerson.Age = 19; updateResult += DBHelper.Update(updatePerson); } } Console.WriteLine($"共更新了 {updateResult} 條記錄。"); //查詢 List<Person> selectList = DBHelper.Query<Person>("Name='Hello'"); if (selectList.Count > 0) { foreach (var item in selectList) { Console.WriteLine("person.Name = " + item.Name); Console.WriteLine("person.Age = " + item.Age); Console.WriteLine("person.Gender = " + item.Gender); } } //刪除 int deleteResult = DBHelper.Delete<Person>("Name='Hello'"); Console.WriteLine($"共刪除了 {deleteResult} 條記錄。"); Console.Read(); } } }
運行結果如下:
參考自:
MiniORM