消息通过Database.Log混合多个线程

时间:2017-10-27 23:01:08

标签: c# multithreading entity-framework asynchronous asp.net-web-api

在我的例子中,它是Visual Studio中的Web API项目。当我测试时,API会同时被多次调用。

我正在使用以下内容记录发送到SQL Server的Raw SQL:

context.Database.Log = Console.WriteLine;

当记录SQL时,它会与其他线程上的查询混淆。更具体地说,它通常是混淆的参数。这使得几乎不可能将正确的参数与正确的查询相关联。有时同时调用两次相同的API。

我正在使用异步调用,但这不会导致问题。事实上,在不同的完成线程上存在多个并发Web请求。

我需要准确可靠的日志记录,因此我可以回顾输出窗口并查看SQL。

1 个答案:

答案 0 :(得分:1)

您需要按上下文缓冲所有日志消息,然后在处理数据库上下文时写出该缓冲区。

您需要能够挂钩数据库上下文的dispose事件

protected override void Dispose(bool disposing)
{
    base.Dispose(disposing);

    if (OnDisposed != null) OnDisposed(this, null);
}

public event EventHandler OnDisposed;

然后你需要这个类来管理每个上下文的缓冲

class LogGroup
{
    static bool ReferenceActiveGroups = true; //I'm not sure if this is needed. It might work fine without.
    static HashSet<LogGroup> LogGroups = ReferenceActiveGroups ? new HashSet<LogGroup>() : null;

    /// <summary>
    /// For the currently being ran query, this outputs the Raw SQL and the length of time it was executed in the Output window (CTRL + ALT + O) when in Debug mode.
    /// </summary>
    /// <param name="db">The DbContext to be outputted in the Output Window.</param>
    public static void Log(ApiController context, AppContext db)
    {
        var o = new LogGroup(context, db);
        o.Initialise();
        if (ReferenceActiveGroups) o.Add();
    }

    public LogGroup(ApiController context, AppContext db)
    {
        this.context = context;
        this.db = db;
    }

    public void Initialise()
    {
        db.OnDisposed += (sender, e) => { this.Complete(); };
        db.Database.Log = this.Handler;

        sb.AppendLine("LOG GROUP START");
    }

    public void Add()
    {
        lock (LogGroups)
        {
            LogGroups.Add(this);
        }
    }

    public void Handler(string message)
    {
        sb.AppendLine(message);
    }

    public AppContext db = null;
    public ApiController context = null;
    public StringBuilder sb = new StringBuilder();

    public void Remove()
    {
        lock (LogGroups)
        {
            LogGroups.Remove(this);
        }
    }

    public void Complete()
    {
        if (ReferenceActiveGroups) Remove();

        sb.AppendLine("LOG GROUP END");
        System.Diagnostics.Debug.WriteLine(sb.ToString());
    }
}

它应该可以在不保存对LogGroup对象的强引用的情况下工作。但我还没有测试过。此外,您可以直接在上下文中包含此类代码,因此您绝对不需要保存LogGroup引用对象。但这不会那么便携。

在控制器动作功能中使用它:

var db = new MyDbContext();
LogGroup.Log(this, db);

注意,我传递了控制器引用,因此日志可以包含一些额外的上下文信息 - 请求URI。

解读您的日志

现在日志工作,您会发现日志输出中的注释参数很难处理。您通常必须手动将它们更改为正确的SQL参数,但即使这样,也很难使用参数运行较大SQL查询的子部分。

我知道有一两种方法可以让EF输出日志。这些方法确实提供了对参数输出方式的更好控制,但鉴于答案是关于使Database.Log工作,我将在WinForms中包含此工具,因此它可以使用函数查询重写剪贴板。

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    class parameter
    {
        public string Name;
        public string Value;
        public string Type;

        public string FormattedValue
        {
            get
            {
                if (Type == "Boolean")
                {
                    if (Value == "True")
                        return "1";
                    else
                        return "0";
                }
                else if (Type == "Int32")
                {
                    return Value;
                }
                else
                    throw new Exception("Unsupported type - " + Type);
            }

        }

        public override string ToString()
        {
            return string.Format("{0} - {1} - {2} - {3}", Name, Value, Type, FormattedValue);

        }
    }

    private void button1_Click(object sender, EventArgs e)
    {


        var sb = new StringBuilder();
        var data = Clipboard.GetText(TextDataFormat.UnicodeText);
        var lines = data.Split(new string[] { "\r\n" }, StringSplitOptions.None);
        var parameters = GetParmeters(lines);
        parameters.Reverse();

        foreach (var item in lines)
        {
            if (item.Trim().Length == 0)
                continue;
            if (item.TrimStart().StartsWith("--"))
                continue;

            var SQLLine = item;
            foreach (var p in parameters)
            {
                SQLLine = SQLLine.Replace("@" + p.Name, p.FormattedValue);
            }
            sb.AppendLine(SQLLine);
        }

        Clipboard.SetText(sb.ToString());
    }

    private static List<parameter> GetParmeters(string[] lines)
    {
        var parameters = new List<parameter>();
        foreach (var item in lines)
        {
            var trimed = item.Trim();
            if (trimed.StartsWith("-- p__linq__") == false)
                continue;

            var colonInd = trimed.IndexOf(':');
            if (colonInd == -1)
                continue;

            var paramName = trimed.Substring(3, colonInd - 3);
            var valueStart = colonInd + 3;
            var valueEnd = trimed.IndexOf('\'', valueStart);
            if (valueEnd == -1)
                continue;

            var value = trimed.Substring(valueStart, valueEnd - valueStart);

            var typeStart = trimed.IndexOf("(Type = ");
            if (typeStart == -1)
                continue;
            typeStart += 8;

            var typeEnd = trimed.IndexOf(',', typeStart);
            if (typeEnd == -1)
                typeEnd = trimed.IndexOf(')', typeStart);
            if (typeEnd == -1)
                continue;

            var type = trimed.Substring(typeStart, typeEnd - typeStart);

            var param = new parameter();
            param.Name = paramName;
            param.Value = value;
            param.Type = type;

            parameters.Add(param);
        }

        return parameters;
    }
}