目錄javascript
返回目錄php
程序集清單資源
在程序集中嵌入資源的最簡單方法是什麼?那就是使用Visual Studio中的「嵌入式資源(Embedded Resource)」建立選項,至關於使用csc的」/resource」參數。具體步驟,首先在Visual Studio的工程中選擇資源文件,而後選擇「屬性」,接着在屬性框中的Build Action中選擇Embedded Resource,以下圖,把a.file做爲資源文件。css
這個a.file成爲嵌入資源後,它將會做爲清單資源(Manifest Resource)存入程序集清單中(Assembly Manifest):html
程序集清單是程序集不可缺乏的元素,上圖來自MSDN,能夠參考更多關於程序集或者程序集清單的信息在這裏:http://msdn.microsoft.com/zh-cn/library/1w45z383(v=VS.100).aspx,本文就再也不多說了。java
接下來要關心的是怎樣使用程序集清單中的資源。node
咱們能夠使用Assembly類的GetManifestResourceNames方法,返回程序集的全部清單資源的文件名。git
或者Assembly.GetMenifestResourceStream方法返回指定資源的流。程序員
好比剛纔那個包含a.file的程序集。github
var ass =Assembly.GetExecutingAssembly();web
foreach(var file in ass.GetManifestResourceNames())
Console.WriteLine(file);
輸出
Mgen.a.file
前面的Mgen是程序集的默認命名空間,VS在編譯後會自動把命名空間加在文件名的前面的。
接下來使用GetManifestResourceStream來讀取文件內容:
var ass =Assembly.GetExecutingAssembly();
var stream = ass.GetManifestResourceStream("Mgen.a.file");
/* 操做Stream對象來讀取文件信息 */
RESX資源文件
另一種建立資源的形式就是RESX資源文件,這個經過VS的添加文件中的「資源文件」類型。RESX文件相比手動建立上面講的程序集清單資源最大的優點就是:
- 支持多語言
- 快速建立資源
- 管理方便
RESX能夠支持多語言,Visual Studio編譯後會出現附屬程序集(satellite assembly),事實上是鏈接器(AL.exe)作這份工做。程序在執行在不一樣語言環境會搜索相應語言的資源。同時Visual Studio還提供了強大的RESX的資源編輯器。
同程序集清單資源同樣,咱們仍是要弄懂所謂RESX資源究竟是怎麼存的。
如今,在工程中建立一個Resource1.resx,編輯它,添加一個b.file文件,再添加一個字符串,隨便寫個名稱。
完成後,你會發現工程裏多了些文件:
a.file是上面咱們手動加的程序集清單資源。而Resource1.resx中的文件被存到了一個叫Resources的文件夾內(圖中的b.file)。
檢查這兩個文件的屬性中的Build Action,你會發現,Resources內的文件的Build Action都是None,VS不會對他們進行任何操做的,就好像他們不在工程裏似的。而RESX文件的Build Action則是Embedded Resource,它會成爲程序員清單資源,不一樣於不一樣程序集清單資源,RESX在編譯時下面的Custom Tool是一個叫ResXFileCodeGenerator的工具:
這個工具會把全部RESX的資源連起來建立成一個二進制文件,VS最後把這個生成的文件最終做爲程序集清單資源文件保存到程序集中。這個二進制資源文件的擴展名是.resources。
此時再運行上面講的Assembly.GetManifestResourceNames方法來枚舉程序集清單資源文件,輸出會成:
Mgen.a.file
Mgen.Resource1.resources
Resource1.resx文件會最終編譯成Mgen.Resource1.resources資源文件。
整個過程能夠看這張圖:
使用ResourceReader和ResourceSet解析二進制資源文件
建議先讀這篇文章來先了解IResourceReader,IResourceWriter和ResourceSet類型:.NET(C#):使用IResourceReader,IResourceWriter和ResourceSet。這裏就不在講這三個類型的使用。
上面講過,RESX資源文件最終會被編譯成.resources擴展名的資源文件(二進制)並保存在程序集清單資源(assembly manifest resource)。
下面咱們用.NET中的.resources二進制資源文件的解析類ResourceReader和ResourceSet來手動解析這個.resources文件。
代碼:
//+ using System.Resources
staticvoid Main()
{
using (Stream resources =Assembly.GetExecutingAssembly().GetManifestResourceStream("Mgen.Resource1.resources"))
{
//使用IResourceReader
ReadUsingResourceReader(resources);
//從新定位Stream
resources.Seek(0, SeekOrigin.Begin);
//使用ResourceSet
ReadUsingResourceSet(resources);
}
}
//使用IResourceReader
staticvoid ReadUsingResourceReader(Stream st)
{
Console.WriteLine("== 使用IResourceReader");
IResourceReader rr =newResourceReader(st);
var iter = rr.GetEnumerator();
while (iter.MoveNext())
Console.WriteLine("鍵: {0} 值: {1}", iter.Key, iter.Value);
//不須要調用IResourceReader.Dispose,Stream會在Main方法中被Dipose
}
//使用ResourceSet
staticvoid ReadUsingResourceSet(Stream st)
{
Console.WriteLine("== 使用ResourceSet");
ResourceSet rs =newResourceSet(newResourceReader(st));
Console.WriteLine(BitConverter.ToString((byte[])rs.GetObject("b")));
Console.WriteLine(rs.GetString("String1"));
//不須要調用ResourceSet.Dispose,Stream會在Main方法中被Dipose
}
這將會以ResourceReader和ResourceSet兩種方式輸出b.file的字節內容和String1字符串。
使用ResourceManager解析二進制資源文件
關於ResourceManager類型的使用,能夠參考:.NET(C#):使用ResourceManager類型。這裏就再也不多講了。
咱們就直接使用ResourceManager,仍是上面的工程,用ResourceManager來解析這個.resources二進制的資源文件。
代碼:
//+ using System.Resources
ResourceManager resManager =newResourceManager(typeof(Resource1));
//等效於:new ResourceManager("Mgen.Resource1", Assembly.GetExecutingAssembly());
//此時ResourceManager.BaseName是Type.FullName正好是Mgen.Resource1
//獲取file.b的內容
Console.WriteLine(BitConverter.ToString((byte[])resManager.GetObject("b")));
//獲取資源中的字符串
Console.WriteLine(resManager.GetString("String1"));
這將會輸出b.file的字節內容和String1字符串。
小看RESX資源文件的Designer.cs文件
最後再讓咱們看看RESX資源文件後面的那個xxx.Designer.cs文件。
它定義了資源讀取的一個類,好比資源文件名稱是Resource1,這個類的名稱就是Resource1。這個類其實就是內部包裝了一個上面講的ResourceManager,而且根據用戶RESX定義的資源數據顯示的定義具備強類型的屬性值用來讀取文件。
其內部ResourceManager是這樣被初始化的,能夠看到,ResourceManager.BaseName就是程序集清單資源的名稱 (注意ResourceManager.BaseName屬性沒有CultureInfo名稱和.resources擴展名,可是有命名空間(其實徹底就 是文件名),因此本例中的Mgen.Resource1.resources程序集清單資源文件的ResourceManager初始化BaseName 就是:Mgen.Resource1。)
internalstaticglobal::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp =newglobal::System.Resources.ResourceManager("Mgen.Resource1", typeof(Resource1).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
接着RESX中定義的文件b和字符串String1資源徹底就是ResourceManager的方法的包裝,好比b文件讀取返回字節數組,就是調用ResourceManager.GetObject,而後轉換成byte[]:
internalstaticbyte[] b {
get {
object obj = ResourceManager.GetObject("b", resourceCulture);
return ((byte[])(obj));
}
}
好了,就到這裏吧,但願讀者讀完文章後對RESX文件和程序集清單資源有更好的理解!
關於單元測試的思考--Asp.Net Core單元測試最佳實踐
2018-07-07 22:23 by 李玉寶, 615 閱讀, 3 評論, 收藏, 編輯
在咱們碼字過程當中,單元測試是必不可少的。但在從業過程當中,不少開發者卻對單元測試望而卻步。有些時候並非不想寫,而是經常會碰到下面這些問題,讓開發者放下了碼字的腳步:
- 這個類初始數據太麻煩,你看:new MyService(new User("test",1), new MyDAO(new Connection(......)),new ToManyPropsClass(......) .....) 。我:。。。
- 這個代碼內部邏輯都是和Cookie有關,我單元測試很差整啊,仍是得啓動到瀏覽器裏一個按鈕一個按鈕點。
- 這個代碼內部讀了配置文件,單元測試也不能給我整個配置文件啊?
- 這個代碼主要是驗證WebAPI入口得模型綁定,必須得調用一次啊?
這些問題確實存在,但它們阻止不了咱們那顆要寫單元測試的心。單元測試的優勢不少,你或許能夠無論。但至少能讓你從那些須要在瀏覽器裏點擊10多下的操做裏解脫出來。本文從一個簡單的邏輯測試出發,慢慢拉開測試的大幕,讓你愛上測試。文章主要是傳播一些單元測試的理念,其次纔是介紹asp.net core中的單元測試。
本文使用的環境爲asp.net core 2.1 webapi,代碼能夠直接下載:https://github.com/yubaolee/DotNetCoreUnitTestSamples 爲了方便閱讀,以一個最簡單的邏輯爲例:
public class UserService{ public bool CheckLogin(UserInfo user) { return user.Name == user.Password; //登陸邏輯,爲了看着舒服,少點 } } public class UserInfo{ public string Name { get; set; } public string Password { get; set; } }
測試的WebAPI控制器以下:
public class ValuesController : ControllerBase { private UserService _service; public ValuesController(UserService service) { _service = service; } [HttpGet] [Route("checklogin")] public bool CheckLogin([FromQuery]UserInfo user) { return _service.CheckLogin(user); } }
都已準備完畢,那麼,開始咱們的表演吧:
普通業務的單元測試
public class TestService { private UserService _service; [SetUp] public void Init() { var server = new TestServer(WebHost.CreateDefaultBuilder().UseStartup<Startup>()); _service = server.Host.Services.GetService<UserService>(); } [Test] public void TestLogin() { bool result = _service.CheckLogin(new UserInfo { Name = "yubao", Password = "yubao" }); Assert.IsTrue(result); } }
在作業務測試過程當中要善於使用注入功能,而不是使用new對象的方式,好比這裏的Host.Services.GetService,防止出現new MyService(new User("test",1), new MyDAO(new Connection(......)),new ToManyPropsClass(......) .....)這種尷尬。用的越多你就越能體會這種作法的好處。我在openauth.net中使用的是autofac的AutofacServiceProvider。
測試Controller
不少時候咱們須要測試頂層的controller(八成是controller裏混的有業務邏輯)。這時咱們能夠快速的寫出下面的測試代碼:
public class TestController { private ValuesController _controller; [SetUp] public void Init() { var server = new TestServer(WebHost.CreateDefaultBuilder().UseStartup<Startup>()); _controller = server.Host.Services.GetService<ValuesController>(); } [Test] public void TestLogin() { bool result = _controller.CheckLogin(new UserInfo{Name = "yubao",Password = "yubao"}); Assert.IsTrue(result); } }
這段代碼在JAVA spring mvc框架下是沒有問題的,但在asp.net core 中,你會發現:
獲取不到controller?spring mvc的理念就是萬物皆服務,哪怕是一個controller也是一個普通的服務。但微軟不喜歡這樣,默認時它要掌控controller的生死(The Subtle Perils of Controller Dependency Injection in ASP.NET Core MVC 有人在聲討微軟了)。因此咱們不能經過普通的ServicCollection來注入和獲取它,除非你指明Controller As Service,以下:
public void ConfigureServices(IServiceCollection services) { services.AddMvc().AddControllersAsServices().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); }
這時便可順利測試經過。
測試含有HTTP上下文的業務邏輯,好比Cookie、URL中的QueryString
在平時的代碼過程當中,經常會和HTTP上下文HttpContext打交道,最多見的如request、response、cookie、querystring等,好比咱們新的邏輯:
public class UserService { private IHttpContextAccessor _httpContextAccessor; public UserService(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public bool IsLogin() { return _httpContextAccessor.HttpContext.Request.Cookies["username"] != null; } }
這時如何測試呢?馬丁福勒在他的大做《企業應用架構模式》中明確指出「測試樁」的概念,來應對這種狀況。各類Mock框架應運而生。好比我最喜歡的Moq:
public class TestCookie { private UserService _service; [SetUp] public void Init() { var httpContextAccessorMock = new Mock<IHttpContextAccessor>(); httpContextAccessorMock.Setup(x => x.HttpContext.Request.Cookies["username"]).Returns("yubaolee"); var server = new TestServer(WebHost.CreateDefaultBuilder() .ConfigureServices(u =>u.AddScoped(x =>httpContextAccessorMock.Object)) .UseStartup<Startup>()); _service = server.Host.Services.GetService<UserService>(); } [Test] public void TestLogin() { bool result = _service.IsLogin(); Assert.IsTrue(result); } }
測試一次HTTP請求
有時咱們須要測試Mvc框架的模型綁定,看看一次客戶端的請求是否能被正確解析,亦或者測試WebAPI入口的一些Filter AOP等是否被正確觸發,這時就須要測試一次HTTP請求。從嚴格意義上來說這種測試已經脫離的單元測試的範疇,屬於集成測試。但這種測試代碼能夠節省咱們大量的重複勞動。asp.net core中能夠經過TestServer快速實現這種模擬:
public class TestHttpRequest { private TestServer _testServer; [SetUp] public void Init() { _testServer = new TestServer(WebHost.CreateDefaultBuilder().UseStartup<Startup>()); } [Test] public void TestLogin() { var client = _testServer.CreateClient(); var result = client.GetStringAsync("/api/values/checklogin?name=yubao&password=yubao"); Console.WriteLine(result.Result); } }
在進行單元測試的過程當中,測試的理念(或者TDD的思惟?)異常重要,它能幫助你構建和諧優美的代碼。
給正在努力的您幾條建議(附開源代碼)
雖然說今天不說技術,但我也整理了本身的開源項目(工具庫、擴展庫、倉儲庫等)分享給你們,但願你們互相學習。
Sikiro.Tookits.Files-基於NPOI的簡單導入導出封裝庫
Sikiro.Tookits.LocalCache-本地緩存封裝
Sikiro.Nosql.Mongo-基於原生驅動的mongo倉儲層封裝
Sikiro.DapperLambdaExtension.MsSql-基於dapper的lambda表達式擴展封裝
Sikiro.NoSql.Redis-Redis倉儲層封裝
AutoBuildEntity-集成vs的生成實體插件
這也爲了兌現去年在《整理本身的.net工具庫》 全部承諾源碼開放的諾言。好,廢話很少說進入正文。
封裝本身的dapper lambda擴展-設計篇
前言
昨天開源了業務業餘時間本身封裝的dapper lambda擴展,同時寫了篇博文《編寫本身的dapper lambda擴展-使用篇》簡單的介紹了下其使用,今天將分享下它的設計思路
鏈式編程
其實就是將多個方法經過點(.)將它們串接起來,讓代碼更加簡潔, 可讀性更強。
new SqlConnection("").QuerySet<User>() .Where(a => a.Name == "aasdasd") .OrderBy(a => a.CreateTime)
.Top(10) .Select(a => a.Name).ToList();
其原理是類的調用方法的返回值類型爲類自己或其基類,選擇返回基類的緣由是爲了作降級約束,例如我但願使用了Top以後接着Select和ToList,沒法再用where或orderBy。
UML圖
原型代碼
CommandSet

public class CommandSet<T> : IInsert<T>, ICommand<T> { #region 方法 public int Insert(T entity) { throw new NotImplementedException(); } public int Update(T entity) { throw new NotImplementedException(); } public int Update(Expression<Func<T, T>> updateExpression) { throw new NotImplementedException(); } public int Delete() { throw new NotImplementedException(); } public IInsert<T> IfNotExists(Expression<Func<T, bool>> predicate) { throw new NotImplementedException(); } public ICommand<T> Where(Expression<Func<T, bool>> predicate) { throw new NotImplementedException(); } #endregion } public interface ICommand<T> { int Update(T entity); int Update(Expression<Func<T, T>> updateExpression); int Delete(); } public interface IInsert<T> { int Insert(T entity); } public static class Database { public static QuerySet<T> QuerySet<T>(this SqlConnection sqlConnection) { return new QuerySet<T>(); } public static CommandSet<T> CommandSet<T>(this SqlConnection sqlConnection) { return new CommandSet<T>(); } }
QuerySet

public class QuerySet<T> : IAggregation<T> { #region 方法 public T Get() { throw new NotImplementedException(); } public List<T> ToList() { throw new NotImplementedException(); } public PageList<T> PageList(int pageIndex, int pageSize) { throw new NotImplementedException(); } public List<T> UpdateSelect(Expression<Func<T, T>> @where) { throw new NotImplementedException(); } public IQuery<TResult> Select<TResult>(Expression<Func<T, TResult>> selector) { throw new NotImplementedException(); } public IOption<T> Top(int num) { throw new NotImplementedException(); } public IOrder<T> OrderBy<TProperty>(Expression<Func<T, TProperty>> field) { throw new NotImplementedException(); } public IOrder<T> OrderByDescing<TProperty>(Expression<Func<T, TProperty>> field) { throw new NotImplementedException(); } public int Count() { throw new NotImplementedException(); } public bool Exists() { throw new NotImplementedException(); } public QuerySet<T> Where(Expression<Func<T, bool>> predicate) { throw new NotImplementedException(); } #endregion } public interface IAggregation<T> : IOrder<T> { int Count(); bool Exists(); } public interface IOrder<T> : IOption<T> { IOrder<T> OrderBy<TProperty>(Expression<Func<T, TProperty>> field); IOrder<T> OrderByDescing<TProperty>(Expression<Func<T, TProperty>> field); } public interface IOption<T> : IQuery<T>, IUpdateSelect<T> { IQuery<TResult> Select<TResult>(Expression<Func<T, TResult>> selector); IOption<T> Top(int num); } public interface IUpdateSelect<T> { List<T> UpdateSelect(Expression<Func<T, T>> where); } public interface IQuery<T> { T Get(); List<T> ToList(); PageList<T> PageList(int pageIndex, int pageSize); }
以上爲基本的設計模型,具體實現若有問題能夠查看個人源碼。
表達式樹的解析
具體實現的時候會涉及到不少的表達式樹的解析,例如where條件、部分字段update,而我實現的時候一共兩步:先修樹,再翻譯。然而不管哪步都得對錶達式樹進行遍歷。
表達式樹
百度的定義:也稱爲「表達式目錄樹」,以數據形式表示語言級代碼,它是一種抽象語法樹或者說是一種數據結構。
我對它的理解是,它本質是一個二叉樹,節點擁有本身的屬性像nodetype。
而它的遍歷方式爲前序遍歷
前序遍歷
百度的定義:歷首先訪問根結點而後遍歷左子樹,最後遍歷右子樹。在遍歷左、右子樹時,仍然先訪問根結點,而後遍歷左子樹,最後遍歷右子樹,如下圖爲例
其遍歷結果爲:ABDECF
以一個實際例子:
從上圖能夠看出,咱們會先遍歷到根節點的NodeType AndAlso翻譯爲 and ,而後到節點2,NodeType的Equal翻譯爲 = ,再到3節點翻譯爲 Name,再到4節點翻譯爲'skychen',那麼將三、4節點拼接起來就爲Name = 'skychen',若是類推六、7爲Age >= 18,最後拼接這個語句爲 Name = 'skychen' and Age >= 18。
修樹
修樹的目的,爲了咱們更好的翻譯,例如DateTime.Now表達式樹裏的NodeType爲MemberAccess,我但願轉換成NodeType爲Constant類型,以'2018-06-27 16:18:00'這個值做爲翻譯。
結束
以上爲設計和實現的要點,具體的實現問題能夠查看源碼,若是有建議和疑問能夠在下方留言,若是對您起到做用,但願您點一下推薦做爲對個人支持。
再次雙手奉上源碼:https://github.com/SkyChenSky/Sikiro.DapperLambdaExtension.MsSql
編寫本身的dapper lambda擴展-使用篇
前言
這是針對dapper的一個擴展,支持lambda表達式的寫法,鏈式風格讓開發者使用起來更加優雅、直觀。如今暫時只有MsSql的擴展,也沒有實現事務的寫法,將會在後續的版本補充。
這是我的業餘的開源小項目,若是你們有更好的實現方式和好的建議歡迎拍磚
本項目已經在github上開源了:Sikiro.DapperLambdaExtension.MsSql
去年寫了《整理本身的.net工具庫》,裏面提供的源碼從新發布到了github並用新的項目名Sikiro.Tookits
這兩個項目都發布到Nuget上了,能夠在Nuget搜索Sikiro能夠所有查看到
另外該項目會用到一些表達式樹的知識,若是有興趣的朋友能夠先去了解,我以前也寫過一篇簡單的文章《表達式樹的解析.》
下面是簡單的使用介紹
開始
Nuget
你能夠運行如下下命令在你的項目中安裝 Sikiro.DapperLambdaExtension.MsSql。
PM> Install-Package Sikiro.DapperLambdaExtension.MsSql
SqlConnection
var con = new SqlConnection("Data Source=192.168.13.46;Initial Catalog=SkyChen;Persist Security Info=True;User ID=sa;Password=123456789");
定義User
[Table("SYS_USER")] public class SysUser { /// <summary> /// 主鍵 /// </summary> [Key] [Required] [StringLength(32)] [Display(Name = "主鍵")] [Column("SYS_USERID")] public string SysUserid { get; set; } /// <summary> /// 建立時間 /// </summary> [Required] [Display(Name = "建立時間")] [Column("CREATE_DATETIME")] public DateTime CreateDatetime { get; set; } /// <summary> /// 郵箱 /// </summary> [Required] [StringLength(32)] [Display(Name = "郵箱")] [Column("EMAIL")] public string Email { get; set; } /// <summary> /// USER_STATUS /// </summary> [Required] [Display(Name = "USER_STATUS")] [Column("USER_STATUS")] public int UserStatus { get; set; } }
Insert
con.CommandSet<SysUser>().Insert(new SysUser { CreateDatetime = DateTime.Now, Email = "287245177@qq.com", SysUserid = Guid.NewGuid().ToString("N"), UserName = "chengong", });
當不存在某條件記錄Insert
con.CommandSet<SysUser>().IfNotExists(a => a.Email == "287245177@qq.com").Insert(new SysUser { CreateDatetime = DateTime.Now, Email = "287245177@qq.com", SysUserid = Guid.NewGuid().ToString("N"), UserName = "chengong", });
UPDATE
您能夠根據某個條件把指定字段更新
con.CommandSet<SysUser>().Where(a => a.Email == "287245177@qq.com").Update(a => new SysUser { Email = "123456789@qq.com" });
也能夠根據主鍵來更新整個實體字段信息
User.Email = "123456789@qq.com"; condb.CommandSet<SysUser>().Update(User);
DELETE
您能夠根據條件來刪除數據
con.CommandSet<SysUser>().Where(a => a.Email == "287245177@qq.com").Delete()
QUERY
GET
獲取過濾條件的一條數據(第一條)
con.QuerySet<SysUser>().Where(a => a.Email == "287245177@qq.com").Get()
TOLIST
固然咱們也能夠查詢出符合條件的數據集
con.QuerySet<SysUser>().Where(a => a.Email == "287245177@qq.com").OrderBy(b => b.Email).Top(10).Select(a => a.Email).ToList();
PAGELIST
還有分頁
con.QuerySet<SysUser>().Where(a => a.Email == "287245177@qq.com") .OrderBy(a => a.CreateDatetime) .Select(a => new SysUser { Email = a.Email, CreateDatetime = a.CreateDatetime, SysUserid = a.SysUserid }) .PageList(1, 10);
UPDATESELECT
先更新再把結果查詢出來
con.QuerySet<SysUser>().Where(a => a.Email == "287245177@qq.com") .OrderBy(a => a.CreateDatetime) .Select(a => new SysUser { Email = a.Email }) .UpdateSelect(a => new SysUser { Email = "2530665632@qq.com" });
事務功能
con.Transaction(tc =>
{
var sysUserid = tc.QuerySet<SysUser>().Where(a => a.Email == "287245177@qq.com").Select(a => a.SysUserid).Get(); tc.CommandSet<SysUser>().Where(a => a.SysUserid == sysUserid).Delete(); tc.CommandSet<SysUser>().Insert(new SysUser { CreateDatetime = DateTime.Now, Email = "287245177@qq.com", Mobile = "13536059332", RealName = "大笨貞", SysUserid = Guid.NewGuid().ToString("N"), UserName = "fengshuzhen", UserStatus = 1, UserType = 1, Password = "asdasdad" }); });
最後來一個完整的DEMO
using (var con = new SqlConnection("Data Source=192.168.13.46;Initial Catalog=SkyChen;Persist Security Info=True;User ID=sa;Password=123456789")) { con.CommandSet<SysUser>().Insert(new SysUser { CreateDatetime = DateTime.Now, Email = "287245177@qq.com", SysUserid = Guid.NewGuid().ToString("N"), UserName = "chengong", }); var model = con.QuerySet<SysUser>().Where(a => a.Email == "287245177@qq.com").Get(); con.CommandSet<SysUser>().Where(a => a.SysUserid == model.SysUserid) .Update(a => new SysUser { Email = "2548987@qq.com" }); con.CommandSet<SysUser>().Where(a => a.SysUserid == model.SysUserid).Delete(); }
其餘
除了簡單的CURD還有Count、Sum、Exists
結束
第一個版本有未完善的地方,若是你們有很好的建議歡迎隨時向我提,但願獲得你們的建議後能良好的改善升級
正確理解CAP定理
前言
CAP的理解我也看了不少書籍,也看了很多同行的博文,基本每一個人的理解都不同,而布魯爾教授得定義又太過的簡單,沒有具體描述和場景案例分析。所以本身參考部分資料梳理了一篇與你們互相分享一下。
標題寫了正確理解,或許某些點不是百分百正確或者有歧義,可是但願與各位分享討論後達到最終正確,
簡介
CAP定理,又被稱做布魯爾定理(Brewer's theorem),是埃裏克·布魯爾教授在2000 年提出的一個猜測,它指出對於一個分佈式系統來講,不可能同時知足如下三點:
- Consistency(一致性): where all nodes see the same data at the same time.(全部節點在同一時間具備相同的數據)
- Availability(可用性): which guarantees that every request receives a response about whether it succeeded or failed.(保證每一個請求無論成功或者失敗都有響應)
- Partition tolerance(分隔容忍): where the system continues to operate even if any one part of the system is lost or fails.(系統中任意信息的丟失或失敗不會影響系統的繼續運做)
不少書籍與文章引用Robert Greiner在2014年8月寫的一篇博文 http://robertgreiner.com/2014/08/cap-theorem-revisited/。相比與看着布魯爾教授一臉懵逼的定義,Robert Greiner的更加容易理解。
定義
原文:In a distributed system (a collection of interconnected nodes that share data.), you can only have two out of the following three guarantees across a write/read pair: Consistency, Availability, and Partition Tolerance - one of them must be sacrificed.
翻譯:在一個分佈式系統(指互相鏈接並共享數據的節點的集合)中,當涉及讀寫操做時,只能保證一致性(Consistence)、可用性(Availability)、分區容錯性(Partition Tolerance)三者中的兩個,另一個必須被犧牲。
關鍵字:interconnected nodes(互連節點)、share data(共享數據)、a write/read pair(讀/寫)
從上面一段話,有幾個,也就是說咱們聊CAP定理的時候,是在具備數據讀寫、數據共享和節點互連的前提下,對上面三者選其二,也是建議咱們不要花費時間與精力同時知足三者。
舉例說明,web集羣、memcached集羣不屬於討論對象
- web集羣只是資源複製分配在不一樣的節點上,然而節點間沒有互連、也沒有數據共享(sessionid、memory cache)。
- memcached集羣數據存儲是經過客戶端實現哈希一致性,可是集羣節點間不互連的,也沒有數據共享。
總得來講,CAP定理討論的並非分佈式系統全部的功能。
一致性(Consistency)
原文:A read is guaranteed to return the most recent write for a given client.
翻譯:對某個指定的客戶端來講,讀操做保證可以返回最新的寫操做結果
關鍵字:a given client(指定的客戶端)。
這裏的一致性與咱們日常瞭解ACID的一致性有點誤差,ACID的一致性關注的是數據庫的數據完整性。
上面定義沒說明是全部節點必須在同一時間數據一致,而關注點在客戶端,假若有個場景,您在ATM(客戶端)往某張銀行卡存500元后,馬上在ATM發起查詢餘額的時候會顯示加了500元后的餘額,隨後咱們也能把這500元取出來。查詢餘額讀操做能夠是寫後馬上讀的主庫,也或者寫後某個時間段事後(中途無寫)讀從庫。
可用性(Availability)
原文:A non-failing node will return a reasonable response within a reasonable amount of time (no error or timeout).
翻譯:非故障節點將在合理的時間內返回合理的響應(不是錯誤或超時)。
關鍵字:non-failing node(非故障節點)、reasonable response(合理的響應)
這裏的可用性和咱們日常所理解的高可用性有點誤差,高可用性指系統無中斷的執行其功能的能力。
已故障的節點就不具備可用性了,由於請求結果要麼error要麼 timeout。合理的響應沒有說明是成功仍是失敗,可是響應應該具備是否成功的精確描述。例如咱們讀取sql server集羣的某從庫,同步須要時間,讀取出來可能不是最新的數據,但倒是合理的響應。
分區容錯性(Partition tolerance)
原文:The system will continue to function when network partitions occur.
翻譯:當網絡分區發生時,系統將繼續正常運做
關鍵字:continue to function(繼續正常運做)
假如作了一個redis的一主兩從的集羣,某天某個從節點由於網絡故障變成不可用,可是另外的一主一從仍然能正常運做,那麼咱們認爲它具備分區容錯性。
CA-犧牲分區容錯性
做爲分佈式系統,分區必然總會發生(2年1次50分鐘仍是1年3次共10分鐘?),所以認爲CAP的討論是大部分創建在P確立前提下。假設咱們犧牲了P這個時候由於網絡故障發生了分區致使節點不可用,這個時候請求響應了error、timeout,與可用性的定義相沖突了。
可是,咱們又假如分區大部分時間是不存在的,這時對單節點的讀\寫,那麼就無需做出C、A的取捨。可是上面說分區總會發生這不互相矛盾麼,仍是取捨。假如1年時間內99.99%時間是正常的,不可用時間爲0.01%(52.56分鐘)不可用,若這個時間屬於業務接受範圍,或者只在某個地區(華南、華北、華中?)有影響,那麼CA也是能夠選擇的。
PC-犧牲可用性
最典型的案例是RDBMS集羣與Redis集羣,這兩種都是利用主從複製實現讀寫分離的方案。假如二者都是創建一主多從的集羣,在主節點寫入數據,爲了保證隨後的讀操做獲取最新數據(一致性),這個讀操做仍會請求主節點(讀寫分離的複雜點在從庫同步不及時致使業務的異常,爲了保證業務的正常性寫後的讀會請求主庫),某個從節點掛了可是隻要主節點和其餘從節點仍然正常運做,就知足分區容錯性。可是哪天主節點由於網絡故障致使寫操做的error或者timeout,那麼這個系統就不可用了(犧牲可用性)。
這個時候能夠引入其餘功能和機制完成,例如Redis哨兵模式、故障轉移功能。
PA-犧牲一致性
最典型的案例是Cassanda集羣和Riak集羣,這種類型的分佈式數據庫,能夠任意節點寫入,任意節點讀取,看成爲集羣出現,不管寫入哪一個節點,都將會把該節點的數據同步到其餘節點上,由於這種同步方式,讀取數據時只要訪問一個節點就足夠了(喜歡任意訪問也不攔着你),可是由於其餘節點數據同步緣由,數據可能並非最新的(犧牲一致性)。若是當前節點由於網絡異常致使分區變得不可用(不管讀\寫),能夠轉移訪問節點(可用性)。
另外這裏說的犧牲一致性,並不表明放棄一致性,而PA選擇的是最終一致性(系統中全部的數據副本,在通過一段時間的同步後,最終可以達到一個一致的狀態)
總結
上面涉及「犧牲」字眼,並不表明非此即彼的選擇,能夠根據子系統、模塊之間的設計上進行混搭使用(例如PA和PC、CA和PC)。
本文對CAP定理作了一個簡單的梳理描述,參考了部分書籍和文章加上本身的理解但願能夠跟你們作個分享,若是有不一樣建議和見解包括文章內描述錯誤,請在下方評論指出,我將及時做出修改。
Quartz.NET的使用(附源碼)
簡介
雖然Quartz.NET被園子裏的大神們寫爛了,本身仍是整理了一篇,結尾會附上源碼地址。
Quartz.NET是一款功能齊全的開源做業調度框架,小至的應用程序,大到企業系統均可以適用。Quartz是做者James House用JAVA語言編寫的,而Quartz.NET是從Quartz移植過來的C#版本。
在通常企業,能夠利用Quartz.Net框架作各類的定時任務,例如,數據遷移、跑報表等等。
另外還有一款Hangfire https://www.hangfire.io/,也是做業調度框架,有自帶監控web後臺,比Quartz.Net更加易用,簡單。可是Cron最低只支持到分鐘級。然而Hangfire不是今天的主角,有機會再介紹。
簡單例子
新建一個控制檯項目,經過Nuget管理下載Quartz包

using System; using System.Collections.Specialized; using Quartz; using Quartz.Impl; namespace QuartzDotNetDemo { class Program { static void Main(string[] args) { //建立一個調度器工廠 var props = new NameValueCollection { { "quartz.scheduler.instanceName", "QuartzDotNetDemo" } }; var factory = new StdSchedulerFactory(props); //獲取調度器 var sched = factory.GetScheduler(); sched.Start(); //定義一個任務,關聯"HelloJob" var job = JobBuilder.Create<HelloJob>() .WithIdentity("myJob", "group1") .Build(); //由觸發器每40秒觸發執行一次任務 var trigger = TriggerBuilder.Create() .WithIdentity("myTrigger", "group1") .StartNow() .WithSimpleSchedule(x => x .WithIntervalInSeconds(40) .RepeatForever()) .Build(); sched.ScheduleJob(job, trigger); } } public class HelloJob : IJob { public void Execute(IJobExecutionContext context) { Console.WriteLine("你好"); } } }
一個簡單的調度任務流程以下:
概念
有幾個重要類和概念須要瞭解一下:
- IScheduler - 與調度器交互的主要API.
- IJob -由執行任務實現的接口。
- IJobDetail - 定義Job實例
- ITrigger - 按照定義的時間讓任務執行的組件.
- JobBuilder - 用於定義或者建立JobDetai
- TriggerBuilder -用於定義或生成觸發器實例
他們之間的關係大概以下:
當有空閒線程同時,到了該執行的時間,那麼就會由Trigger去觸發綁定的Job執行它的Excute方法,假如此次沒執行完,卻到了下一次的運行時間,若是有空閒線程就仍然會再次執行。可是若是沒有空閒線程,會等到騰出空閒的線程纔會執行,可是超過quartz.jobStore.misfireThreshold設置的時間就會放棄此次的運行。
固然也能夠在Job貼上DisallowConcurrentExecution標籤讓Job進行單線程跑,避免沒跑完時的重複執行。
改造
在第一個簡單的demo裏是沒法良好的在實際中使用,所以咱們須要改造一下。
須要的第三方包:
- Autofac version="4.6.2"
- Autofac.Extras.Quartz version="3.4.0"
- Common.Logging version="3.4.1"
- Common.Logging.Core version="3.4.1"
- Common.Logging.Log4Net1213 version="3.4.1"
- log4net version="2.0.3"
- Newtonsoft.Json version="10.0.3"
- Quartz version="2.6.1"
- Topshelf version="4.0.3"
- Topshelf.Autofac version="3.1.1"
- Topshelf.Log4Net version="3.2.0"
- Topshelf.Quartz version="0.4.0.1"
Topshelf
Topshelf是一款爲了方便安裝部署在Windows系統下而誕生的宿主框架,它基於控制檯項目,爲開發人員帶來更方便的調試和部署。
官網:https://topshelf.readthedocs.io/en/latest/index.html
那咱們能夠在Program.cs裏寫入如下代碼:

using Topshelf; using Topshelf.Autofac; namespace QuartzDotNetDemo { class Program { static void Main(string[] args) { HostFactory.Run(config => { config.SetServiceName(JobService.ServiceName); config.SetDescription("Quartz.NET的demo"); config.UseLog4Net(); config.UseAutofacContainer(JobService.Container); config.Service<JobService>(setting => { JobService.InitSchedule(setting); setting.ConstructUsingAutofacContainer(); setting.WhenStarted(o => o.Start()); setting.WhenStopped(o => o.Stop()); }); }); } } }
JobService
此類用來讀取配置信息、初始化調度任務和注入ioc容器

public class JobService { #region 初始化 private static readonly ILog Log = LogManager.GetLogger(typeof(JobService)); private const string JobFile = "JobsConfig.xml"; private static readonly string JobNamespceFormat; public static readonly string ServiceName; private static readonly Jobdetail[] JobList; public static IContainer Container; static JobService() { var job = JobFile.XmlToObject<JobsConfig>(); ServiceName = job.Quartz.ServiceName; JobNamespceFormat = job.Quartz.Namespace; JobList = job.Quartz.JobList.JobDetail; Log.Info("Jobs.xml 初始化完畢"); InitContainer(); } #endregion /// <summary> /// 初始化調度任務 /// </summary> /// <param name="svc"></param> public static void InitSchedule(ServiceConfigurator<JobService> svc) { svc.UsingQuartzJobFactory(Container.Resolve<IJobFactory>); foreach (var job in JobList) { svc.ScheduleQuartzJob(q => { q.WithJob(JobBuilder.Create(Type.GetType(string.Format(JobNamespceFormat, job.JobName))) .WithIdentity(job.JobName, ServiceName) .Build); q.AddTrigger(() => TriggerBuilder.Create() .WithCronSchedule(job.Cron) .Build()); Log.InfoFormat("任務 {0} 已完成調度設置", string.Format(JobNamespceFormat, job.JobName)); }); } Log.Info("調度任務 初始化完畢"); } /// <summary> /// 初始化容器 /// </summary> private static void InitContainer() { var builder = new ContainerBuilder(); builder.RegisterModule(new QuartzAutofacFactoryModule()); builder.RegisterModule(new QuartzAutofacJobsModule(typeof(JobService).Assembly)); builder.RegisterType<JobService>().AsSelf(); var execDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); var files = Directory.GetFiles(execDir, "QuartzDotNetDemo.*.dll", SearchOption.TopDirectoryOnly); if (files.Length > 0) { var assemblies = new Assembly[files.Length]; for (var i = 0; i < files.Length; i++) assemblies[i] = Assembly.LoadFile(files[i]); builder.RegisterAssemblyTypes(assemblies) .Where(t => t.GetInterfaces().ToList().Contains(typeof(IService))) .AsSelf() .InstancePerLifetimeScope(); } Container = builder.Build(); Log.Info("IOC容器 初始化完畢"); } public bool Start() { Log.Info("服務已啓動"); return true; } public bool Stop() { Container.Dispose(); Log.Info("服務已關閉"); return false; } }
觸發器類型
一共有4種:
- WithCalendarIntervalSchedule
- WithCronSchedule
- WithDailyTimeIntervalSchedule
- WithSimpleSchedule
在項目中使用的是WithCronSchedule,由於cron表達式更加靈活、方便。
Cron表達式
字段名 | 是否必填 | 值範圍 | 特殊字符 |
---|---|---|---|
Seconds | YES | 0-59 | , - * / |
Minutes | YES | 0-59 | , - * / |
Hours | YES | 0-23 | , - * / |
Day of month | YES | 1-31 | , - * ? / L W |
Month | YES | 1-12 or JAN-DEC | , - * / |
Day of week | YES | 1-7 or SUN-SAT | , - * ? / L # |
Year | NO | empty, 1970-2099 | , - * / |
例子:
"0 0/5 * * * ?"
---- 每5分鐘觸發一次
"10 0/5 * * * ?
"
-----每5分鐘觸發一次,每分鐘10秒(例如:10:00:10 am,10:05:10,等等)
"0 0/30 8-9 5,20 * ?" ----在每月的第5到20個小時之間,每隔半小時就會觸發一個觸發點。請注意,觸發器不會在上午10點觸發,僅在8點,8點30分,9點和9點30分
BaseJob
咱們定義一個BaseJob寫入公共處理邏輯,例如:業務邏輯禁用、公共異常日誌消息推送等等。再由具體的Job去繼承重寫基類的ExecuteJob,簡單的適配器模式運用。

public abstract class BaseJob : IJob { protected readonly CommonService CommonService; protected BaseJob(CommonService commonService) { CommonService = commonService; } public void Execute(IJobExecutionContext context) { //公共邏輯 CommonService.Enabled(); //job邏輯 ExecuteJob(context); } public abstract void ExecuteJob(IJobExecutionContext context); }
結束
最後按照慣例雙手奉上demo源碼。https://github.com/SkyChenSky/QuartzDotNetDemo.git
整理本身的.net工具庫
前言
今天我會把本身平日整理的工具庫給開放出來,提供給有須要的朋友,若是有朋友日常也在積累歡迎提意見,我會樂意採納並補充完整。按照慣例在文章結尾給出地址^_^。
以前我開放其餘源碼的時候(Framework.MongoDB、AutoBuildEntity),都有引用個人Framework工具庫,可是爲何如今纔開放出來呢緣由有幾點:
- 相對簡單日常收集的朋友應該有不少
- 真想要能夠去我開源代碼反編譯
- 被評論說Framework.dll有貓膩
首先借用社區裏的88大哥一句話,開源的意義在於開源以後有其餘公司能夠深刻底層,而後推出本身的產品和工具,這樣生態就會愈來愈龐大。然而個人東西開源出去,爲了配合文章讓讀者更方便的去理解,同時但願在個人基礎上找到問題並改進。
作技術的,主要是開拓思路,經過模仿與交流後,你領悟的是你的,你學習到別人的也是你的。
可是!我並不提倡「麪包已經給你了,非要等別人嚼碎了再喂到你嘴裏?」。源碼都已經給出去了,有問題只要主動調試一下,實在想知道里面幹了什麼就反編譯一下,主動邁出這一步,問題解決了,想了解的瞭解到了,獲得的經驗和知識都是你的,何樂而不爲呢?
本文章不針對也不是爲了噴某人,一來我不但願本身「走歪路」告誡本身,二來提醒下剛入行的萌新。淨化.net環境從我作起吧。
Framework功能點
- 驗證標籤(中文、郵箱、身份證、手機號)
- 集合根據條件去重擴展方法
- EmitMapper封裝
- 加解密擴展方法
- 字符串擴展方法
- Object擴展方法
- 類型轉換
- 本地緩存封裝
- Log4net的封裝
- HttpWeb的封裝
- 有序guid的封裝
- Json.net的封裝
推薦書籍
簡單介紹幾本書介紹給你們看看
- 大話設計模式(能夠反覆多讀幾遍)
- CLR via C#(工具書有疑問就看)
- 重構 改善既有代碼的設計
- NoSql精粹
- 微服務設計
結尾
雙手奉上源碼 https://github.com/SkyChenSky/Framework.Toolkits 。
下圖是我在vs online上的源碼,代碼會在我整理好和文章一塊兒放出,可是裏面有部分完成度不高,因此得一步一步來。
GC的前世與此生
原文地址:http://kb.cnblogs.com/page/106720/
做者: spring yang
GC的前世與此生
雖然本文是以.NET做爲目標來說述GC,可是GC的概念並不是才誕生不久。早在1958年,由鼎鼎大名的圖林獎得主John McCarthy所實現的Lisp語言就已經提供了GC的功能,這是GC的第一次出現。Lisp的程序員認爲內存管理過重要了,因此不能由程序員本身來管理。
但後來的日子裏Lisp卻沒有成氣候,採用內存手動管理的語言佔據了上風,以C爲表明。出於一樣的理由,不一樣的人卻又不一樣的見解,C程序員認爲內存管理過重要了,因此不能由系統來管理,而且譏笑Lisp程序慢如烏龜的運行速度。的確,在那個對每個Byte都要精心計算的年代GC的速度和對系統資源的大量佔用使不少人的沒法接受。然後,1984年由Dave Ungar開發的Smalltalk語言第一次採用了Generational garbage collection的技術(這個技術在下文中會談到),可是Smalltalk也沒有獲得十分普遍的應用。
直到20世紀90年代中期GC才以主角的身份登上了歷史的舞臺,這不得不歸功於Java的進步,今日的GC已非吳下阿蒙。Java採用VM(Virtual Machine)機制,由VM來管理程序的運行固然也包括對GC管理。90年代末期.NET出現了,.NET採用了和Java相似的方法由CLR(Common Language Runtime)來管理。這兩大陣營的出現將人們引入了以虛擬平臺爲基礎的開發時代,GC也在這個時候愈來愈獲得大衆的關注。
爲何要使用GC呢?也能夠說是爲何要使用內存自動管理?有下面的幾個緣由:
一、提升了軟件開發的抽象度;
二、程序員能夠將精力集中在實際的問題上而不用分心來管理內存的問題;
三、能夠使模塊的接口更加的清晰,減少模塊間的偶合;
四、大大減小了內存人爲管理不當所帶來的Bug;
五、使內存管理更加高效。
總的說來就是GC能夠使程序員能夠從複雜的內存問題中擺脫出來,從而提升了軟件開發的速度、質量和安全性。
什麼是GC
GC如其名,就是垃圾收集,固然這裏僅就內存而言。Garbage Collector(垃圾收集器,在不至於混淆的狀況下也成爲GC)以應用程序的root爲基礎,遍歷應用程序在Heap上動態分配的全部對象[2],經過識別它們是否被引用來肯定哪些對象是已經死亡的、哪些仍須要被使用。已經再也不被應用程序的root或者別的對象所引用的對象就是已經死亡的對象,即所謂的垃圾,須要被回收。這就是GC工做的原理。爲了實現這個原理,GC有多種算法。比較常見的算法有Reference Counting,Mark Sweep,Copy Collection等等。目前主流的虛擬系統.NET CLR,Java VM和Rotor都是採用的Mark Sweep算法。
1、Mark-Compact 標記壓縮算法
簡單地把.NET的GC算法看做Mark-Compact算法。階段1: Mark-Sweep 標記清除階段,先假設heap中全部對象均可以回收,而後找出不能回收的對象,給這些對象打上標記,最後heap中沒有打標記的對象都是能夠被回收的;階段2: Compact 壓縮階段,對象回收以後heap內存空間變得不連續,在heap中移動這些對象,使他們從新從heap基地址開始連續排列,相似於磁盤空間的碎片整理。
Heap內存通過回收、壓縮以後,能夠繼續採用前面的heap內存分配方法,即僅用一個指針記錄heap分配的起始地址就能夠。主要處理步驟:將線程掛起→肯定roots→建立reachable objects graph→對象回收→heap壓縮→指針修復。能夠這樣理解roots:heap中對象的引用關係錯綜複雜(交叉引用、循環引用),造成複雜的graph,roots是CLR在heap以外能夠找到的各類入口點。
GC搜索roots的地方包括全局對象、靜態變量、局部對象、函數調用參數、當前CPU寄存器中的對象指針(還有finalization queue)等。主要能夠歸爲2種類型:已經初始化了的靜態變量、線程仍在使用的對象(stack+CPU register) 。 Reachable objects:指根據對象引用關係,從roots出發能夠到達的對象。例如當前執行函數的局部變量對象A是一個root object,他的成員變量引用了對象B,則B是一個reachable object。從roots出發能夠建立reachable objects graph,剩餘對象即爲unreachable,能夠被回收 。
指針修復是由於compact過程移動了heap對象,對象地址發生變化,須要修復全部引用指針,包括stack、CPU register中的指針以及heap中其餘對象的引用指針。Debug和release執行模式之間稍有區別,release模式下後續代碼沒有引用的對象是unreachable的,而debug模式下須要等到當前函數執行完畢,這些對象纔會成爲unreachable,目的是爲了調試時跟蹤局部對象的內容。傳給了COM+的託管對象也會成爲root,而且具備一個引用計數器以兼容COM+的內存管理機制,引用計數器爲0時,這些對象纔可能成爲被回收對象。Pinned objects指分配以後不能移動位置的對象,例如傳遞給非託管代碼的對象(或者使用了fixed關鍵字),GC在指針修復時沒法修改非託管代碼中的引用指針,所以將這些對象移動將發生異常。pinned objects會致使heap出現碎片,但大部分狀況來講傳給非託管代碼的對象應當在GC時可以被回收掉。
2、 Generational 分代算法
程序可能使用幾百M、幾G的內存,對這樣的內存區域進行GC操做成本很高,分代算法具有必定統計學基礎,對GC的性能改善效果比較明顯。將對象按照生命週期分紅新的、老的,根據統計分佈規律所反映的結果,能夠對新、老區域採用不一樣的回收策略和算法,增強對新區域的回收處理力度,爭取在較短期間隔、較小的內存區域內,以較低成本將執行路徑上大量新近拋棄再也不使用的局部對象及時回收掉。分代算法的假設前提條件:
一、大量新建立的對象生命週期都比較短,而較老的對象生命週期會更長;
二、對部份內存進行回收比基於所有內存的回收操做要快;
三、新建立的對象之間關聯程度一般較強。heap分配的對象是連續的,關聯度較強有利於提升CPU cache的命中率,.NET將heap分紅3個代齡區域: Gen 0、Gen 一、Gen 2;
Heap分爲3個代齡區域,相應的GC有3種方式: # Gen 0 collections, # Gen 1 collections, #Gen 2 collections。若是Gen 0 heap內存達到閥值,則觸發0代GC,0代GC後Gen 0中倖存的對象進入Gen1。若是Gen 1的內存達到閥值,則進行1代GC,1代GC將Gen 0 heap和Gen 1 heap一塊兒進行回收,倖存的對象進入Gen2。
2代GC將Gen 0 heap、Gen 1 heap和Gen 2 heap一塊兒回收,Gen 0和Gen 1比較小,這兩個代齡加起來老是保持在16M左右;Gen2的大小由應用程序肯定,可能達到幾G,所以0代和1代GC的成本很是低,2代GC稱爲full GC,一般成本很高。粗略的計算0代和1代GC應當能在幾毫秒到幾十毫秒之間完成,Gen 2 heap比較大時,full GC可能須要花費幾秒時間。大體上來說.NET應用運行期間,2代、1代和0代GC的頻率應當大體爲1:10:100。
3、Finalization Queue和Freachable Queue
這兩個隊列和.NET對象所提供的Finalize方法有關。這兩個隊列並不用於存儲真正的對象,而是存儲一組指向對象的指針。當程序中使用了new操做符在Managed Heap上分配空間時,GC會對其進行分析,若是該對象含有Finalize方法則在Finalization Queue中添加一個指向該對象的指針。
在GC被啓動之後,通過Mark階段分辨出哪些是垃圾。再在垃圾中搜索,若是發現垃圾中有被Finalization Queue中的指針所指向的對象,則將這個對象從垃圾中分離出來,並將指向它的指針移動到Freachable Queue中。這個過程被稱爲是對象的復生(Resurrection),原本死去的對象就這樣被救活了。爲何要救活它呢?由於這個對象的Finalize方法尚未被執行,因此不能讓它死去。Freachable Queue平時不作什麼事,可是一旦裏面被添加了指針以後,它就會去觸發所指對象的Finalize方法執行,以後將這個指針從隊列中剔除,這是對象就能夠安靜的死去了。
.NET Framework的System.GC類提供了控制Finalize的兩個方法,ReRegisterForFinalize和SuppressFinalize。前者是請求系統完成對象的Finalize方法,後者是請求系統不要完成對象的Finalize方法。ReRegisterForFinalize方法其實就是將指向對象的指針從新添加到Finalization Queue中。這就出現了一個頗有趣的現象,由於在Finalization Queue中的對象能夠復生,若是在對象的Finalize方法中調用ReRegisterForFinalize方法,這樣就造成了一個在堆上永遠不會死去的對象,像鳳凰涅槃同樣每次死的時候均可以復生。
託管資源:
.NET中的全部類型都是(直接或間接)從System.Object類型派生的。
CTS中的類型被分紅兩大類——引用類型(reference type,又叫託管類型[managed type]),分配在內存堆上;值類型(value type),分配在堆棧上。如圖:
值類型在棧裏,先進後出,值類型變量的生命有前後順序,這個確保了值類型變量在退出做用域之前會釋放資源。比引用類型更簡單和高效。堆棧是從高地址往低地址分配內存。
引用類型分配在託管堆(Managed Heap)上,聲明一個變量在棧上保存,當使用new建立對象時,會把對象的地址存儲在這個變量裏。託管堆相反,從低地址往高地址分配內存,如圖:
.NET中超過80%的資源都是託管資源。
非託管資源:
ApplicationContext, Brush, Component, ComponentDesigner, Container, Context, Cursor, FileStream, Font, Icon, Image, Matrix, Object, OdbcDataReader, OleDBDataReader, Pen, Regex, Socket, StreamWriter, Timer, Tooltip, 文件句柄, GDI資源, 數據庫鏈接等等資源。可能在使用的時候不少都沒有注意到!
.NET的GC機制有這樣兩個問題:
首先,GC並非能釋放全部的資源。它不能自動釋放非託管資源。
第二,GC並非實時性的,這將會形成系統性能上的瓶頸和不肯定性。
GC並非實時性的,這會形成系統性能上的瓶頸和不肯定性。因此有了IDisposable接口,IDisposable接口定義了Dispose方法,這個方法用來供程序員顯式調用以釋放非託管資源。使用using語句能夠簡化資源管理。
示例:
///summary
/// 執行SQL語句,返回影響的記錄數
////summary
///param name="SQLString"SQL語句/param
///returns影響的記錄數/returns
publicstaticint ExecuteSql(string SQLString)
{
using (SqlConnection connection =new SqlConnection(connectionString))
{
using (SqlCommand cmd =new SqlCommand(SQLString, connection))
{
try
{
connection.Open();
int rows = cmd.ExecuteNonQuery();
return rows;
}
catch (System.Data.SqlClient.SqlException e)
{
connection.Close();
throw e;
}
finally
{
cmd.Dispose();
connection.Close();
}
}
}
}
當你用Dispose方法釋放未託管對象的時候,應該調用GC.SuppressFinalize。若是對象正在終結隊列(finalization queue), GC.SuppressFinalize會阻止GC調用Finalize方法。由於Finalize方法的調用會犧牲部分性能。若是你的Dispose方法已經對委託管資源做了清理,就不必讓GC再調用對象的Finalize方法(MSDN)。附上MSDN的代碼,你們能夠參考。
publicclass BaseResource : IDisposable
{
// 指向外部非託管資源
private IntPtr handle;
// 此類使用的其它託管資源.
private Component Components;
// 跟蹤是否調用.Dispose方法,標識位,控制垃圾收集器的行爲
privatebool disposed =false;
// 構造函數
public BaseResource()
{
// Insert appropriate constructor code here.
}
// 實現接口IDisposable.
// 不能聲明爲虛方法virtual.
// 子類不能重寫這個方法.
publicvoid Dispose()
{
Dispose(true);
// 離開終結隊列Finalization queue
// 設置對象的阻止終結器代碼
//
GC.SuppressFinalize(this);
}
// Dispose(bool disposing) 執行分兩種不一樣的狀況.
// 若是disposing 等於 true, 方法已經被調用
// 或者間接被用戶代碼調用. 託管和非託管的代碼都能被釋放
// 若是disposing 等於false, 方法已經被終結器 finalizer 從內部調用過,
//你就不能在引用其餘對象,只有非託管資源能夠被釋放。
protectedvirtualvoid Dispose(bool disposing)
{
// 檢查Dispose 是否被調用過.
if (!this.disposed)
{
// 若是等於true, 釋放全部託管和非託管資源
if (disposing)
{
// 釋放託管資源.
Components.Dispose();
}
// 釋放非託管資源,若是disposing爲 false,
// 只會執行下面的代碼.
CloseHandle(handle);
handle = IntPtr.Zero;
// 注意這裏是非線程安全的.
// 在託管資源釋放之後能夠啓動其它線程銷燬對象,
// 可是在disposed標記設置爲true前
// 若是線程安全是必須的,客戶端必須實現。
}
disposed =true;
}
// 使用interop 調用方法
// 清除非託管資源.
[System.Runtime.InteropServices.DllImport("Kernel32")]
privateexternstatic Boolean CloseHandle(IntPtr handle);
// 使用C# 析構函數來實現終結器代碼
// 這個只在Dispose方法沒被調用的前提下,才能調用執行。
// 若是你給基類終結的機會.
// 不要給子類提供析構函數.
~BaseResource()
{
// 不要重複建立清理的代碼.
// 基於可靠性和可維護性考慮,調用Dispose(false) 是最佳的方式
Dispose(false);
}
// 容許你屢次調用Dispose方法,
// 可是會拋出異常若是對象已經釋放。
// 不論你什麼時間處理對象都會覈查對象的是否釋放,
// check to see if it has been disposed.
publicvoid DoSomething()
{
if (this.disposed)
{
thrownew ObjectDisposedException();
}
}
// 不要設置方法爲virtual.
// 繼承類不容許重寫這個方法
publicvoid Close()
{
// 無參數調用Dispose參數.
Dispose();
}
publicstaticvoid Main()
{
// Insert code here to create
// and use a BaseResource object.
}
}
GC.Collect() 方法
做用:強制進行垃圾回收。
GC的方法:
名稱 |
說明 |
Collect() |
強制對全部代進行即時垃圾回收。 |
Collect(Int32) |
強制對零代到指定代進行即時垃圾回收。 |
Collect(Int32, GCCollectionMode) |
強制在 GCCollectionMode 值所指定的時間對零代到指定代進行垃圾回收 |
GC注意事項:
一、只管理內存,非託管資源,如文件句柄,GDI資源,數據庫鏈接等還須要用戶去管理。
二、循環引用,網狀結構等的實現會變得簡單。GC的標誌-壓縮算法能有效的檢測這些關係,並將再也不被引用的網狀結構總體刪除。
三、GC經過從程序的根對象開始遍從來檢測一個對象是否可被其餘對象訪問,而不是用相似於COM中的引用計數方法。
四、GC在一個獨立的線程中運行來刪除再也不被引用的內存。
五、GC每次運行時會壓縮託管堆。
六、你必須對非託管資源的釋放負責。能夠經過在類型中定義Finalizer來保證資源獲得釋放。
七、對象的Finalizer被執行的時間是在對象再也不被引用後的某個不肯定的時間。注意並不是和C++中同樣在對象超出聲明週期時當即執行析構函數
八、Finalizer的使用有性能上的代價。須要Finalization的對象不會當即被清除,而須要先執行Finalizer.Finalizer,不是在GC執行的線程被調用。GC把每個須要執行Finalizer的對象放到一個隊列中去,而後啓動另外一個線程來執行全部這些Finalizer,而GC線程繼續去刪除其餘待回收的對象。在下一個GC週期,這些執行完Finalizer的對象的內存纔會被回收。
九、.NET GC使用"代"(generations)的概念來優化性能。代幫助GC更迅速的識別那些最可能成爲垃圾的對象。在上次執行完垃圾回收後新建立的對象爲第0代對象。經歷了一次GC週期的對象爲第1代對象。經歷了兩次或更多的GC週期的對象爲第2代對象。代的做用是爲了區分局部變量和須要在應用程序生存週期中一直存活的對象。大部分第0代對象是局部變量。成員變量和全局變量很快變成第1代對象並最終成爲第2代對象。
十、GC對不一樣代的對象執行不一樣的檢查策略以優化性能。每一個GC週期都會檢查第0代對象。大約1/10的GC週期檢查第0代和第1代對象。大約1/100的GC週期檢查全部的對象。從新思考Finalization的代價:須要Finalization的對象可能比不須要Finalization在內存中停留額外9個GC週期。若是此時它尚未被Finalize,就變成第2代對象,從而在內存中停留更長時間。
Visual Studio Package 插件開發之自動生成實體工具
前言
這一篇是VS插件基於Visual Studio SDK擴展開發的,可能有些朋友看到【生成實體】內心可能會暗想,T4模板均可以作了、動軟不是已經作了麼、不就是讀庫保存文件到指定路徑麼……
我但願作的效果是:
1.工具集成到vs上
2.動做完成後體現到項目(添加、刪除項目項)
3.使用簡單、輕量、靈活(配置化)
4.不依賴ORM(前兩點有點像EF的DBFirst吧?)
文章最後會給上源碼地址。
下面是效果圖:
處理流程
以上是完整處理流程,我打算選擇部分流程來說。若是有對Visual Studio Package開發還沒一個認識,能夠看我以前寫的一篇《Visual Studio Package 插件開發》。
按鈕的位置
從上圖看見,按鈕是在選中項目右鍵彈出的菜單欄裏。
打開vsct文件,修改Group的Parent節點,修改對應的guid和id。
以前那邊文章有提到在文件:您的vs安裝目錄\VisualStudio2013\VSSDK\VisualStudioIntegration\Common\Inc\vsshlids.h 能夠找到須要修改的名稱,可是右鍵是沒有在文件裏定義,所以咱們須要另外換一種方法。
一、打開註冊表編輯器(打開運行窗口,輸入regedit),
二、路徑[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\12.0\General],
三、右擊-新建-DWORD(32-位)值(D),其命名爲EnableVSIPLogging
四、並將其值改成1。重啓VS,打開項目
五、按下Ctrl+Shift,對項目點擊右鍵,就會彈出窗口(以下圖)
Guid和CmdID的值就是咱們須要的,在vsct文件Symbols節點添加GuidSymbol項,value上圖的{D309F791-903F-11D0-9EFC-00A0C911004F},IDSymbol項value爲1026。
最後在Group的Parent節點的屬性guid和id改成與上面對應,下面代碼爲例子。

<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <Commands package="guidAutoBuildEntityPkg"> <Groups> <Group guid="guidAutoBuildEntityCmdSet" id="MyMenuGroup" priority="0x0600"> <Parent guid="guidCodeWindowRightClickCmdSet" id="CodeWindowRightClickMenu"/> </Group> </Groups> </Commands> <Symbols> <GuidSymbol name="guidAutoBuildEntityPkg" value="{c095f8f8-3f87-4eac-8dc0-44939a85b2f2}" /> <GuidSymbol name="guidCodeWindowRightClickCmdSet" value="{D309F791-903F-11D0-9EFC-00A0C911004F}"> <IDSymbol name="CodeWindowRightClickMenu" value="1026" /> </GuidSymbol> </Symbols> </CommandTable>
讀取選中項目信息
重點是DTE 接口的使用,MSDN的描述是:DTE 接口Visual Studio 自動化對象模型中的頂級對象。強大到當前開發環境中任何屬性能夠拿到例如:當前打開的文檔集合,解決方案下的項目信息……剩下本身看,傳送門
下面是代碼示例:

var dte = (DTE)GetService(typeof(SDTE)); /// <summary> /// 獲取選中項目的信息 /// </summary> /// <param name="dte"></param> /// <returns></returns> public static SelectedProject GetSelectedProjectInfo(this DTE dte) { var selectedItems = dte.SelectedItems; var projectName = (from SelectedItem item in selectedItems select item.Name).ToList(); if (!selectedItems.MultiSelect && selectedItems.Count == 1) { var selectProject = selectedItems.Item(projectName.First()); var projectFileList = (from ProjectItem projectItem in selectProject.Project.ProjectItems where projectItem.Name.EndsWith(".cs") select Path.GetFileNameWithoutExtension(projectItem.Name)).ToList(); return new SelectedProject(selectProject.Project.FullName, selectProject.Project, projectFileList); } return null; }
讀取實體配置信息
配置存放兩點信息:數據庫鏈接、類文件模版,同時咱們約定存放在項目根目錄下。以下圖
那麼,剩下就是XML的基本獲取處理了。__entity.xml的模版在源碼裏,可自行拷貝去須要使用的項目,如下是代碼示例:

private void AutoBuildEntityEvent(object sender, EventArgs e) { var autoBuildEntityContent = new AutoBuildEntityContent (); //讀取選中項目下的配置信息 var entityXmlModel = new EntityXml(autoBuildEntityContent.SelectedProject.EntityXmlPath); entityXmlModel.Load(); autoBuildEntityContent.EntityXml = entityXmlModel; new MainForm(autoBuildEntityContent).ShowDialog(); } public class EntityXml { private readonly string _path; public EntityXml(string path) { _path = path; } public string ConnString { get; private set; } public string EntityTemplate { get; private set; } /// <summary> /// 讀取_entity.xml /// </summary> /// <returns></returns> public EntityXml Load() { var xml = new XmlDocument(); xml.Load(_path); var autoEntityNode = xml.SelectSingleNode("AutoEntity"); if (autoEntityNode != null) { var connStringNode = autoEntityNode.SelectSingleNode("ConnString"); if (connStringNode != null) { ConnString = connStringNode.InnerText; } var templatesNodes = autoEntityNode.SelectSingleNode("Template"); if (templatesNodes != null) { EntityTemplate = templatesNodes.InnerText; } } return this; } }
讀取物理表
查詢當前數據庫的表集合,傳給窗體作列表展現,直接上代碼:

/// <summary> /// 物理表 /// </summary> public class DbTable { public string TableName { get; private set; } public List<TableColumn> Columns { get; set; } private readonly string _conn; public DbTable(string conn) { _conn = conn; } public DbTable(string tableName, List<TableColumn> columns) { TableName = tableName; Columns = columns; } public List<string> QueryTablesName() { var result = SqlHelper.Query(_conn, @"SELECT name FROM sysobjects WHERE xtype IN ( 'u','v' ); "); return (from DataRow row in result.Rows select row[0].ToString()).ToList(); } public List<DbTable> GetTables(List<string> tablesName) { if (!tablesName.Any()) return new List<DbTable>(); var t = new TableColumn(_conn); var columns = t.QueryColumn(tablesName); return columns.GroupBy(a => a.TableName).Select(a => new DbTable(a.Key, a.ToList())).ToList(); } }
讀取表結構
選擇響應的表後,查詢出對應的表結構,通常實體的所須要的信息有:列名、列備註、類型、長度、是否主鍵、是否自增加、是否可空,繼續上代碼:

/// <summary> /// 物理表的列信息 /// </summary> public class TableColumn { private readonly string _connStr; public TableColumn() { } public TableColumn(string connStr) { _connStr = connStr; } public string TableName { get; private set; } public string Name { get; private set; } public string Remark { get; private set; } public string Type { get; private set; } public int Length { get; private set; } public bool IsIdentity { get; private set; } public bool IsKey { get; private set; } public bool IsNullable { get; private set; } public string CSharpType { get { return SqlHelper.MapCsharpType(Type, IsNullable); } } /// <summary> /// 查詢列信息 /// </summary> /// <param name="tablesName"></param> /// <returns></returns> public List<TableColumn> QueryColumn(List<string> tablesName) { #region 表結構 var paramKey = string.Join(",", tablesName.Select((a, index) => "@p" + index)); var paramVal = tablesName.Select((a, index) => new SqlParameter("@p" + index, a)).ToArray(); var sql = string.Format(@"SELECT obj.name AS tablename , col.name , ISNULL(ep.[value], '') remark , t.name AS type , col.length , COLUMNPROPERTY(col.id, col.name, 'IsIdentity') AS isidentity , CASE WHEN EXISTS ( SELECT 1 FROM dbo.sysindexes si INNER JOIN dbo.sysindexkeys sik ON si.id = sik.id AND si.indid = sik.indid INNER JOIN dbo.syscolumns sc ON sc.id = sik.id AND sc.colid = sik.colid INNER JOIN dbo.sysobjects so ON so.name = si.name AND so.xtype = 'PK' WHERE sc.id = col.id AND sc.colid = col.colid ) THEN 1 ELSE 0 END AS iskey , col.isnullable FROM dbo.syscolumns col LEFT JOIN dbo.systypes t ON col.xtype = t.xusertype INNER JOIN dbo.sysobjects obj ON col.id = obj.id AND obj.xtype IN ( 'U', 'v' ) AND obj.status >= 0 LEFT JOIN dbo.syscomments comm ON col.cdefault = comm.id LEFT JOIN sys.extended_properties ep ON col.id = ep.major_id AND col.colid = ep.minor_id AND ep.name = 'MS_Description' LEFT JOIN sys.extended_properties epTwo ON obj.id = epTwo.major_id AND epTwo.minor_id = 0 AND epTwo.name = 'MS_Description' WHERE obj.name IN ({0});", paramKey); #endregion var result = SqlHelper.Query(_connStr, sql, paramVal); return (from DataRow row in result.Rows select new TableColumn { IsIdentity = Convert.ToBoolean(row["isidentity"]), IsKey = Convert.ToBoolean(row["iskey"]), IsNullable = Convert.ToBoolean(row["isnullable"]), Length = Convert.ToInt32(row["length"]), Name = row["name"].ToString(), Remark = row["remark"].ToString(), TableName = row["tablename"].ToString(), Type = row["type"].ToString() }).ToList(); } }
根據模板生成代碼
開始我是嘗試用T4的,發現不方便,繁雜的聲明。所以我選擇了nVelocity,這裏不作太多介紹,附上相關文章學習,傳送門

// <summary> /// 初始化模板引擎 /// </summary> public static string ProcessTemplate(string template, Dictionary<string, object> param) { var templateEngine = new VelocityEngine(); templateEngine.SetProperty(RuntimeConstants.RESOURCE_LOADER, "file"); templateEngine.SetProperty(RuntimeConstants.INPUT_ENCODING, "utf-8"); templateEngine.SetProperty(RuntimeConstants.OUTPUT_ENCODING, "utf-8"); templateEngine.SetProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, AppDomain.CurrentDomain.BaseDirectory); var context = new VelocityContext(); foreach (var item in param) { context.Put(item.Key, item.Value); } templateEngine.Init(); var writer = new StringWriter(); templateEngine.Evaluate(context, writer, "mystring", template); return writer.GetStringBuilder().ToString(); }
以前已經拿到的文件模版,經過上面的方法輸出類文本,保存到選中項目的根目錄下。

操做項目
終於到了最後一步了,部分人覺得保存了文件後就完事了,最後經過包含文件就完事了。咱們仍是有點追求的,既然作成了插件就要更加的方便化。
經過以前[讀取選中項目信息]步驟拿到的EnvDTE.Project ProjectDte,使用如下擴展方法進行添加、刪除項目項。

附加
部分同窗可能想調試的時候會出現:沒法直接啓動「類庫輸出類型」項目,能夠在項目屬性-調試配置:
1.啓動配置外部程序:C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe
2.命令行參數:/rootsuffix Exp
估計有同窗會製做本身的圖標,另外附上兩條icon製做的網站:
http://iconfont.cn/search/index
http://www.easyicon.net/covert/
結尾
整篇文章的技術難點並很少,可是由於插件開發的資料相對較少,80%的時間花去找接口文檔、找資料。
此工具的原型是公司架構師的,公司全部開發都在用,可是他把源碼丟了………………好奇心使我從新實現了一份,固然了,說不定哪天帶團隊的時候會用上。
最後雙手奉上源碼,並非什麼牛逼的東西,但願能夠幫助須要的同窗。https://github.com/SkyChenSky/AutoBuildEntity