多个线程正在通过Database.Log混合消息 [英] Messages are being mixed up through Database.Log by multiple threads

查看:74
本文介绍了多个线程正在通过Database.Log混合消息的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

就我而言,这是Visual Studio中的一个Web API项目。在测试时,会同时多次调用该API。

In my case it's a web API project in Visual Studio. When I'm testing, the API is called multiple times concurrently.

我正在使用以下命令记录发送到SQL Server的原始SQL:

I'm using the following to log the Raw SQL being sent to the SQL Server:

context.Database.Log = Console.WriteLine;

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

When SQL is logged, it's getting mixed up with queries on other threads. More specifically, it's most-often the parameters which get mixed up. This makes it next to impossible to correlate the right parameters with the right query. Sometimes the same API is called twice concurrently.

我正在使用异步调用,但这不会引起问题。这将是事实,即在不同的完成线程上有多个并发的Web请求。

I am using async calls, but that wouldn't be causing the issue. It will be the fact there are multiple concurrent web requests on different completion threads.

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

I need accurate reliable logging so I can look back in the output window and review the SQL.

推荐答案

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

You need to buffer all log messages per-context, then write out that buffer upon disposal of your db context.

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

You need to be able to hook into your db context's dispose event

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

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

public event EventHandler OnDisposed;

然后,您需要使用此类来管理每个上下文的缓冲

Then you need this class to manage the buffering per-context

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引用对象。但这并不是便携式的。

It should work without saving a strong reference to the LogGroup object. But I haven't tested that yet. Also, you could include this kind of code directly on the context, so you definitely won't need to save a LogGroup reference object. But that wouldn't be as portable.

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

To use it in a controller action funtion:

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

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

Note, that I pass the controller reference, so the log can include some extra context information - the request URI.

解释您的日志

现在该日志有效,您可以会发现在日志输出中使用注释的参数很麻烦。通常,您必须手动将它们更改为适当的SQL参数,但是即使那样,也很难使用参数运行较大的SQL查询的子节。

Now that the log works, you'll find the commented parameters in the log output are a pain to work with. You would normally have to manually change them to proper SQL parameters, but even then it's difficult to run sub-sections of a larger SQL query with parameters.

我知道一种或两种其他方式使EF输出日志。这些方法确实可以更好地控制参数的输出方式,但是给出的答案是使Database.Log正常工作,因此我将在WinForms中包含此工具,以便它可以使用功能查询来重写剪贴板。

I know there are one or two other ways to get EF to output the log. Those methods do provide better control over how parameters are output, but given the answer is about making Database.Log work I'll include this tool in WinForms, so it can rewrite your clipboard with a functional query.

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;
    }
}

这篇关于多个线程正在通过Database.Log混合消息的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆