目录
前言
在《EF Core使用Simple Logging输出日志》中,我们介绍了查询标记 TagWith
,它可以帮助我们快速定位到需要的日志:
而在 .NET 6 中,新增了另外一个查询标记 TagWithCallSite
,它可以标记出代码的位置:
var user = await new DefaultDbContext().User
.Where(p => p.Name == "My IO")
.TagWith("Find My IO")
.TagWithCallSite()
.FirstOrDefaultAsync();
那它是怎么做到的呢?
原理探究
查看 TagWithCallSite
的源代码:
public static IQueryable<T> TagWithCallSite<T>(
this IQueryable<T> source,
[NotParameterized] [CallerFilePath] string? filePath = null,
[NotParameterized] [CallerLineNumber] int lineNumber = 0)
原来,它使用了 CallerFilePathAttribute
和 CallerLineNumberAttribute
来获取包含调用方的源文件完整路径和调用方法的行号。
原理利用
这让我们想到了,在《.NET 6新特性试用 | ArgumentNullException卫语句》中发现的 CallerArgumentExpressionAttribute
,它可以获取执行的表达式。
通过添加此 Attribute,我们可以创建自己的自定义查询标记。实现代码如下:
public static IQueryable<T> TagWithCallInfo<T>(this IQueryable<T> source,
string? tag = null,
[NotParameterized][CallerArgumentExpression("source")] string? argument = null,
[NotParameterized][CallerMemberName] string? memberName = null,
[NotParameterized][CallerFilePath] string? filePath = null,
[NotParameterized][CallerLineNumber] int lineNumber = 0)
{
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine(tag);
foreach (var str in argument.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries))
{
stringBuilder.AppendLine(str);
}
stringBuilder.AppendLine($@"at {memberName}");
stringBuilder.AppendLine($@"File: {filePath}:{lineNumber}");
return source.TagWith(stringBuilder.ToString());
}
该方法不仅包含自定义标记文本,还自动包括了被调用的 LINQ 查询表达式、方法名称、文件路径、行号。
Demo
运行下列代码进行验证,完全满足了我们的要求:
var user = await new DefaultDbContext().User
.Where(p => p.Name == "My IO")
.TagWithCallInfo("Find My IO")
.FirstOrDefaultAsync();
结论
今天,通过扩展 TagWithCallSite
,我们实现了自定义查询标记。