前言
.NET Core項目利用EntityFramework Core作為數據訪問層一直在進行中,一直沒有過多的去關注背后生成的SQL語句,然后老大撈出日志文件一看,恩,有問題了,所以本文產生了,也是有點疑惑,若有知情者,還望告知。
EntityFramework Core忽略導航屬性
在前面我們已經探討過利用Serilog日志框架來輸出日志,所以對於本節查詢日志的輸出依然借助Seilog。我們在Startup.cs類中Starup方法中是創建日志實例。
Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.RollingFile(Path.Combine( env.ContentRootPath, "{Date}.log")) .CreateLogger();
接着我們只需要將Serilog注入到日志管道中即可在Configure方法中注入。
loggerFactory.AddSerilog();
完成上述日志輸出只需要安裝如下三個包即可。
接下來記錄日志只需要在控制器類或者其他類構造函數注入即可。
public class HomeController : Controller { private readonly ILogger _logger; private IBlogRepository _blogRepository; public HomeController(ILogger<HomeController> logger) { _logger = logger; _blogRepository = blogRepository; } }
關於EntityFramework Core中映射等就不再闡述,請參看前面EntityFramework Core系列。下面我們直接給出查詢操作
public async Task<IEnumerable<Post>> GetPosts() { var posts = await _context.Blogs .AsNoTracking() .Include(d => d.Posts) .SelectMany(d => d.Posts) .Select(p => new Post() { Id = p.Id, Title = p.Title, Content = p.Content }).ToListAsync(); return await Task.FromResult(posts); }
不必太糾結上述查詢語句,當有多表查詢時我們最終需要獲取Blog中的Post,最終才有了上述語句。我們看到如下日志文件。
我們來查看其中生成的Linq語句。
繼續往下看我們將看到如下語句:
2017-09-28 00:36:58.901 +08:00 [Warning] The Include operation for navigation: 'd.Posts' was ignored because the target navigation is not reachable in the final query results. To configure this warning use the DbContextOptionsBuilder.ConfigureWarnings API (event id 'CoreEventId.IncludeIgnoredWarning'). ConfigureWarnings can be used when overriding the DbContext.OnConfiguring method or using AddDbContext on the application service provider.
上述警告語句提示導航屬性Posts被忽略了因為其未能到達最終的查詢結果,但是最終我們還是能看到里面確確實實是有數據的。
然后查看EntityFramework Core官方文檔已經說明了此情況何時發生。
當利用飢餓加載進行查詢操作時,最終並未返回原實體的實例此時將忽略Include導航屬性。但是上述最終還是返回了數據,這是不是就暗示着並未利用飢餓加載而是在內存中操作呢。然后通過SQL Profiler進行監控得知只生成了一條SQL語句。
到這里還是沒明白官方文檔中所敘述的忽略導航屬性究竟是什么意思?如果忽略了導航屬性上述利用Linq進行查詢應該會出現異常才對或者不會進行內連接,不知所雲。所以上述查詢我們只能返回Blog,而非其他實體,例如如下:
public async Task<IEnumerable<Blog>> GetPosts() { var posts = await _context.Blogs .AsNoTracking() .Include(d => d.Posts) .ToListAsync(); return await Task.FromResult(posts); }
或者
public async Task<IEnumerable<Blog>> GetPosts() { var posts = await _context.Blogs .AsNoTracking() .Include(d => d.Posts) .Select(b => b) .ToListAsync(); return await Task.FromResult(posts); }
通過在github上找到如下issue:【https://github.com/aspnet/EntityFrameworkCore/issues/7153】 文中所述在1.1版本中將優化這種查詢,目前我所使用版本為1.1.2,既然能正確返回值為何還打印警告日志提醒呢,看來這並不是問題,雖然忽略了但是還是進行了優化查詢能夠正確查詢出數據。
總結
該問題演示在EntityFramework Core 1.1.2版本中,既然給出了提示那么應該是未解決,如果未解決那么將出現性能問題,如果我們進行投影然后ToList,此時利用Include進行飢餓加載,但是Include卻被忽略,此時將生成一條單個SQL語句來查詢獲取結果集中每個元素的導航屬性。若Include未被忽略並按照我們設想進行表連接,此時性能會更好。文中日志記錄顯示Include被忽略,但是生成SQL語句沒有問題,卻還是輸出日志警告提醒,這究竟是為何,郁悶?