1.查看硬盘 [mushme@investide ~]$ df -ah 文件系统 容量 已用 可用 已用% 挂载点 /dev/cciss/c0d0p1 123G 63G 55G 54% / proc 0 0 0 – /proc sysfs 0 0 0 – /sys devpts 0 0 0 – […]
View Detailslaravel 5.4 改变了默认的数据库字符集,现在utf8mb4包括存储emojis支持。如果你运行MySQL v5.7.7或者更高版本,则不需要做任何事情。 当你试着在一些MariaDB或者一些老版本的的MySQL上运行 migrations 命令时,你可能会碰到下面这个错误:
1 2 3 4 5 |
[Illuminate\Database\QueryException] SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes (SQL: alter table users add unique users_email_unique(email)) [PDOException] SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes |
我们可以在 AppServiceProvider.php 文件里的 boot 方法里设置一个默认值:
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 |
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\Schema; class AppServiceProvider extends ServiceProvider { /** * Bootstrap any application services. * * @return void */ public function boot() { Schema::defaultStringLength(191); } /** * Register any application services. * * @return void */ public function register() { // } } |
from:https://www.cnblogs.com/betx/p/6544090.html
View Details简述 适用特性 使用Dapper流程 代码示例 简述 Dapper是一个轻量级的ORM工具:ORM框架的核心思想是对象关系映射,ORM是将表与表之间的操作,映射成对象和对象之间的操作,就是通过操作实体类来达到操作表的目的。从数据库提取的数据会自动按你设置的映射要求封装成特定的对象。之后你就可以通过对对象进行操作来修改数据库中的数据。这时候你面对的不是信息的碎片,而是一个形象鲜明的对象。 ORM 框架很多: Dapper、 Mybatis.Net、EntityFramework 和 NHibernate。如果你在小的项目中,使用Entity Framework、NHibernate 来处理大数据访问及关系映射,未免有点杀鸡用牛刀。而Mybatis.Net需要配置XML文件,综合考虑你会觉得觉得ORM省时省力。 适用特性 1、Dapper是一个轻型的ORM类。代码就一个SqlMapper.cs文件,编译后就70K。 2、Dapper很快。Dapper的速度接近与IDataReader,取列表的数据超过了DataTable。 3、Dapper支持多数据库。Dapper支持Mysql,SqlLite,SqlServer,Oracle等一系列的数据库。 4、Dapper支持多表并联的对象。支持一对多 多对多的关系。并且没侵入性,想用就用,不想用就不用。无XML无属性。代码以前怎么写现在还怎么写。 5、Dapper原理通过Emit反射IDataReader的序列队列,来快速的得到和产生对象。性能很高(性能和原生ado.net相近)。 6、Dapper支持net2.0,3.0,3.5,4.0。【如果想在Net2.0下使用,可以去网上找一下Net2.0下如何配置运行Net3.5即可。】 7、Dapper语法十分简单,快捷添加到项目,容易上手。并且无须迁就数据库的设计。 适用Dapper流程 流程一: 通过NuGet程序包进行Dapper安装引用: 这个引用的Dapper DLL文件是对前面说的SqlMapper.cs 源文件的封装。 流程二: 我们这里用Mysql数据库,那么用到的MySql.Data.dll在官网下载.net驱动包(已经附加在此教程demo中) mysql .net连接驱动下载地址:http://dev.mysql.com/downloads/connector/net/ 示例代码 1、定义一个Person类对应数据库的Person表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
CREATE TABLE person ( idint(11) NOT NULL AUTO_INCREMENT, usernamevarchar(255) NOT NULL, passwordvarchar(20) NOT NULL, ageint(11) DEFAULT NULL, registerDatedatetime DEFAULT NULL, addressvarchar(255) DEFAULT NULL, PRIMARY KEY (id) ); //Person类 publicclassPerson { publicint id { get; set; } publicstring username { get; set; } publicstring password { get; set; } publicint age { get; set; } publicDateTimeregisterDate { get; set; } publicstring address { set; get; } } |
2、定义连接数据库字符串
1 2 3 4 |
public static string ConnString = "server=localhost;port=3306;user id=root;password=123456;database=test"; //SQL Server public static string ConnString = "server=localhost;user id=sa;password=123456;database=Dormitory"; |
3、 新增、修改、删除的 Execute 方法
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 |
public static int Execute(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null); //新增 var conn = MySqlDBHelper.CreateMySqlConnection(); try { var result = conn.Execute("Insert into Person(username,password,age,registerDate,address) values (@username,@password,@age,@registerDate,@address)", new { username = txtUserName.Text, password = txtPassword.Text, age = numAge.Value, registerDate = dptRegisterDate.Value, address = txtAddress.Text }); if (result > 0) { MessageBox.Show("添加成功!"); LoadDBInfoToDgvFirst(); } } catch (Exception ex) { throw ex; } finally { if (conn.State == System.Data.ConnectionState.Open) { conn.Close(); } } |
批量新增 和新增一条语句是一样的,后面的param 是object 对象,单个模型数据和集合都可以支持。执行上面方法会插入多条记录,这样sql可以灵活的控制,参数不用像ADO.Net那样声明每个参数,最后还要把参数集合赋值给ADO的命令。可以看出这样简洁多了。 4、 查询的Query 方法
1 2 3 |
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null); 实际运用: var result = conn.Query<Person>("select * from Person order by id desc limit @indx", new { indx = num }); |
5、存储过程调用
1 2 3 4 5 6 7 8 |
var param = new DynamicParameters(); param.Add("@idIn", numUpdateId.Value, DbType.Int32, ParameterDirection.Input); param.Add("@Inusername", txtUserName.Text, DbType.String, ParameterDirection.Input); param.Add("@res", 0, DbType.Int32, ParameterDirection.Output); var res2 = conn.Execute("prUpdatePersion", param, null, null, CommandType.StoredProcedure); int res = param.Get<Int32>("@res"); |
6、多个数据库插入数据
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 |
private void button4_Click(object sender, EventArgs e) { MySqlConnection mySqlConn = MySqlDBHelper.CreateMySqlConnection(); MySqlTransaction tranMySql = mySqlConn.BeginTransaction(); SqlConnection SqlServerConn = SqlServerDBHelper.CreateSqlServerConnection(); SqlTransaction tranSqlServer = SqlServerConn.BeginTransaction(); try { var persion = new Person { username = txtUserName.Text, password = txtPassword.Text, age = numAge.Value, registerDate = dptRegisterDate.Value, address = txtAddress.Text }; int result = mySqlConn.Execute("Insert into Person(username,password,age,registerDate,address) values (@username,@password,@age,@registerDate,@address)", persion, tranMySql); persion.username += "Sqlserver"; result = SqlServerConn.Execute("Insert into Person(username,password,age,registerDate,address) values (@username,@password,@age,@registerDate,@address)", persion, tranSqlServer); MessageBox.Show("添加成功!"); tranMySql.Commit(); tranSqlServer.Commit(); } catch (Exception ex) { tranMySql.Rollback(); tranSqlServer.Rollback(); throw ex; } finally { if (mySqlConn.State == System.Data.ConnectionState.Open) { mySqlConn.Close(); } if (SqlServerConn.State == System.Data.ConnectionState.Open) { SqlServerConn.Close(); } tranSqlServer.Dispose(); tranMySql.Dispose(); } tabControl.SelectedTab = tabPage2; //加载信息 LoadDBInfoToDoubleDgvFirst(); } |
通过上面的代码演示主要常用的是:封装后 Query 方法、Excute 方法。(其底层的封装的方法可以见源码:513 from:https://blog.csdn.net/laokang426/article/details/77885137
View Details项目右键 Manage NuGet Packages for Solution 搜索Dapper -> Install
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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Data.SqlClient; using System.Linq; namespace Dapper.Repository { public class DapperDemo { public static string ConnectionString { get { string _connectionString = ConfigurationManager.ConnectionStrings["DefaultConnection"].ToString(); return _connectionString; } } public SqlConnection OpenConnection() { SqlConnection connection = new SqlConnection(ConnectionString); connection.Open(); return connection; } /// <summary> /// 添加 /// </summary> /// <returns></returns> public bool Add() { int row = 0; ED_Data model = new ED_Data(); model.TableName = "123"; model.DataKey = "123"; model.FieldName = "123"; model.Value = "123"; model.Reference = "123"; model.Branch = 1; model.InActive = false; model.Updated = DateTime.Now; string query = "INSERT INTO ED_Data(TableName,DataKey,FieldName,Value,Reference,Branch,InActive,Updated) VALUES (@TableName,@DataKey,@FieldName,@Value,@Reference,@Branch,@InActive,@Updated)"; using (IDbConnection conn = OpenConnection()) { row = conn.Execute(query, model); } if (row > 0) return true; else return false; } /// <summary> /// 修改 /// </summary> /// <returns></returns> public int Update() { int row = 0; ED_Data model = new ED_Data(); model.TableName = "123"; model.DataKey = "123"; model.Updated = DateTime.Now; using (IDbConnection conn = OpenConnection()) { const string query = "UPDATE ED_Data SET DataKey=@DataKey,Updated=@Updated WHERE TableName=@TableName"; row = conn.Execute(query, model); } return row; } /// <summary> /// 删除 /// </summary> /// <returns></returns> public int Delete() { int row = 0; ED_Data model = new ED_Data(); model.TableName = "123"; using (IDbConnection conn = OpenConnection()) { const string query = "DELETE FROM ED_Data WHERE TableName=@TableName"; row = conn.Execute(query, model); } return row; } /// <summary> /// 查询一条数据 /// </summary> /// <param name="columnCatId"></param> /// <returns>ED_Data</returns> public ED_Data GetModel(string TableName) { using (IDbConnection conn = OpenConnection()) { const string query = "SELECT * FROM ED_Data WHERE TableName = @TableName"; return conn.Query<ED_Data>(query, new { tableName = TableName }).SingleOrDefault<ED_Data>(); } } /// <summary> /// 查询list集合 /// </summary> /// <returns>List</returns> public List<ED_Data> GetED_DataList() { using (IDbConnection conn = OpenConnection()) { const string query = "SELECT * FROM ED_Data"; return conn.Query<ED_Data>(query, null).ToList(); } } /// <summary> /// 事务处理 /// 删除 /// </summary> /// <param name="cat"></param> /// <returns></returns> public int DeleteColumnCatAndColumn(ED_Data cat) { try { using (IDbConnection conn = OpenConnection()) { string delete1 = "DELETE FROM ED_Data WHERE TableName=@TableName"; string delete2 = "DELETE FROM ED_Data WHERE TableName=@TableName"; IDbTransaction transaction = conn.BeginTransaction(); int row = conn.Execute(delete1, new { TableName = cat.TableName }, transaction, null, null); row += conn.Execute(delete2, new { TableName = cat.TableName }, transaction, null, null); transaction.Commit(); return row; } } catch (Exception) { throw; } } /// <summary> /// 执行存储过程 /// </summary> public void ExecuteStoredProcedure() { try { DynamicParameters para = new DynamicParameters(); para.Add("@param1", 1); para.Add("@param2", 2); using (IDbConnection conn = OpenConnection()) { int row = conn.Execute("存储过程名称", para, null, null, CommandType.StoredProcedure); } } catch (Exception) { throw; } } /// <summary> /// 批量添加 /// </summary> public void InsertBatch() { try { string sqlStr = "INSERT INTO ED_Data(TableName,DataKey,FieldName,Value,Reference,Branch,InActive,Updated) VALUES (@TableName,@DataKey,@FieldName,@Value,@Reference,@Branch,@InActive,@Updated)"; using (IDbConnection conn = OpenConnection()) { conn.Execute(sqlStr, new[] { new { TableName = "user1", DataKey = "a", FieldName = "name", Value = "000001", Reference = "", Branch = "", InActive = "", Updated = DateTime.Now }, new { TableName = "user2", DataKey = "b", FieldName = "age", Value = "000002", Reference = "", Branch= "", InActive = "", Updated = DateTime.Now }, new { TableName = "user3", DataKey = "c", FieldName = "phone", Value = "000003", Reference= "", Branch= "", InActive= "", Updated= DateTime.Now }, }, null, null, null); } } catch (Exception) { throw; } } } } |
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 |
using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Data.SqlClient; using System.Linq; namespace Dapper.Repository { public class ED_Data { public string TableName { get; set; } public string DataKey { get; set; } public string FieldName { get; set; } public string Value { get; set; } public string Reference { get; set; } public int Branch { get; set; } public bool InActive { get; set; } public DateTime Updated { get; set; } } } |
Base基类
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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 |
using Dapper.CoreLibrary; using Dapper.Entity; using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Reflection; using System.Text; using System.Transactions; namespace Dapper.Repository { public abstract class RepositoryBase<T> : DbConnectionFactory, IRepositoryBase<T> where T : IEntityBase<T> { public RepositoryBase(IDbConnection db) : base(db) { } /// <summary> /// /// </summary> /// <param name="model"></param> /// <returns></returns> public virtual int Add(T model) { int result = 0; try { var ps = model.GetType().GetProperties(); List<string> @colms = new List<string>(); List<string> @params = new List<string>(); foreach (var p in ps) { if (!p.CustomAttributes.Any(x => x.AttributeType == typeof(PrimaryKeyAttribute)) && !p.CustomAttributes.Any(x => x.AttributeType == typeof(DBIgnoreAttribute))) { @colms.Add(string.Format("[{0}]", p.Name)); @params.Add(string.Format("@{0}", p.Name)); } } var sql = string.Format("INSERT INTO [{0}] ({1}) VALUES({2}); SELECT @@IDENTITY;", typeof(T).Name, string.Join(", ", @colms), string.Join(", ", @params)); result = _conn.ExecuteScalar<int>(sql, model); } catch (Exception ex) { throw; } return result; } /// <summary> /// /// </summary> /// <param name="listModel"></param> public virtual void Add(List<T> listModel) { try { using (var scope = new TransactionScope()) { listModel.ForEach(model => { var ps = model.GetType().GetProperties(); List<string> @colms = new List<string>(); List<string> @params = new List<string>(); foreach (var p in ps) { if (!p.CustomAttributes.Any(x => x.AttributeType == typeof(PrimaryKeyAttribute)) && !p.CustomAttributes.Any(x => x.AttributeType == typeof(DBIgnoreAttribute))) { @colms.Add(string.Format("[{0}]", p.Name)); @params.Add(string.Format("@{0}", p.Name)); } } var sql = string.Format("INSERT INTO [{0}] ({1}) VALUES({2}); SELECT @@IDENTITY;", typeof(T).Name, string.Join(", ", @colms), string.Join(", ", @params)); _conn.ExecuteScalar<int>(sql, model); }); scope.Complete(); } } catch (Exception ex) { } } /// <summary> /// /// </summary> /// <param name="model"></param> /// <returns></returns> public virtual int AddWithGuid(T model) { int result = 0; try { var ps = model.GetType().GetProperties(); List<string> @colms = new List<string>(); List<string> @params = new List<string>(); foreach (var p in ps) { if (!p.CustomAttributes.Any(x => x.AttributeType == typeof(DBIgnoreAttribute))) { @colms.Add(string.Format("[{0}]", p.Name)); @params.Add(string.Format("@{0}", p.Name)); } } var sql = string.Format("INSERT INTO [{0}] ({1}) VALUES({2});", typeof(T).Name, string.Join(", ", @colms), string.Join(", ", @params)); result = _conn.Execute(sql, model); } catch (Exception ex) { throw; } return result; } /// <summary> /// /// </summary> /// <param name="model"></param> public virtual void Update(T model) { PropertyInfo pkInfo = null; var ps = model.GetType().GetProperties(); List<string> @params = new List<string>(); foreach (var p in ps) { if (p.CustomAttributes.Any(x => x.AttributeType == typeof(DBIgnoreAttribute))) { continue; } if (p.CustomAttributes.Any(x => x.AttributeType == typeof(PrimaryKeyAttribute))) { pkInfo = p; } else { @params.Add(string.Format("[{0}]=@{0}", p.Name)); } } var sql = string.Format("UPDATE [{0}] SET {1} WHERE [{2}] = @{2}", typeof(T).Name, string.Join(", ", @params), pkInfo.Name); _conn.Execute(sql, model); } /// <summary> /// /// </summary> /// <param name="listModel"></param> public virtual void Update(List<T> listModel) { using (var scope = new TransactionScope()) { listModel.ForEach(model => { PropertyInfo pkInfo = null; var ps = model.GetType().GetProperties(); List<string> @params = new List<string>(); foreach (var p in ps) { if (p.CustomAttributes.Any(x => x.AttributeType == typeof(DBIgnoreAttribute))) { continue; } if (p.CustomAttributes.Any(x => x.AttributeType == typeof(PrimaryKeyAttribute))) { pkInfo = p; } else { @params.Add(string.Format("[{0}] = @{0}", p.Name)); } } var sql = string.Format("UPDATE [{0}] SET {1} WHERE [{2}] = @{2}", typeof(T).Name, string.Join(", ", @params), pkInfo.Name); _conn.Execute(sql, model); }); scope.Complete(); } } /// <summary> /// /// </summary> /// <param name="primaryValue">主键ID</param> /// <param name="tableName">表名</param> /// <returns></returns> public virtual T GetModel(string primaryValue, string tableName = "") { try { string primaryWhere = string.Empty; var ps = typeof(T).GetProperties(); if (string.IsNullOrEmpty(tableName)) { tableName = typeof(T).Name; } var primary = ps.Single(p => p.CustomAttributes.FirstOrDefault(c => c.AttributeType == typeof(PrimaryKeyAttribute)) != null); primaryWhere = (string.Format("[{0}] = @primarykey", primary.Name)); var sql = string.Format("SELECT * FROM [{0}] WHERE {1}", tableName, primaryWhere); return _conn.Query<T>(sql, new { primarykey = primaryValue }).FirstOrDefault(); } catch (Exception) { throw; } } /// <summary> /// /// </summary> /// <param name="strWhere">where条件</param> /// <param name="tableName">表名</param> /// <returns></returns> public virtual T GetModelQuery(string strWhere, string tableName = "") { try { var sql = string.Format("SELECT * FROM [{0}] WHERE {1}", tableName, strWhere); return _conn.Query<T>(sql, new { where = strWhere }).FirstOrDefault(); } catch (Exception) { throw; } } /// <summary> /// 根据主键删除 /// </summary> /// <param name="primaryValue"></param> public virtual void Delete(string primaryValue) { try { string primaryWhere = string.Empty; var ps = typeof(T).GetProperties(); var primary = ps.Single(p => p.CustomAttributes.FirstOrDefault(c => c.AttributeType == typeof(PrimaryKeyAttribute)) != null); var sql = string.Format("DELETE FROM [{0}] WHERE {1} = @primarykey", typeof(T).Name, primary.Name); _conn.Execute(sql, new { primarykey = primaryValue }); } catch (Exception) { throw; } } /// <summary> /// /// </summary> /// <param name="strWhere"></param> public void DeleteStrWhere(string strWhere) { try { var sql = string.Format("DELETE FROM [{0}] WHERE {1}", typeof(T).Name, strWhere); _conn.Execute(sql); } catch (Exception) { throw; } } /// <summary> /// /// </summary> /// <param name="strWhere"></param> /// <param name="tableName"></param> /// <returns></returns> public virtual List<T> GetList(string strWhere, string tableName = "") { try { if (string.IsNullOrEmpty(tableName)) tableName = typeof(T).Name; var sql = string.Format("SELECT * FROM [{0}] " + (strWhere == "" ? "" : " WHERE " + " {1} "), tableName, strWhere); return _conn.Query<T>(sql).ToList(); } catch (Exception) { throw; } } /// <summary> /// /// </summary> /// <param name="param"></param> /// <returns></returns> public virtual PagerListResult<List<T>> GetPageList(PagerRequestParam param) { PagerListResult<List<T>> result = null; List<T> list = new List<T>(); int pageTotal = 1; int recordTotal = 0; int startIndex = 1; int endIndex = param.PageSize; try { if (param.PageIndex - 1 > 0) { startIndex = (param.PageIndex - 1 <= 0 ? 1 : param.PageIndex - 1) * param.PageSize + 1; endIndex = param.PageIndex * param.PageSize; } if (string.IsNullOrEmpty(param.TableName)) param.TableName = typeof(T).Name; StringBuilder strSql = new StringBuilder(); strSql.Append("SELECT * FROM ( "); strSql.Append(" SELECT ROW_NUMBER() OVER ("); if (!string.IsNullOrEmpty(param.OrderBy)) { strSql.Append("ORDER BY T." + param.OrderBy); } else { strSql.Append("ORDER BY T.ID DESC"); } strSql.Append(")AS Row, T.* FROM " + param.TableName + " T "); if (!string.IsNullOrEmpty(param.StrWhere)) { strSql.Append(" WHERE " + param.StrWhere); } strSql.Append(" ) TT"); strSql.AppendFormat(" WHERE TT.Row BETWEEN {0} AND {1}", startIndex, endIndex); list = _conn.Query<T>(strSql.ToString(), param.StrWhere).ToList(); if (list.Count > 0) { recordTotal = this.GetRecordCount(param.StrWhere, param.TableName); pageTotal = PagerRequestParam.Tool.PageTotal(param); } result = new PagerListResult<List<T>>(list, pageTotal, recordTotal); } catch (Exception ex) { result = new PagerListResult<List<T>>(ex); } return result; } /// <summary> /// 事务处理 /// Demo /// </summary> /// <returns></returns> public int DeleteTransaction() { try { const string delete1 = "DELETE FROM ED_Data WHERE TableName=@TableName"; const string delete2 = "DELETE FROM ED_Data WHERE TableName=@TableName"; IDbTransaction transaction = _conn.BeginTransaction(); int row = _conn.Execute(delete1, new { TableName = "user" }, transaction, null, null); row += _conn.Execute(delete2, new { TableName = "customer" }, transaction, null, null); transaction.Commit(); return row; } catch (Exception) { throw; } } /// <summary> /// 获取记录数 /// </summary> /// <param name="strWhere">Where条件</param> /// <returns></returns> public virtual int GetRecordCount(string strWhere, string tableName = "") { int count = 0; try { if (string.IsNullOrEmpty(tableName)) tableName = typeof(T).Name; StringBuilder strSql = new StringBuilder(); strSql.Append("SELECT COUNT(1) FROM " + tableName); if (!string.IsNullOrEmpty(strWhere)) { strSql.Append(" WHERE " + strWhere); } count = _conn.ExecuteScalar<int>(strSql.ToString()); } catch (Exception) { throw; } return count; } } } |
from:https://blog.csdn.net/kingcruel/article/details/52848950
View DetailsCQRS架构简介 前不久,看到博客园一位园友写了一篇文章,其中的观点是,要想高性能,需要尽量:避开网络开销(IO),避开海量数据,避开资源争夺。对于这3点,我觉得很有道理。所以也想谈一下,CQRS架构下是如何实现高性能的。 关于CQRS(Command Query Responsibility Segration)架构,大家应该不会陌生了。简单的说,就是一个系统,从架构上把它拆分为两部分:命令处理(写请求)+查询处理(读请求)。然后读写两边可以用不同的架构实现,以实现CQ两端(即Command Side,简称C端;Query Side,简称Q端)的分别优化。CQRS作为一个读写分离思想的架构,在数据存储方面,没有做过多的约束。所以,我觉得CQRS可以有不同层次的实现,比如: CQ两端数据库共享,CQ两端只是在上层代码上分离;这种做法,带来的好处是可以让我们的代码读写分离,更好维护,且没有CQ两端的数据一致性问题,因为是共享一个数据库的。我个人认为,这种架构很实用,既兼顾了数据的强一致性,又能让代码好维护。 CQ两端数据库和上层代码都分离,然后Q的数据由C端同步过来,一般是通过Domain Event进行同步。同步方式有两种,同步或异步,如果需要CQ两端的强一致性,则需要用同步;如果能接受CQ两端数据的最终一致性,则可以使用异步。采用这种方式的架构,个人觉得,C端应该采用Event Sourcing(简称ES)模式才有意义,否则就是自己给自己找麻烦。因为这样做你会发现会出现冗余数据,同样的数据,在C端的db中有,而在Q端的db中也有。和上面第一种做法相比,我想不到什么好处。而采用ES,则所有C端的最新数据全部用Domain Event表达即可;而要查询显示用的数据,则从Q端的ReadDB(关系型数据库)查询即可。 我觉得要实现高性能,可以谈的东西还有很多。下面我想重点说说我想到的一些设计思路: 避开资源争夺 秒杀活动的例子分析 我觉得这是很重要的一点。什么是资源争夺?我想就是多个线程同时修改同一个数据。就像阿里秒杀活动一样,秒杀开抢时,很多人同时抢一个商品,导致商品的库存会被并发更新减库存,这就是一个资源争夺的例子。一般如果资源竞争不激烈,那无所谓,不会影响性能;但是如果像秒杀这种场景,那db就会抗不住了。在秒杀这种场景下,大量线程需要同时更新同一条记录,进而导致MySQL内部大量线程堆积,对服务性能、稳定性造成很大伤害。那怎么办呢?我记得阿里的丁奇写过一个分享,思路就是当MySQL的服务端多个线程同时修改一条记录时,可以对这些修改请求进行排队,然后对于InnoDB引擎层,就是串行的。这样排队后,不管上层应用发过来多少并行的修改同一行的请求,对于MySQL Server端来说,内部总是会聪明的对同一行的修改请求都排队处理;这样就能确保不会有并发产生,从而不会导致线程浪费堆积,导致数据库性能下降。这个方案可以见下图所示: 如上图所示,当很多请求都要修改A记录时,MySQL Server内部会对这些请求进行排队,然后一个个将对A的修改请求提交到InnoDB引擎层。这样看似在排队,实际上会确保MySQL Server不会死掉,可以保证对外提供稳定的TPS。 但是,对于商品秒杀这个场景,还有优化的空间,就是Group Commit技术。Group Commit就是对多个请求合并为一次操作进行处理。秒杀时,大家都在购买这个商品,A买2件,B买3件,C买1件;其实我们可以把A,B,C的这三个请求合并为一次减库存操作,就是一次性减6件。这样,对于A,B,C的这三个请求,在InnoDB层我们只需要做一次减库存操作即可。假设我们Group Commit的每一批的size是50,那就是可以将50个减操作合并为一次减操作,然后提交到InnoDB。这样,将大大提高秒杀场景下,商品减库存的TPS。但是这个Group Commit的每批大小不是越大越好,而是要根据并发量以及服务器的实际情况做测试来得到一个最优的值。通过Group Commit技术,根据丁奇的PPT,商品减库存的TPS性能从原来的1.5W提高到了8.5W。 从上面这个例子,我们可以看到阿里是如何在实际场景中,通过优化MySQL Server来实现高并发的商品减库存的。但是,这个技术一般人还真的不会!因为没多少人有能力去优化MySQL的服务端,排队也不行,更别说Group Commit了。这个功能并不是MySQL Server自带的,而是需要自己实现的。但是,这个思路我想我们都可以借鉴。 CQRS如何实现避免资源竞争 那么对于CQRS架构,如何按照这个思路来设计呢?我想重点说一下我上面提到的第二种CQRS架构。对于C端,我们的目标是尽可能的在1s内处理更多的Command,也就是数据写请求。在经典DDD的四层架构中,我们会有一个模式叫工作单元模式,即Unit of Work(简称UoW)模式。通过该模式,我们能在应用层,一次性以事务的方式将当前请求所涉及的多个对象的修改提交到DB。微软的EF实体框架的DbContext就是一个UoW模式的实现。这种做法的好处是,一个请求对多个聚合根的修改,能做到强一致性,因为是事务的。但是这种做法,实际上,没有很好的遵守避开资源竞争的原则。试想,事务A要修改a1,a2,a3三个聚合根;事务B要修改a2,a3,a4;事务C要修改a3,a4,a5三个聚合根。那这样,我们很容易理解,这三个事务只能串行执行,因为它们要修改相同的资源。比如事务A和事务B都要修改a2,a3这两个聚合根,那同一时刻,只能由一个事务能被执行。同理,事务B和事务C也是一样。如果A,B,C这种事务执行的并发很高,那数据库就会出现严重的并发冲突,甚至死锁。那要如何避免这种资源竞争呢?我觉得我们可以采取三个措施: 让一个Command总是只修改一个聚合根 这个做法其实就是缩小事务的范围,确保一个事务一次只涉及一条记录的修改。也就是做到,只有单个聚合根的修改才是事务的,让聚合根成为数据强一致性的最小单位。这样我们就能最大化的实现并行修改。但是你会问,但是我一个请求就是会涉及多个聚合根的修改的,这种情况怎么办呢?在CQRS架构中,有一个东西叫Saga。Saga是一种基于事件驱动的思想来实现业务流程的技术,通过Saga,我们可以用最终一致性的方式最终实现对多个聚合根的修改。对于一次涉及多个聚合根修改的业务场景,一般总是可以设计为一个业务流程,也就是可以定义出要先做什么后做什么。比如以银行转账的场景为例子,如果是按照传统事务的做法,那可能是先开启一个事务,然后让A账号扣减余额,再让B账号加上余额,最后提交事务;如果A账号余额不足,则直接抛出异常,同理B账号如果加上余额也遇到异常,那也抛出异常即可,事务会保证原子性以及自动回滚。也就是说,数据一致性已经由DB帮我们做掉了。 但是,如果是Saga的设计,那就不是这样了。我们会把整个转账过程定义为一个业务流程。然后,流程中会包括多个参与该流程的聚合根以及一个用于协调聚合根交互的流程管理器(ProcessManager,无状态),流程管理器负责响应流程中的每个聚合根产生的领域事件,然后根据事件发送相应的Command,从而继续驱动其他的聚合根进行操作。 转账的例子,涉及到的聚合根有:两个银行账号聚合根,一个交易(Transaction)聚合根,它用于负责存储流程的当前状态,它还会维护流程状态变更时的规则约束;然后当然还有一个流程管理器。转账开始时,我们会先创建一个Transaction聚合根,然后它产生一个TransactionStarted的事件,然后流程管理器响应事件,然后发送一个Command让A账号聚合根做减余额的操作;A账号操作完成后,产生领域事件;然后流程管理器响应事件,然后发送一个Command通知Transaction聚合根确认A账号的操作;确认完成后也会产生事件,然后流程管理器再响应,然后发送一个Command通知B账号做加上余额的操作;后续的步骤就不详细讲了。大概意思我想已经表达了。总之,通过这样的设计,我们可以通过事件驱动的方式,来完成整个业务流程。如果流程中的任何一步出现了异常,那我们可以在流程中定义补偿机制实现回退操作。或者不回退也没关系,因为Transaction聚合根记录了流程的当前状态,这样我们可以很方便的后续排查有状态没有正常结束的转账交易。具体的设计和代码,有兴趣的可以去看一下ENode源代码中的银行转账的例子,里面有完整的实现。 对修改同一个聚合根的Command进行排队 和上面秒杀的设计一样,我们可以对要同时修改同一个聚合根的Command进行排队。只不过这里的排队不是在MySQL Server端,而是在我们自己程序里做这个排队。如果我们是单台服务器处理所有的Command,那排队很容易做。就是只要在内存中,当要处理某个Command时,判断当前Command要修改的聚合根是否前面已经有Command在处理,如果有,则排队;如果没有,则直接执行。然后当这个聚合根的前一个Command执行完后,我们就能处理该聚合根的下一个Command了;但是如果是集群的情况下呢,也就是你不止有一台服务器在处理Command,而是有十台,那要怎么办呢?因为同一时刻,完全有可能有两个不同的Command在修改同一个聚合根。这个问题也简单,就是我们可以对要修改聚合根的Command根据聚合根的ID进行路由,根据聚合根的ID的hashcode,然后和当前处理Command的服务器数目取模,就能确定当前Command要被路由到哪个服务器上处理了。这样我们能确保在服务器数目不变的情况下,针对同一个聚合根实例修改的所有Command都是被路由到同一台服务器处理。然后加上我们前面在单个服务器里面内部做的排队设计,就能最终保证,对同一个聚合根的修改,同一时刻只有一个线程在进行。 通过上面这两个设计,我们可以确保C端所有的Command,都不会出现并发冲突。但是也要付出代价,那就是要接受最终一致性。比如Saga的思想,就是在最终一致性的基础上而实现的一种设计。然后,基于以上两点的这种架构的设计,我觉得最关键的是要做到:1)分布式消息队列的可靠,不能丢消息,否则Saga流程就断了;2)消息队列要高性能,支持高吞吐量;这样才能在高并发时,实现整个系统的整体的高性能。我开发的EQueue就是为了这个目标而设计的一个分布式消息队列,有兴趣的朋友可以去了解下哦。 Command和Event的幂等处理 CQRS架构是基于消息驱动的,所以我们要尽量避免消息的重复消费。否则,可能会导致某个消息被重复消费而导致最终数据无法一致。对于CQRS架构,我觉得主要考虑三个环节的消息幂等处理。 Command的幂等处理 这一点,我想不难理解。比如转账的例子中,假如A账号扣减余额的命令被重复执行了,那会导致A账号扣了两次钱。那最后就数据无法一致了。所以,我们要保证Command不能被重复执行。那怎么保证呢?想想我们平时一些判断重复的操作怎么做的?一般有两个做法:1)db对某一列建唯一索引,这样可以严格保证某一列数据的值不会重复;2)通过程序保证,比如插入前先通过select查询判断是否存在,如果不存在,则insert,否则就认为重复;显然通过第二种设计,在并发的情况下,是不能保证绝对的唯一性的。然后CQRS架构,我认为我们可以通过持久化Command的方式,然后把CommandId作为主键,确保Command不会重复。那我们是否要每次执行Command前线判断该Command是否存在呢?不用。因为出现Command重复的概率很低,一般只有是在我们服务器机器数量变动时才会出现。比如增加了一台服务器后,会影响到Command的路由,从而最终会导致某个Command会被重复处理,关于这里的细节,我这里不想多展开了,呵呵。有问题到回复里讨论吧。这个问题,我们也可以最大程度上避免,比如我们可以在某一天系统最空的时候预先增加好服务器,这样可以把出现重复消费消息的情况降至最低。自然也就最大化的避免了Command的重复执行。所以,基于这个原因,我们没有必要在每次执行一个Command时先判断该Command是否已执行。而是只要在Command执行完之后,直接持久化该Command即可,然后因为db中以CommandId为主键,所以如果出现重复,会主键重复的异常。我们只要捕获该异常,然后就知道了该Command已经存在,这就说明该Command之前已经被处理过了,那我们只要忽略该Command即可(当然实际上不能直接忽略,这里我由于篇幅问题,我就不详细展开了,具体我们可以再讨论)。然后,如果持久化没有问题,说明该Command之前没有被执行过,那就OK了。这里,还有个问题也不能忽视,就是某个Command第一次执行完成了,也持久化成功了,但是它由于某种原因没有从消息队列中删除。所以,当它下次再被执行时,Command Handler里可能会报异常,所以,健壮的做法时,我们要捕获这个异常。当出现异常时,我们要检查该Command是否之前已执行过,如果有,就要认为当前Command执行正确,然后要把之前Command产生的事件拿出来做后续的处理。这个问题有点深入了,我暂时不细化了。有兴趣的可以找我私聊。 Event持久化的幂等处理 然后,因为我们的架构是基于ES的,所以,针对新增或修改聚合根的Command,总是会产生相应的领域事件(Domain Event)。我们接下来的要做的事情就是要先持久化事件,再分发这些事件给所有的外部事件订阅者。大家知道,聚合根有生命周期,在它的生命周期里,会经历各种事件,而事件的发生总有确定的时间顺序。所以,为了明确哪个事件先发生,哪个事件后发生,我们可以对每个事件设置一个版本号,即version。聚合根第一个产生的事件的version为1,第二个为2,以此类推。然后聚合根本身也有一个版本号,用于记录当前自己的版本是什么,它每次产生下一个事件时,也能根据自己的版本号推导出下一个要产生的事件的版本号是什么。比如聚合根当前的版本号为5,那下一个事件的版本号则为6。通过为每个事件设计一个版本号,我们就能很方便的实现聚合根产生事件时的并发控制了,因为一个聚合根不可能产生两个版本号一样的事件,如果出现这种情况,那说明一定是出现并发冲突了。也就是一定是出现了同一个聚合根同时被两个Command修改的情况了。所以,要实现事件持久化的幂等处理,也很好做了,就是db中的事件表,对聚合根ID+聚合根当前的version建唯一索引。这样就能在db层面,确保Event持久化的幂等处理。另外,对于事件的持久化,我们也可以像秒杀那样,实现Group Commit。就是Command产生的事件不用立马持久化,而是可以先积累到一定的量,比如50个,然后再一次性Group Commit所有的事件。然后事件持久化完成后,再修改每个聚合根的状态即可。如果Group Commit事件时遇到并发冲突(由于某个聚合根的事件的版本号有重复),则退回为单个一个个持久化事件即可。为什么可以放心的这样做?因为我们已经基本做到确保一个聚合根同一时刻只会被一个Command修改。这样就能基本保证,这些Group Commit的事件也不会出现版本号冲突的情况。所以,大家是否觉得,很多设计其实是一环套一环的。Group Commit何时出发?我觉得可以只要满足两个条件了就可以触发:1)某个定时的周期到了就可以触发,这个定时周期可以根据自己的业务场景进行配置,比如每隔50ms触发一次;2)要Commit的事件到达某个最大值,即每批可以持久化的事件个数的最大值,比如每50个事件为一批,这个BatchSize也需要根据实际业务场景和你的存储db的性能综合测试评估来得到一个最适合的值;何时可以使用Group Commit?我觉得只有是在并发非常高,当单个持久化事件遇到性能瓶颈时,才需要使用。否则反而会降低事件持久化的实时性,Group Commit提高的是高并发下单位时间内持久化的事件数。目的是为了降低应用和DB之间交互的次数,从而减少IO的次数。不知不觉就说到了最开始说的那3点性能优化中的,尽量减少IO了,呵呵。 Event消费时的幂等处理 CQRS架构图中,事件持久化完成后,接下来就是会把这些事件发布出去(发送到分布式消息队列),给消费者消费了,也就是给所有的Event Handler处理。这些Event Handler可能是更新Q端的ReadDB,也可能是发送邮件,也可能是调用外部系统的接口。作为框架,应该有职责尽量保证一个事件尽量不要被某个Event Handler重复消费,否则,就需要Event Handler自己保证了。这里的幂等处理,我能想到的办法就是用一张表,存储某个事件是否被某个Event Handler处理的信息。每次调用Event Handler之前,判断该Event Handler是否已处理过,如果没处理过,就处理,处理完后,插入一条记录到这个表。这个方法相信大家也都很容易想到。如果框架不做这个事情,那Event Handler内部就要自己做好幂等处理。这个思路就是select if not exist, then handle, and at last insert的过程。可以看到这个过程不像前面那两个过程那样很严谨,因为在并发的情况下,理论上还是会出现重复执行Event Handler的情况。或者即便不是并发时也可能会造成,那就是假如event handler执行成功了,但是last insert失败了,那框架还是会重试执行event handler。这里,你会很容易想到,为了做这个幂等支持,Event Handler的一次完整执行,需要增加不少时间,从而会最后导致Query Side的数据更新的延迟。不过CQRS架构的思想就是Q端的数据由C端通过事件同步过来,所以Q端的更新本身就是有一定的延迟的。这也是CQRS架构所说的要接收最终一致性的原因。 关于幂等处理的性能问题的思考 关于CommandStore的性能瓶颈分析 大家知道,整个CQRS架构中,Command,Event的产生以及处理是非常频繁的,数据量也是非常大的。那如何保证这几步幂等处理的高性能呢?对于Command的幂等处理,如果对性能要求不是很高,那我们可以简单使用关系型DB即可,比如Sql Server, MySQL都可以。要实现幂等处理,只需要把主键设计为CommandId即可。其他不需要额外的唯一索引。所以这里的性能瓶颈相当于是对单表做大量insert操作的最大TPS。一般MySQL数据库,SSD硬盘,要达到2W […]
View DetailsDapper高级玩法1: 数据库中带下划线的表字段自动匹配无下划线的Model字段。 Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true; 备注: 这个对使用Mysql数据库的朋友最有帮助,因为Mysql默认都是小写,一般字段都带下划线,比如:user_name之类。 具体效果如下演示 1,首先创建一张表并插入数据 2,创建Model模型
1 2 3 4 5 6 7 8 |
public class User { public int UserID { get; set; } public string UserName { get; set; } public int RoleID { get; set; } } |
3,扩写抽取数据逻辑代码. select * from [user]
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 |
static Program() { var config = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); var data = config.Build(); DapperExtension.DBConnectionString = data.GetConnectionString("DefaultConnection"); } static void Main(string[] args) { IDbConnection dbconnection = null; using (dbconnection = dbconnection.OpenConnection()) { var users = dbconnection.List("select * from [user]", null); foreach (var user in users) { Console.WriteLine($"{user.UserID}-{user.UserName}-{user.RoleID}"); } } Console.ReadKey(); } |
4,无MatchNamesWithUnderscores设置时的数据抽取 没有绑定成功?? 这是因为用了Select * from的缘故,取出来的字段是带下划线的与Model的字段不匹配。 5,设置MatchNamesWithUnderscores再次数据抽取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
static void Main(string[] args) { Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true; IDbConnection dbconnection = null; using (dbconnection = dbconnection.OpenConnection()) { var users = dbconnection.List("select * from [user]", null); foreach (var user in users) { Console.WriteLine($"{user.UserID}-{user.UserName}-{user.RoleID}"); } } Console.ReadKey(); } |
数据绑定成功。 就一句Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true,让我们少写了不少AS语句。 Dapper高级玩法2: 法力无边的Query,由于带有Function功能,可以自由设置模型绑定逻辑。 1,创建两张有关联的表,并填入数据。 2,抽取user和它关联的role数据。 select 1 as table1,T1.*,1 as table2,T2.* from [user] T1 inner join [role] T2 on T1.role_id = T2.role_id 扩展方法:
1 2 3 4 |
public static IEnumerable QueryT(this IDbConnection dbconnection, string sql, Func map, object param = null, IDbTransaction transaction = null, string splitOn = "Id") { return dbconnection.Query(sql, map, param, transaction, splitOn: splitOn); } |
使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
static void QueryTest() { Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true; IDbConnection dbconnection = null; using (dbconnection = dbconnection.OpenConnection()) { var result = dbconnection.QueryT( @"select 1 as table1,T1.*,1 as table2,T2.* from [user] T1 inner join [role] T2 on T1.role_id = T2.role_id", (user, role) => { user.Role = role; return user; }, null, splitOn: "table1,table2"); foreach (var user in result) { Console.WriteLine($"{user.UserID}-{user.UserName}-{user.Role.RoleID}-{user.Role.RoleName}"); } } Console.ReadKey(); } |
成功取到数据。 splitOn解释:模型绑定时的字段分割标志。table1到table2之间的表字段绑定到User,table2之后的表字段绑定到Role。 3,特殊Function逻辑。比如抽取role_id对应的user一览。 select 1 as table1,T1.*,1 as table2,T2.* from [role] T1 left join [user] T2 on T1.role_id = T2.role_id 外部定义了一个字典类型,Query内部模型绑定的时候每次调用Function函数,Function函数中将数据添加到外部字典中,这在复杂数据处理时很有用。
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 |
static void QueryTest2() { Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true; IDbConnection dbconnection = null; using (dbconnection = dbconnection.OpenConnection()) { Dictionary> dic = new Dictionary>(); dbconnection.QueryT( @"select 1 as table1,T1.*,1 as table2,T2.* from [role] T1 left join [user] T2 on T1.role_id = T2.role_id", (role, user) => { if (dic.ContainsKey(role.RoleID)) { dic[role.RoleID].Add(user); } else { dic.Add(role.RoleID, new List { user }); } return true; }, null, splitOn: "table1,table2"); foreach (var data in dic) { Console.WriteLine($"role:{data.Key}"); foreach (var user in data.Value) { Console.WriteLine($"user:{user.UserID}-{user.UserName}"); } } } Console.ReadKey(); } |
[…]
View Details我们都知道ORM全称叫做Object Relationship Mapper,也就是可以用object来map我们的db,而且市面上的orm框架有很多,其中有一个框架 叫做dapper,而且被称为the king of ORM。 一:为什么选择Dapper 1. 性能优越: 其实在各大网站上,我们大概都会看到这样的一个对比效果图,在超过500次poco serialization的过程中所表现的性能,我们发现dapper是第二名, 当然第一名谁也无法超越,越底层的当然久越快,同时也就越麻烦。就好像谁能超过“01代码”呢??? 2. 支持多数据库 支持多数据库的本质是因为Dapper是对IDBConnection接口进行了方法扩展,比如你看到的SqlMapper.cs,一旦你这样做了,我们也知道, SqlConnection,MysqlConnection,OracleConnection都是继承于DBConnection,而DBConnection又是实现了IDBConnection的接口,对吧。。。 二:安装Dapper install dapper的方式通常有两种: 1. 通过nuget进行安装 如果你不知道怎么用nuget进行安装,或者不知道install-package是什么,可以在browser上找一下,比如下面这样: 然后我们copy到package console 试试看。 2. 在github上获取源码。 为什么要获取源码,是因为用ilspy调试dapper的源码太费劲了,毕竟现在都是异步编程了,从ilspy中看都是匿名方法很多都无法渗透,废话不多 说,我们只要把Dapper文件夹拉出来然后copy到我们的solution就可以了,如下图: 三:快速CURD操作 其实对数据库的操作莫过于CURD,在进行操作之前我们再配一个Users表。 1. 配置Users表
1 2 3 4 5 6 7 8 9 10 |
CREATE TABLE [dbo].[Users]( [UserID] [int] IDENTITY(1,1) NOT NULL, [UserName] [varchar](50) NULL, [Email] [varchar](100) NULL, [Address] [varchar](100) NULL, CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED ( [UserID] 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. Insert操作 通常来说,有两种insert操作: <1>单条insert操作 这是一个简单的参数化insert,而且还可以塞入匿名类型,对吧,跟原始的SqlParameter相比,是不是简单的多???
1 2 3 4 5 6 7 |
static void Main(string[] args) { IDbConnection connection = new SqlConnection("Data Source=.;Initial Catalog=DataMip;Integrated Security=True;MultipleActiveResultSets=True"); var result = connection.Execute("Insert into Users values (@UserName, @Email, @Address)", new { UserName = "jack", Email = "380234234@qq.com", Address = "上海" }); } |
<2> InsertBulk操作 既然是Bulk操作,那肯定就是批量插入了,我们要做的就是将上面这个 ”匿名对象" 变成 ”匿名对象集合“ 就可以了。。。为了方便操作,这里定义 一个Users类,比如下面这样。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
static void Main(string[] args) { IDbConnection connection = new SqlConnection("Data Source=.;Initial Catalog=DataMip;Integrated Security=True;MultipleActiveResultSets=True"); //var result = connection.Execute("Insert into Users values (@UserName, @Email, @Address)", // new { UserName = "jack", Email = "380234234@qq.com", Address = "上海" }); var usersList = Enumerable.Range(0, 10).Select(i => new Users() { Email = i + "qq.com", Address = "安徽", UserName = i + "jack" }); var result = connection.Execute("Insert into Users values (@UserName, @Email, @Address)",usersList); } |
2. Query操作 其实在Dapper在query上提供的的文章太多了。。。这篇我们就按照最简单的参数化查询就好了。。。比如我要找到username=jack的记录,如下:
1 2 3 4 5 6 7 |
1 static void Main(string[] args) 2 { 3 IDbConnection connection = new SqlConnection("Data Source=.;Initial Catalog=DataMip;Integrated Security=True;MultipleActiveResultSets=True"); 4 5 var query = connection.Query<Users>("select * from Users where UserName=@UserName", new { UserName = "jack" }); 6 7 } |
图上的亮点就在于能够自动化mapper到我们object上面来,这是我们DataReader所不能办到的,对吧~~ 3.update操作 这种操作方式,我们还是使用Execute方法来实现,和insert是一种套路的哦。 4. delete操作 这里我还是采用参数化的形式来删除UserID=10这条记录,方式如下: 最终sql的table展示如下,可以看到已经正确的修改了UserID=11的记录,删除了UserID=10的record。。。。当然Dapper好玩的地方多着呢, 这篇只是一个入门而已。。。希望本篇对大家有帮助~~~
View Details访问量不大的项目我都是用EF写数据库操作,因为EF除了速度上慢以外,但开发效率极快,省略了很多sql写法,并能很方便的调用外键、集合等信息,用EF写项目最爽的事。不过有些项目网站要考虑运行速度,这时不得不用其它的ORM框架,我常用dapper,因为它效果快,而且写sql非常灵活,接下来面写一些方法看看dapper的使用 1、连接语句 var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["SqlDiagnosticsDb"].ConnectionString); 使用dapper不需要考虑conn是否连接,在执行dapper时自行判断 open状态,如果没有打开它会自己打开。 2、insert
1 2 |
string query = "INSERT INTO Book(Name)VALUES(@name)"; conn.Execute(query, book); |
book类中有name属性,就可以这样方便去写,当然也可以写成
1 2 |
string query = "INSERT INTO Book(Name)VALUES(@name)"; conn.Execute(query, new{@name=book.name}); |
3、update
1 2 |
string query = "UPDATE Book SET Name=@name WHERE id =@id"; conn.Execute(query, book); |
4、 delete
1 2 3 |
string query = "DELETE FROM Book WHERE id = @id"; conn.Execute(query, book); conn.Execute(query, new { id = id }); |
5、query
1 2 3 4 5 6 7 |
string query = "SELECT * FROM Book"; //无参数查询,返回列表,带参数查询和之前的参数赋值法相同。 conn.Query<Book>(query).ToList(); //返回单条信息 string query = "SELECT * FROM Book WHERE id = @id"; book = conn.Query<Book>(query, new { id = id }).SingleOrDefault(); |
6、 传统sql in (1,2,3) 用dapper就这样写
1 2 |
conn.Query<Users>("SELECT * FROM Users s WHERE s.id IN (@ids) ",new { ids = new int[]{1,2,3}}) conn.Query<Users>("SELECT * FROM Users s WHERE s.id IN (@ids) ",new { ids = IDs.ToArray()}) |
在dapper因为安全性,不能直接用sql接接 要采用参数化, 7、批量插入
1 |
conn.Execute(@"insert MyTable(colA, colB) values (@a, @b)", new[] { new { a=1, b=1 }, new { a=2, b=2 }, new { a=3, b=3 } }) |
也可以直接写入一个集合
1 |
conn.Execute("insert user(name) values(@name)",users) |
这里users是一个user表的对象集合,可一次把集合中的所有数据插入到数据表中。 8、多表查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//查询图书时,同时查找对应的书评,并存在List中。实现1--n的查询操作 string query = "SELECT * FROM Book b LEFT JOIN BookReview br ON br.BookId = b.Id WHERE b.id = @id"; Book lookup = null; //Query<TFirst, TSecond, TReturn> var b = conn.Query<Book, BookReview, Book>(query, (book, bookReview) => { //扫描第一条记录,判断非空和非重复 if (lookup == null || lookup.Id != book.Id) lookup = book; //书对应的书评非空,加入当前书的书评List中,最后把重复的书去掉。 if (bookReview != null) lookup.Reviews.Add(bookReview); return lookup; }, new { id = id }).Distinct().SingleOrDefault(); return b; |
多表联合查询是比较麻烦一些,到现在不是完全明白,多看几个例子
1 2 |
var sql = @"select * from Posts p join Users u on u.Id = p.OwnerId Order by p.Id"; var data = conn.Query<Post, User, Post>(sql, (post, user) => { post.Owner = user; return post;},splitOn:"id"); |
Post类和User类,它们存在外键, conn.Query返回的类型是最后一个参数Post, 其中Post中有一属性Owner是User对象,在(post, user)=>lamda中指定了Owner值,上边的代码中的splitOn是ID,运行时,会从查询结果所有字段列表的最后一个字段开始进行匹配,一直到找到Id这个字段(大小写忽略),找到的第一个ID字段匹配User类的ID属性,那么从ID到最后一个字段都属于User,ID以前的字段都被影射到Post, 通过 (post, user) => {return post;},把两个类的实例解析出来。 9、三表查询,一个是关联主键表(单个对象),一个是关联外键表(集合)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public partial class UserInfo { public UserInfo() { this.Persion = new HashSet<Persion>(); this.MyTYC = new HashSet<MyTYC>(); } public int id { get; set; } public string name { get; set; } public Nullable<System.DateTime> createTime { get; set; } public Movies Movies { get; set; } public virtual ICollection<MyTYC> MyTYC { get; set; } } |
1 2 3 4 5 6 7 8 9 10 |
public class Movies { public int ID { get; set; } public string Title { get; set; } public string ReleaseDate { get; set; } public string Genre { get; set; } public string Price { get; set; } public UserInfo UserInfo { get; set; } } |
1 2 3 4 5 6 |
public partial class MyTYC { public int id { get; set; } public string name { get; set; } } |
1 2 3 4 |
string sql = @"select * from UserInfo u inner join [Movies].dbo.Movies m on u.id=m.ID inner join MyTYC t on u.id=t.id"; var data = conn.Query<UserInfo, Movies, MyTYC, UserInfo>(sql, (u, m, t) => { u.Movies = m; u.MyTYC.Add(t); return u; }); |
注意这里的对象和集合的获取方法:u.Movies = m; u.MyTYC.Add(t); 10、多结果查询
1 2 3 4 5 6 7 8 9 10 |
var sql = @"select * from Customers where CustomerId = @id; select * from Orders where CustomerId = @id; select * from Returns where CustomerId = @id"; using (var multi = connection.QueryMultiple(sql, new {id=selectedId})) { var customer = multi.Read<Customer>().Single(); var orders = multi.Read<Order>().ToList(); var returns = multi.Read<Return>().ToList(); } |
再来一个
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 |
class Program { //创建连接对象 protected static SqlConnection GetConnection() { var connection = new SqlConnection("Data Source=.;Initial Catalog=TestDB;Integrated Security=True"); connection.Open(); return connection; } static void Main(string[] args) { //测试输出多个结果集 var sql = @"INSERT INTO [dbo].[Student] ([Name]) VALUES ('A1'); select @@IDENTITY as A; INSERT INTO [dbo].[Student] ([Name]) VALUES ('B1'); select @@IDENTITY as A; INSERT INTO [dbo].[Student] ([Name]) VALUES ('C1'); select @@IDENTITY as A"; //初始化数据库连接 using (SqlConnection connection = GetConnection()) { List<int> ilist = new List<int>(); //执行查询,获取结果集集合 var multi = connection.QueryMultiple(sql); //遍历结果集 while(!multi.IsConsumed) { //读取当前结果集 var result = multi.Read().ToList()[0].A; if (result != null) { ilist.Add(Convert.ToInt32(result)); } } //for(int i = 0;i<3;i++) //{ // var result = multi.Read().ToList()[0].A; // if (result != null) // { // ilist.Add(Convert.ToInt32(result)); // } //} foreach (var item in ilist) { Console.WriteLine(item.ToString()); } } Console.ReadLine(); } } |
11、如果某一代码中多次操作数据库,可以把conn设置为打开,最后时再close, 比如:
1 2 3 4 5 6 |
conn.open() conn.Query(..... ..... for.... ..... conn.close() |
from:https://www.cnblogs.com/lunawzh/p/6607116.html
View DetailsElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。 我们建立一个网站或应用程序,并要添加搜索功能,但是想要完成搜索工作的创建是非常困难的。我们希望搜索解决方案要运行速度快,我们希望能有一个零配置和一个完全免费的搜索模式,我们希望能够简单地使用JSON通过HTTP来索引数据,我们希望我们的搜索服务器始终可用,我们希望能够从一台开始并扩展到数百台,我们要实时搜索,我们要简单的多租户,我们希望建立一个云的解决方案。因此我们利用Elasticsearch来解决所有这些问题及可能出现的更多其它问题。 下载地址:https://www.elastic.co/cn/downloads/elasticsearch
View DetailsABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称。 ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板。 ABP的官方网站 : http://www.aspnetboilerplate.com ABP在Github上的开源项目:https://github.com/aspnetboilerplate ABP 的由来 “DRY——避免重复代码”是一个优秀的开发者在开发软件时所具备的最重要的思想之一。我们在开发企业WEB应用程序时都有一些类似的需求,例如:都需要登录页面、用户/角色管理、权限验证、数据有效性验证、多语言/本地化等等。一个高品质的大型软件都会运用一些最佳实践,例如分层体系结构、领域驱动设计、依赖注入等。我们也可能会采用ORM、数据库迁移(Database Migrations)、日志记录(Logging)等工具。 从零开始创建一个企业应用程序是一件繁琐的事,因为需要重复做很多常见的基础工作。许多公司都在开发自己的应用程序框架来重用于不同的项目,然后在框架的基础上开发一些新的功能。但并不是每个公司都有这样的实力。假如我们可以分享的更多,也许可以避免每个公司或每个项目的重复编写类似的代码。作者之所以把项目命名为“ASP.NET Boilerplate”,就是希望它能成为开发一般企业WEB应用的新起点,直接把ABP作为项目模板。 ABP是什么? ABP是为新的现代Web应用程序使用最佳实践和使用最流行工具的一个起点。可作为一般用途的应用程序的基础框架或项目模板。它的功能包括: 服务器端: 基于最新的.NET技术 (目前是ASP.NET MVC 5、Web API 2、C# 5.0,在ASP.NET 5正式发布后会升级) 实现领域驱动设计(实体、仓储、领域服务、领域事件、应用服务、数据传输对象,工作单元等等) 实现分层体系结构(领域层,应用层,展现层和基础设施层) 提供了一个基础架构来开发可重用可配置的模块 集成一些最流行的开源框架/库,也许有些是你正在使用的。 提供了一个基础架构让我们很方便地使用依赖注入(使用Castle Windsor作为依赖注入的容器) 提供Repository仓储模式支持不同的ORM(已实现Entity Framework 、NHibernate、MangoDb和内存数据库) 支持并实现数据库迁移(EF 的 Code first) 模块化开发(每个模块有独立的EF DbContext,可单独指定数据库) 包括一个简单的和灵活的多语言/本地化系统 包括一个 EventBus来实现服务器端全局的领域事件 统一的异常处理(应用层几乎不需要处理自己写异常处理代码) 数据有效性验证(Asp.NET MVC只能做到Action方法的参数验证,ABP实现了Application层方法的参数有效性验证) 通过Application Services自动创建Web Api层(不需要写ApiController层了) 提供基类和帮助类让我们方便地实现一些常见的任务 使用“约定优于配置原则” 客户端: Bootstrap、Less、AngularJs、jQuery、Modernizr和其他JS库: jQuery.validate、jQuery.form、jQuery.blockUI、json2等 为单页面应用程序(AngularJs、Durandaljs)和多页面应用程序(Bootstrap+Jquery)提供了项目模板。 自动创建Javascript 的代理层来更方便使用Web Api 封装一些Javascript 函数,更方便地使用ajax、消息框、通知组件、忙状态的遮罩层等等 除ABP框架项目以外,还开发了名叫“Zero”的模块,实现了以下功能: 身份验证与授权管理(通过ASP.NET Identity实现的) 用户&角色管理 系统设置存取管理(系统级、租户级、用户级,作用范围自动管理) 审计日志(自动记录每一次接口的调用者和参数) ABP不是什么? ABP 提供了一个应用程序开发模型用于最佳实践。它拥有基础类、接口和工具使我们容易建立起可维护的大规模的应用程序。 然而: 它不是RAD工具之一,RAD工具的目的是无需编码创建应用程序。相反,ABP提供了一种编码的最佳实践。 它不是一个代码生成工具。在运行时虽然它有一些特性构建动态代码,但它不能生成代码。 它不是一个一体化的框架。相反,它使用流行的工具/库来完成特定的任务(例如用EF做ORM,用Log4Net做日志记录,使得Castle Windsor作为赖注入容器, AngularJs 用于SPA 框架)。 就我使用了ABP几个月的经验来看,虽然ABP不是RAD,但是用它开发项目绝对比传统三层架构要快很多。 虽然ABP不是代码生成工具,但因为有了它,使我们项目的代码更简洁规范,这有利于使用代码生成工具。 我自己使用VS2013的Scaffolder+T4开发的代码生成器,可根据领域对象的UML类图自动生成全部前后端代码和数据库,简单的CURD模块几乎不需要编写代码,有复杂业务逻辑的模块主要补充领域层代码即可。这样就能把时间多花在领域模型的设计上,减少写代码的时间。 下面通过原作者的“简单任务系统”例子,演示如何运用ABP开发项目 从模板创建空的web应用程序 ABP提供了一个启动模板用于新建的项目(尽管你能手动地创建项目并且从nuget获得ABP包,模板的方式更容易)。 转到www.aspnetboilerplate.com/Templates从模板创建你的应用程序。 你可以选择SPA(AngularJs或DurandalJs)或者选择MPA(经典的多页面应用程序)项目。可以选择Entity Framework或NHibernate作为ORM框架。 这里我们选择AngularJs和Entity Framework,填入项目名称“SimpleTaskSystem”,点击“CREATE […]
View Details