前言
本節我們來看看ASP.NET Core MVC中比較常用的功能,對於導入和導出目前仍在探索中,項目需要自定義列合並,所以事先探索了如何在ASP.NET Core MVC進行導入、導出,更高級的內容還需等我學習再給出。
EntityFramework Core
在學習ASP.NET Core MVC之前我們來看看在EF Core中如何更新對象指定屬性,這個問題之前我們已經探討過,但是還是存在一點問題,請往下看。
public void Update(T entity, params Expression<Func<T, object>>[] properties) { _context.Entry(entity).State = EntityState.Unchanged; foreach (var property in properties) { var propertyName = ExpressionHelper.GetExpressionText(property); _context.Entry(entity).Property(propertyName).IsModified = true; } }
上述方法可以用來更新對象指定屬性,使用lambda來指定需要更新的屬性,如下:
[HttpGet("[action]")] public IActionResult Index() { var blog = new Blog() { Id = 1, Name = "Jeffcky1" }; _blogRepository.Update(blog, d => d.Name); _blogRepository.SaveChanges(); }
但是當更新數值類型時會解析不到該屬性,如下:
var blog = new Blog() { Id = 1, Count = 1 }; _blogRepository.Update(blog, d => d.Count); _blogRepository.SaveChanges();
此時得到該屬性名稱為空字符串
lambda為何解析不到呢?原來它進行了Convert如下:
既然是將其轉換成了Convert,那么在表達式樹中應該用其節點類型,如下:
此時我們需要判斷其節點類型是否已經經過Convert即可,最終代碼如下:
public void Update(T entity, params Expression<Func<T, object>>[] properties) { EntityEntry<T> entry = _context.Entry(entity); entry.State = EntityState.Unchanged; foreach (var property in properties) { string propertyName = ""; Expression bodyExpression = property.Body; if (bodyExpression.NodeType == ExpressionType.Convert && bodyExpression is UnaryExpression) { Expression operand = ((UnaryExpression)property.Body).Operand; propertyName = ((MemberExpression)operand).Member.Name; } else { propertyName = ExpressionHelper.GetExpressionText(property); } entry.Property(propertyName).IsModified = true; } }
此時將能正確解析到數值類型名稱,如下:
為何要封裝這一層,只是不想首先查詢出對象然后再來進行更新屬性,其兩步操作合並為一步豈不爽哉。當然我們也可以不通過lambda來實現,直接給出屬性集合,然后遍歷即可,大概如下:
public void Update(T entity, params object[] properties) { _context.ChangeTracker.TrackGraph(entity, e => { e.Entry.State = EntityState.Unchanged; if ((e.Entry.Entity as T) != null) { foreach (var p in properties) { _context.Entry(e.Entry.Entity as T).Property(p.ToString()).IsModified = true; } } }); }
Serilog日志輸出
目前比較流行的日志框架則是Log4net、NLog,之前也一直用的Log4net,但是在.net core中已經內置了日志框架Serilog,在github上也已加星不少,想必比較強大,既然這樣為何不使用內置的呢,免去再用其他日志框架還要配置的麻煩。
創建默認項目在Startup中已經注入日志,如下:
loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug();
當利用dotnet.exe調試時在控制台會顯示調試各種信息,要是直接運行那該如何呢?在web中的配置文件web.config中,如下:
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/> </system.webServer>
我們需要設置 stdoutLogEnabled="true" 來啟動調試,此時運行項目你會發現屁都沒有,此時我們查看windows日志發現根本無法創建文件夾,想必是沒有權限的緣故。
此時我們需要手動創建logs文件夾,最終運行程序則會自動將所有信息寫入到日志文件中,如下:
是不是這樣就完了呢,這樣設置則會將所有信息都會寫入日志那樣豈不是額外做些無用功,看看如下生成的日志。
光說不練假把式,來,我們來實現一個,讓你見識見識Serilog的強大。先定義一個自定義輸出格式的Option並創建輸出日志文件夾。
public class JeffckyLogOptions { public string LogPath { get; set; } = @"C:\Jeffcky_StudyEFCore\logs"; public string PathFormat { get; set; } public static void EnsurePreConditions(JeffckyLogOptions options) { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (string.IsNullOrWhiteSpace(options.LogPath)) { throw new ArgumentException("系統日志文件存儲路徑未配置", nameof(options.LogPath)); } if (string.IsNullOrWhiteSpace(options.PathFormat)) { throw new ArgumentException("系統日志文件名稱未配置", nameof(options.PathFormat)); } if (!Directory.Exists(options.LogPath)) { Directory.CreateDirectory(options.LogPath); } } }
內置實現日志的接口.net core已經給出,所以我們需要獲取內置日志接口服務並實現自定義擴展方法。
app.ApplicationServices.GetService(typeof(ILoggerFactory));
此時上述app來源於 IApplicationBuilder 此時我們則只需要實現該接口的自定義擴展方法。我們開始下載Serilog程序包
此時我們需要對日志通過 LoggerConfiguration 類進行初始化配置。
接下來則是將日志寫到文件中我們可以自定義格式,此時再下載如下程序包。
里面有個 RollingFile 方法來自定輸出日志模板,自此我們就有了如下代碼:
var serilog = new LoggerConfiguration() .MinimumLevel.Debug() .Enrich.FromLogContext() .WriteTo.RollingFile(Path.Combine(options.LogPath, options.PathFormat), outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {Message}{NewLine}{Exception}");
我們獲取.net core內置的日志接口服務。
ILoggerFactory loggerFactory = (ILoggerFactory)app.ApplicationServices.GetService(typeof(ILoggerFactory));
同時設置我們輸出日志的路徑選項。
JeffckyLogOptions.EnsurePreConditions(options);
接下來將Serilog創建的日志添加到內置日志服務中,如下:
loggerFactory.AddSerilog(serilog.CreateLogger());
當然上述AddSerilog方法時擴展方法,此時我們仍需要添加如下擴展程序包:
最后還差一步則是日志的生命周期,灰常重要,為什么說灰常重要,我們要確保當程序關閉時或者系統出問題時所有在內存緩沖區的日志都將輸送到日志文件夾中,如下:
// Ensure any buffered events are sent at shutdown IApplicationLifetime appLifetime = (IApplicationLifetime)app.ApplicationServices.GetService(typeof(IApplicationLifetime)); if (appLifetime != null) { appLifetime.ApplicationStopped.Register(Log.CloseAndFlush); }
一氣呵成,最終我們在Startup的如下方法中進行調用即可:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) {...}
利用Serilog添加自定日志格式輸出,進行如下調用:
app.UseJeffckySelfDefineLog(new JeffckyLogOptions() { LogPath = Configuration[nameof(JeffckyLogOptions.LogPath)], PathFormat = "Jeffcky_StudyEFCore_{Date}.log" });
當然還有其他強大功能,比如Serilog中可以獲取到上下文,這樣的話我們就可以過濾對於那個應用程序使用對應的日志輸出級別以及其他,如下:
serilog = serilog.Filter.ByIncludingOnly((e) => { var context = e.Properties["SourceContext"].ToString(); return (context.StartsWith("\"Your Applicion Name") || e.Level == LogEventLevel.Warning || e.Level == LogEventLevel.Error || e.Level == LogEventLevel.Fatal); });
說一千到一萬,出現結果才是真理,我們一起來看看。
看看日志輸出信息,你會看到干凈的日志輸出。
總結
本節我們詳細講解了.net core中新生代日志框架-Serilog,別抱着Log4net不放了,Serilog你值得擁有,跟着老大一直學習中,漲漲見識!因為項目中會用到批量導入和導出,所以在研究導出、導入,下節可能會講到導入、導出在ASP.NET Core MVC中,也有可能會講SQL Server中的最后幾節關於死鎖的內容,不管怎樣都是在積累知識,你說呢,敬請期待!