项目右键 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 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跨平台是ASP.NET Core一个显著的特性,而KestrelServer是目前微软推出了唯一一个能够真正跨平台的Server。KestrelServer利用一个名为KestrelEngine的网络引擎实现对请求的监听、接收和响应。KetrelServer之所以具有跨平台的特质,源于KestrelEngine是在一个名为libuv的跨平台网络库上开发的。 目录 一、libuv 二、KestrelServer 三、KestrelServerOptions 四、ApplicationLifetime 五、设置监听地址 一、libuv 说起libuv,就不得不谈谈libev,后者是Unix系统上一个事件循环和事件模型的网络库。libev因其具有的高性能成为了继lievent和Event perl module之后一套最受欢迎的网络库。由于Libev不支持Windows,有人在libev之上创建了一个抽象层以屏蔽平台之间的差异,这个抽象层就是libuv。libuv在Windows平台上是采用IOCP的形式实现的,右图揭示了libuv针对Unix和Windows的跨平台实现原理。到目前为止,libuv支持的平台已经不限于Unix和Windows了,包括Linux(2.6)、MacOS和Solaris (121以及之后的版本)在内的平台在libuv支持范围之内。 二、KestrelServer 如下所示的代码片段体现了KestrelServer这个类型的定义。除了实现接口IServer定义的Features属性之外,KestrelServer还具有一个类型为KestrelServerOptions的只读属性Options。这个属性表示对KestrelServer所作的相关设置,我们在调用构造函数时通过输入参数options所代表的IOptions<KestrelServerOptions>对象对这个属性进行初始化。构造函数还具有另两个额外的参数,它们的类型分别是IApplicationLifetime和ILoggerFactory,后者用于创建记录日志的Logger,前者与应用的生命周期管理有关。
1 2 3 4 5 6 7 8 9 |
1: public class KestrelServer : IServer 2: { 3: public IFeatureCollection Features { get; } 4: public KestrelServerOptions Options { get; } 5: 6: public KestrelServer(IOptions<KestrelServerOptions> options,IApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory); 7: public void Dispose(); 8: public void Start<TContext>(IHttpApplication<TContext> application); 9: } |
我们一般通过调用WebHostBuilder的扩展方法UseKestrel方法来完成对KestrelServer的注册。如下面的代码片段所示,UseKestrel方法具有两个重载,其中一个具有同一个类型为Action<KestrelServerOptions>的参数,我们可以利用这个参数直接完成对KestrelServerOptions的设置。
1 2 3 4 5 |
1: public static class WebHostBuilderKestrelExtensions 2: { 3: public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder); 4: public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder, Action<KestrelServerOptions> options); 5: } |
三、KestrelServerOptions 由于Server负责请求的监听、接收和响应,所以Server是影响整个Web应用响应能力和吞吐量最大的因素之一,为了更加有效地使用Server,我们往往针对具体的网络负载状况对其作针对性的设置。对于KestrelServer来说,在构造函数中作为参数指定的KestrelServerOptions对象代表针对它所做的设置。我们针对KestrelServer所做的设置主要体现在KestrelServerOptions类型的如下5个属性上。
1 2 3 4 5 6 7 8 9 |
1: public class KestrelServerOptions 2: { 3: //省略其他成员 4: public int MaxPooledHeaders { get; set; } 5: public int MaxPooledStreams { get; set; } 6: public bool NoDelay { get; set; } 7: public TimeSpan ShutdownTimeout { get; set; } 8: public int ThreadCount { get; set; } 9: } |
KestrelServerOptions注册的KetrelServer在管道中会以依赖注入的方式被创建,并采用构造器注入的方式提供其构造函数的参数options,由于这个参数类型为IOptions<KestrelServerOptions>,所以我们利用Options模型以配置的方式来指定KestrelServerOptions对象承载的设置。比如我们可以将KestrelServer的相关配置定义在如下一个JSON文件中。
1 2 3 4 5 |
1: { 2: "noDelay" : false, 3: "shutdownTimeout" : "00:00:10", 4: "threadCount" : 10 5: } |
为了让应用加载这么一个配置文件(文件名假设为“KestrelServerOptions.json”),我们只需要在启动类型(Startup)类的ConfigureServces方法中按照如下的方式利用ConfigurationBuilder加载这个配置文件并生成相应的Configuration对象,最后按照Options模型的编程方式完成KestrelServerOptions类型和该对象的映射即可。
1 2 3 4 5 6 7 8 9 10 11 |
1: public class Startup 2: { 3: //其他成员 4: public void ConfigureServices(IServiceCollection services) 5: { 6: IConfiguration configuration = new ConfigurationBuilder() 7: .AddJsonFile("KestrelServerOptions.json") 8: .Build(); 9: services.Configure<KestrelServerOptions>(configuration); 10: } 11: } |
四、ApplicationLifetime 我们将所有实现了IApplicationLifetime接口的所有类型及其对应对象统称为ApplicationLifetime。从命名的角度来看,ApplicationLifetime貌似是对当前应用生命周期的描述,而实际上它存在的目的仅仅是在应用启动和关闭(只要是关闭)时对相关组件发送通知而已。如下面的代码片段所示,IApplicationLifetime接口具有三个CancellationToken类型的属性(ApplicationStarted、ApplicationStopping和ApplicationStopped),我们可以利用它们是否已经被取消(Cancel)确定当前应用的状态(已经开启、正在关闭和已经关闭)。如果试图关闭应用,StopApplication方法应该被调用以发出应用正在被关闭的通知。对于KestrelServer来说,如果请求处理线程中发生未被处理异常,它会调用这个方法。
1 2 3 4 5 6 7 8 |
1: public interface IApplicationLifetime 2: { 3: CancellationToken ApplicationStarted { get; } 4: CancellationToken ApplicationStopping { get; } 5: CancellationToken ApplicationStopped { get; } 6: 7: void StopApplication(); 8: } |
ASP.NET Core默认使用的ApplicationLifetime是具有如下定义的一个同名类型。可以看出它实现的三个属性返回的CancellationToken对象是通过三个对应的CancellationTokenSource生成。除了实现IApplicationLifetime接口的StopApplication方法用于发送“正在关闭”通知之外,这个类型还定义了额外两个方法(NotifyStarted和NotifyStopped)用于发送“已经开启/关闭”的通知。
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 |
1: public class ApplicationLifetime : IApplicationLifetime 2: { 3: private readonly CancellationTokenSource _startedSource = new CancellationTokenSource(); 4: private readonly CancellationTokenSource _stoppedSource = new CancellationTokenSource(); 5: private readonly CancellationTokenSource _stoppingSource = new CancellationTokenSource(); 6: 7: public CancellationToken ApplicationStarted 8: { 9: get { return this._startedSource.Token; } 10: } 11: public CancellationToken ApplicationStopped 12: { 13: get { return this._stoppedSource.Token; } 14: } 15: public CancellationToken ApplicationStopping 16: { 17: get { return this._stoppingSource.Token; } 18: } 19: 20: public void NotifyStarted() 21: { 22: this._startedSource.Cancel(false); 23: } 24: public void NotifyStopped() 25: { 26: this._stoppedSource.Cancel(false); 27: } 28: public void StopApplication() 29: { 30: this._stoppingSource.Cancel(false); 31: } 32: } |
一个ASP.NET Core应用利用管道处理请求,所以管道的生命周期等同于应用自身的生命周期。当我们调用Run方法开启WebHost时,请求处理管道被构建出来。如果管道在处理请求时发生未被处理的异常,管道的Sever会调用ApplicationLifeTime对象的StopApplication方法向WebHost发送关闭应用的通知以便后者执行一些回收释放工作。 五、设置监听地址 在演示的实例中,我们实际上并不曾为注册的KestrelServer指定一个监听地址,从运行的效果我们不难看出,WebHost在这种情况下会指定“http://localhost:5000”为默认的监听地址,Server的监听地址自然可以显式指定。在介绍如何通过编程的方式为Server指定监听地址之前,我们有先来认识一个名为ServerAddressesFeature的特性。 我们知道表示Server的接口IServer中定义了一个类型为IFeatureCollection 的只读属性Features,它表示用于描述当前Server的特性集合,ServerAddressesFeature作为一个重要的特性,就包含在这个集合之中。我们所说的ServerAddressesFeature对象是对所有实现了IServerAddressesFeature接口的所有类型及其对应对象的统称,该接口具有一个唯一的只读属性返回Server的监听地址列表。ASP.NET Core默认使用的ServerAddressesFeature是具有如下定义的同名类型。
1 2 3 4 5 6 7 8 9 |
1: public interface IServerAddressesFeature 2: { 3: ICollection<string> Addresses { get; } 4: } 5: 6: public class ServerAddressesFeature : IServerAddressesFeature 7: { 8: public ICollection<string> Addresses { get; } 9: } |
对于WebHost在通过依赖注入的方式创建的Server,由它的Features属性表示的特性集合中会默认包含这么一个ServerAddressesFeature对象。如果没有一个合法的监听地址被添加到这个 ServerAddressesFeature对象的地址列表中,WebHost会将显式指定的地址(一个或者多个)添加到该列表中。我们显式指定的监听地址实际上是作为WebHost的配置保存在一个Configuration对象上,配置项对应的Key为“server.urls”,WebHostDefaults的静态只读属性ServerUrlsKey返回的就是这么一个Key。
1 2 3 4 5 6 |
1: new WebHostBuilder() 2: .UseSetting(WebHostDefaults.ServerUrlsKey, "http://localhost:3721/") 3: .UseMyKestrel() 4: .UseStartup<Startup>() 5: .Build() 6: .Run(); |
WebHost的配置最初来源于创建它的WebHostBuilder,后者提供了一个UseSettings方法来设置某个配置项的值,所以我们可以采用如下的方式来指定监听地址(“http://localhost:3721/”)。不过,针对监听地址的显式设置,最直接的编程方式还是调用WebHostBuilder的扩展方法UseUrls,如下面的代码片段所示,该方法的实现逻辑与上面完全一致。
1 2 3 4 5 |
1: public static class WebHostBuilderExtensions 2: { 3: public static IWebHostBuilder UseUrls(this IWebHostBuilder hostBuilder, params string[] urls) 4: =>hostBuilder.UseSetting(WebHostDefaults.ServerUrlsKey, string.Join(ServerUrlsSeparator, urls)) ; 5: } |
from:https://www.cnblogs.com/artech/p/KestrelServer.html
View Details原文地址—-Kestrel server for ASP.NET Core By Tom Dykstra, Chris Ross, and Stephen Halter Kestrel是一个基于libuv的跨平台ASP.NET Core web服务器,libuv是一个跨平台的异步I/O库。ASP.NET Core模板项目使用Kestrel作为默认的web服务器。 Kestrel支持以下功能: HTTPS 用于启用不透明升级的WebSockets 位于Nginx之后的高性能Unix sockets Kestrel 被.NET Core支持的所有平台和版本所支持 查看或下载示例代码 何时使用Kestrel和反向代理服务器 如果你的应用只接收来自内部网络的请求,你可以只使用Kestrel本身。 如果你将你的应用部署在公共网络上,我们建议你使用IIS,Nginx或者Apache作为反向代理服务器。一个反向代理服务器接收来自网络的HTTP请求并且在经过一些初步处理后将请求传递到Kestrel服务器。 出于安全性的理由,反向代理常常被edge deployments所采用。因为Kestrel相对较新,对抵御安全攻击至今还没有一个完整的功能补充。安全性处理包括但不限于适当的超时,大小的限制,以及并发连接限制等问题。 另一个需要反向代理的场景是,你有多个需要在单独的服务器上运行并分享同一端口的应用。因为Kestrel不支持在多进程间分享同一端口,所以应用并不能直接和Kestrel合作。当你在某个端口上配置Kestrel运行侦听时,不算主机头如何标识,Kestrel会为该端口处理所有的流量。反向代理可以为多个应用共享唯一端口并将流量发送给Kestrel。 即使不需要反向代理服务器,使用它也可以简化负载均衡和SSL设置 — 只要你的反向代理服务器需要SSL证书,并且该服务器可以和你的应用在内部网中通过普通HTTP进行通信。 如何在ASP.NET Core应用中使用Kestrel 安装 Microsoft.AspNetCore.Server.Kestrel Nuget包。 在应用的Main方法中调用WebHostBuilder的UseKestrel 扩展方法,指定你需要的Kestrel选项,如以下示例所示:
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 |
public static int Main(string[] args) { Console.WriteLine("Running demo with Kestrel."); var config = new ConfigurationBuilder() .AddCommandLine(args) .Build(); var builder = new WebHostBuilder() .UseContentRoot(Directory.GetCurrentDirectory()) .UseConfiguration(config) .UseStartup<Startup>() .UseKestrel(options => { if (config["threadCount"] != null) { options.ThreadCount = int.Parse(config["threadCount"]); } }) .UseUrls("http://localhost:5000"); var host = builder.Build(); host.Run(); return 0; } |
URL 前缀 默认情况下,ASP.NET Core项目绑定了http://localhost:5000。通过使用UseUrls扩展方法——编辑urls命令行参数,或者是通过ASP.NET Core配置系统,你可以为Ketrel配置URL前缀和端口号以用来侦听请求。关于这些方法更多的信息,请参考Hosting。有关于当你使用IIS作为反向代理时,URL绑定是如何工作的信息,请参考ASP.NET Core 模块。 Kestrel URL前缀可以是以下格式中的任一种。 IPv4 地址和端口号
1 2 |
http://65.55.39.10:80/ https://65.55.39.10:443/ |
IPv6 地址和端口号
1 2 |
http://[0:0:0:0:0:ffff:4137:270a]:80/ https://[0:0:0:0:0:ffff:4137:270a]:443/ |
IPv6中的 [::] 等价于 IPv4 0.0.0.0。 主机名和端口号
1 2 3 4 |
http://contoso.com:80/ http://*:80/ https://contoso.com:443/ https://*:443/ |
主机名称,*,以及+,都不是特殊的。任何没有公认的IP 或是“localhost”的地址将绑定到所有的IPv4和IPv6的IP上。如果你需要为不同的ASP.NET Core应用在同一端口上绑定不同的主机名,请使用WebListener或者诸如IIS,Nginx或Apache这样的反向代理服务器。 * "Localhost" 名称和端口号或回送IP地址和端口号
1 2 3 |
http://localhost:5000/ http://127.0.0.1:5000/ http://[::1]:5000/ |
当localhost被指定时,Kestrel会尝试去绑定到IPv4和IPv6的环回接口。如果被请求的端口号正在任一环回接口上被其他服务所使用,Kestrel将会启动失败。如果任一环回接口出于各种原因而不可用(最通常的情况是因为IPv6暂不被支持),Kestrel将记录下一个警告信息。 Unix socket
1 |
http://unix:/run/dan-live.sock |
如果你指定了端口号0,Kestrel将动态地绑定到合适的端口号。除了localhost名称,绑定到0端口号被其他任何主机名称或IP地址所允许。 当你指定了端口号0,你可以使用IServerAddressesFeature接口去决定运行时Kestrel实际绑定到哪个端口。下列示例用于获取绑定端口并且在console上显示出来。
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 void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); var serverAddressesFeature = app.ServerFeatures.Get<IServerAddressesFeature>(); app.UseStaticFiles(); app.Run(async (context) => { context.Response.ContentType = "text/html"; await context.Response .WriteAsync("<p>Hosted by Kestrel</p>"); if (serverAddressesFeature != null) { await context.Response .WriteAsync("<p>Listening on the following addresses: " + string.Join(", ", serverAddressesFeature.Addresses) + "</p>"); } await context.Response.WriteAsync($"<p>Request URL: {context.Request.GetDisplayUrl()}<p>"); }); } |
SSL的URL前缀 如果你调用UseSSL扩展方法,请确保在https:中包含URL前缀,如下所示:
1 2 3 4 5 6 7 8 9 |
var host = new WebHostBuilder() .UseKestrel(options => { options.UseHttps("testCert.pfx", "testPassword"); }) .UseUrls("http://localhost:5000", "https://localhost:5001") .UseContentRoot(Directory.GetCurrentDirectory()) .UseStartup<Startup>() .Build(); |
Note HTTPS和HTTP不能在同一端口上被托管。 下一步 更多的信息,请参考以下资源: Sample app for this […]
View Details控制反转:我们向IOC容器发出获取一个对象实例的一个请求,IOC容器便把这个对象实例“注入”到我们的手中,在这个过程中你不是一个控制者而是一个请求者,依赖于容器提供给你的资源,控制权落到了容器身上。这个过程就是控制反转。 依赖注入:我们向容器发出请求以后,获得这个对象实例的过程就叫依赖注入。 IoC模式其实是工厂设计模式的升华,在微软较早的PetShop项目通过反射技术做了很多工厂。我认为可能是IoC的原型。 关于Ioc的框架有很多,比如Castle Windsor、Unity、Spring.NET、StructureMap,我们这边使用微软提供的Unity做示例,你可以使用 Nuget 添加 Unity ,也可以引用Microsoft.Practices.Unity.dll和Microsoft.Practices.Unity.Configuration.dll,下面我们就一步一步的学习下 Unity依赖注入 的详细使用。 Unity是微软推出的IOC框架, 使用这个框架,可以实现AOP面向切面编程,便于代码的后期维护,此外,这套框架还自带单例模式,可以提高程序的运行效率。 下面是我自己的案例,以供日后参考: 使用VS2015的Nuget管理器下载Unity。 程序员接口类:
1 2 3 4 5 6 7 |
namespace UnityDemo { public interface IProgrammer { void Working(); } } |
程序员类:
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; namespace UnityDemo { public class CSharp : IProgrammer { public void Working() { Console.WriteLine("programming C# ..."); } } public class VB : IProgrammer { public void Working() { Console.WriteLine("programming VB ..."); } } public class Java : IProgrammer { public void Working() { Console.WriteLine("programming Java ..."); } } } |
App.config配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?xml version="1.0" encoding="utf-8"?> <configuration> <!--<startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/> </startup>--> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration"/> </configSections> <unity> <containers> <container name="Programmer"> <register type="UnityDemo.IProgrammer,UnityDemo" mapTo="UnityDemo.CSharp,UnityDemo" name="CSharp"></register> <register type="UnityDemo.IProgrammer,UnityDemo" mapTo="UnityDemo.VB,UnityDemo" name="VB"></register> <register type="UnityDemo.IProgrammer,UnityDemo" mapTo="UnityDemo.Java,UnityDemo" name="Java"></register> </container> </containers> </unity> </configuration> |
主程序代码:
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 |
using Microsoft.Practices.Unity; using Microsoft.Practices.Unity.Configuration; using System; using System.Configuration; namespace UnityDemo { class Program { private static IUnityContainer container = null; static void Main(string[] args) { RegisterContainer(); var programmer = container.Resolve<IProgrammer>("CSharp"); //var programmer = container.Resolve<IProgrammer>("VB"); programmer.Working(); Console.ReadKey(); } private static void RegisterContainer() { container = new UnityContainer(); UnityConfigurationSection config =(UnityConfigurationSection) ConfigurationManager.GetSection(UnityConfigurationSection.SectionName); config.Configure(container, "Programmer"); } } } |
运行结果: 这个Demo的思想: 定义一个程序员接口IProgrammer,有3个实现这个接口的程序员类,分别是CSharp、VB、Java, 然后配置App.config文件,里面首先定义一个配置节点(configSection),名称为Unity,引用的命名空间是微软的Unity, 接着下面定义一个unity节点,里面的容器集合(containers),集合里面可以有多个容器,这个Demo里的只有一个容器,里面包含了3个注册节点(register),分别是CSharp,VB、Java,注意,里节点中的name属性用于在程序调用时选择类的。 在主程序的代码中, 1、有一个Unity的容器container; 2、注册容器的方法RegisterContainer,作用是到App.config中读取容器的信息,把接口和3个类的映射关系注册到容器中; 3、主程序运行前,先注册容器,然后通过容器的Resolve方法,实例化一个programmer类,原理大概是,我们把类名传入到容器中,容器会根据类名,找到App.config中对应的register映射关系,容器就会通过反射得到相应的程序员类,返回这个类。 4、此时programmer类就实例化完成,可以使用了。 使用了Unity,以后如果需要追加新的类,只需要实现IProgrammer接口,在配置文件追加映射关系,执行代码中修改name的值就能使用新的类了。 这样,我们不需要删除旧的代码,就能追加新的功能,便于维护代码。 本人的Demo下载 转自: https://www.cnblogs.com/chenyucong/p/6272600.html
View Details官网生成的ABP + module zero出现Default language is not defined!的错误,原因是数据库没有language数据,而不是缺少language.xml资源文件,所以先创建数据库就好了。 解决方法: 1.选择Web项目作为起始项目。 2.打开程序包管理控制台,选择“EntityFramework”项目作为默认项目,然后运行EF的’Update-Database’命令。该命令会创建数据库。 3.运行该应用,默认的用户名是’admin’,密码是’123qwe’。 在执行第2步时遇到了如下错误: Update-Database : 无法将“Update-Database”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后重试。 原因可能是EntityFramework没有安装,Install-Package EntityFramework就好了。什么?还是不行?全部重新生成再重新打开vs试试吧! https://blog.csdn.net/klo220/article/details/51138582
View Details