Как выполнить DataAdapter.Fill() одновременно

Я давно работаю над приложением ASP.Net, и приложение используют более 10 клиентов. Но теперь я обнаружил проблему в приложении, то есть у меня есть вызов хранимой процедуры, которая выполняется около 30 секунд. Это не проблема, потому что код SQL очень сложен и многократно зацикливается. Проблема в том, что всякий раз, когда выполняется вызов хранимой процедуры, я не могу использовать какие-либо другие функции или вызов хранимой процедуры. Когда я пытался выполнить отладку, проблема заключалась в том, что функция DataAdapter.Fill() ожидает завершения первого вызова хранимой процедуры.

Мой код, который выполняет вызов хранимой процедуры и возвращает данные:

public static DataSet ExecuteQuery_SP(string ProcedureName, object[,] ParamArray)
    {
        SqlDataAdapter DataAdapter = new SqlDataAdapter();       
        DataSet DS = new DataSet();
        try
        {
            if (CON.State != ConnectionState.Open)
                OpenConnection();
            SqlCommand cmd = new SqlCommand();
            cmd.CommandTimeout = 0;
            cmd.CommandText = ProcedureName;
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Connection = CON;
            cmd.Transaction = SqlTrans;
            string ParamName;
            object ParamValue;
            for (int i = 0; i < ParamArray.Length / 2; i++)
            {
                ParamName = ParamArray[i, 0].ToString();
                ParamValue = ParamArray[i, 1];
                cmd.Parameters.AddWithValue(ParamName, ParamValue);
            }
            DataAdapter = new SqlDataAdapter(cmd);
            DataAdapter.Fill(DS);
            cmd.CommandText = "";
        }
           catch (Exception ea)
        {
        }
        return DS;
    }

Все вызовы хранимых процедур работают через эту функцию. Следовательно, когда мой первый вызов хранимой процедуры «A» выполняется, вызов хранимой процедуры «B» не будет выполняться до тех пор, пока «A» не будет завершен.

Это снижает общую производительность приложения и вызывает проблемы при извлечении данных. Я просмотрел Google и обнаружил, что «Threading» может быть полезным, но я не могу правильно выполнить многопоточность. Я не настолько знаком с такими вещами. Будет полезно, если вы сможете исправить проблему. Мой первый вызов хранимой процедуры:

 ds = DB.ExecuteQuery_SP("SelectOutstandingReportDetailed", parArray);

Где ds — объект DataSet. Второй вызов хранимой процедуры:

ds = DB.ExecuteQuery_SP("[SelectAccLedgersDetailsByID]", ParamArray);

Моя текущая функция открытия соединения с БД:

 public static bool OpenConnection() 
        {
            try
            {

                    Server = (String)HttpContext.GetGlobalResourceObject("Resource", "Server");
                    DBName = (String)HttpContext.GetGlobalResourceObject("Resource", "DBName");
                    UserName = (String)HttpContext.GetGlobalResourceObject("Resource", "UserName");
                    PassWord = (String)HttpContext.GetGlobalResourceObject("Resource", "PassWord");

                    string ConnectionString;
                    ConnectionString = "server=" + Server + "; database=" + DBName + "; uid=" + UserName + "; pwd=" + PassWord + "; Pooling='true';Max Pool Size=100;MultipleActiveResultSets=true;Asynchronous Processing=true";

                    CON.ConnectionString = ConnectionString;
                    if (CON.State != ConnectionState.Open)
                    {
                        CON.Close();
                        CON.Open();
                    }

            }
            catch (Exception ea)
            {
            }
            return false;
        }

Где CON — общедоступная переменная SqlConnection.

static SqlConnection CON = new SqlConnection();

Я нашел проблему, то есть все вызовы хранимых процедур выполняются через этот объект «CON». Если для каждого вызова хранимой процедуры существует отдельный объект SqlConnection, то проблем нет. Так можно ли сделать отдельный SqlConnection для каждого вызова ExecuteQuery_SP. Если у вас есть какие-либо сомнения, пожалуйста, прокомментируйте. Спасибо


person Codinoz Technologies    schedule 08.11.2019    source источник
comment
Все вызовы хранимых процедур работают через эту функцию. Тогда не используйте один и тот же метод для выполнения разных хранимых процедур?   -  person IrishChieftain    schedule 08.11.2019
comment
Функция ExecuteQuery_SP является общей функцией для этого открытого соединения с базой данных. Я не могу дать отдельные функции для каждого вызова хранимой процедуры, потому что будет около сотни вызовов хранимой процедуры @IrishChieftain   -  person Codinoz Technologies    schedule 08.11.2019
comment
docs.microsoft.com/en- us/dotnet/visual-basic/language-reference/ -- Вы пробовали это? Извините, сначала не тот язык. Позвольте мне посмотреть, есть ли что-то подобное в C#.   -  person JohnPete22    schedule 08.11.2019
comment
Это то, что называется уровнем данных. У вас есть разные методы доступа к данным, каждый из которых потенциально может вызвать хранимую процедуру. Попробуйте.   -  person IrishChieftain    schedule 08.11.2019
comment
Вот ссылка на C# для блокировки его до одного потока за раз: docs.microsoft.com/en-us/dotnet/csharp/language-reference/   -  person JohnPete22    schedule 08.11.2019
comment
JohnPete22, от этого будет только хуже.   -  person IrishChieftain    schedule 08.11.2019
comment
@JohnPete22 JohnPete22 Я хочу запустить обе хранимые процедуры одновременно. Ваша ссылка не работает.   -  person Codinoz Technologies    schedule 08.11.2019
comment
Я не имею в виду слой данных. Я просто новичок в этом. Если вы можете объяснить, это будет полезно @IrishChieftain   -  person Codinoz Technologies    schedule 08.11.2019
comment
Какую транзакцию вы используете? Может быть, это проблема.   -  person Alexander Petrov    schedule 08.11.2019
comment
Что ты имеешь в виду? @АлександрПетров   -  person Codinoz Technologies    schedule 08.11.2019
comment
Вы используете cmd.Transaction = SqlTrans; Эта транзакция передается извне. Каковы настройки этой транзакции? Какие еще запросы выполняются в той же транзакции? Есть ли тупики?   -  person Alexander Petrov    schedule 08.11.2019
comment
Вы используете один SqlConnection для доступа ко всем данным? Вы не должны этого делать. Создайте новый SqlConnection для каждого запроса. ADO.NET управляет пулом соединений внутри.   -  person Theodor Zoulias    schedule 08.11.2019
comment
Из функции ExecuteQuery_SP видно, что всякий раз, когда текущее соединение закрывается, открывается новое соединение. Я пытался открывать соединение для каждого вызова хранимой процедуры, но результат все тот же @TheodorZoulias   -  person Codinoz Technologies    schedule 08.11.2019


Ответы (2)


SQL Server позволит тысячи соединений одновременно, по умолчанию. Это НЕ источник вашей проблемы. Вы заставили каждый вызов хранимой процедуры проходить через один метод. Исключите вызовы хранимых процедур — другими словами, избавьтесь от метода ExecuteQuery_SP, который является узким местом. Затем снова протестируйте.

Вот введение в слои данных.

person IrishChieftain    schedule 08.11.2019
comment
Я не понял тебя должным образом. Вы имеете в виду написать отдельные функции, подобные ExecuteQuery_SP, для каждого вызова хранимой процедуры? - person Codinoz Technologies; 08.11.2019
comment
Да. Вы создаете узкое место без необходимости. - person IrishChieftain; 08.11.2019
comment
Извините, в моем случае это невозможно. Потому что существует большое количество вызовов хранимых процедур. - person Codinoz Technologies; 08.11.2019
comment
Я попытался добавить отдельную функцию для «SelectOutstandingReportDetailed», но проблема не устранена. - person Codinoz Technologies; 08.11.2019
comment
Вы только что вызвали «SelectOutstandingReportDetailed», а не какие-либо другие процедуры? Есть ли проблема с запросами со многими соединениями? Может быть, отсутствует индекс БД? Вам нужно сузить это. - person IrishChieftain; 08.11.2019
comment
Нет. Я обычно вызываю много процедур. Просто, например, я использовал этот «SelectOutstandingReportDetailed», и для завершения выполнения требуется некоторое время. Что вы подразумеваете под индексом БД? - person Codinoz Technologies; 08.11.2019
comment
Вы не можете решить проблему, пока не узнаете, в чем проблема. Вам нужно точно определить, где происходит падение производительности. Это конкретный запрос? Никто не сможет вам ответить на этот вопрос, пока проблема не будет выявлена. В любом случае вам нужно потерять метод ExecuteQuery_SP. - person IrishChieftain; 08.11.2019
comment
Какая хранимая процедура выполняется 30 секунд? Начните с этого и используйте SQL Profiler, чтобы увидеть, есть ли проблема с самим запросом. Это может быть вообще не о параллелизме. - person IrishChieftain; 08.11.2019
comment
Проблема связана с методом DataAdapter.Fill(DS). Когда 'SelectOutstandingReportDetailed' запускает DataAadapter.Fill(DS), то вызовы других хранимых процедур будут просто ждать в DataAdapter.Fill(DS) до завершения предыдущего выполнения. - person Codinoz Technologies; 08.11.2019
comment
Я сказал вам избавиться от этого метода. Я могу объяснить это очень многими способами. Запускайте свои запросы независимо в SSMS и смотрите, что происходит. Здесь никто не знает ни вашего кода, ни того, сколько запросов следуют один за другим. - person IrishChieftain; 08.11.2019
comment
С запросом проблем нет, в нем есть циклы while, поэтому его выполнение требует времени. Это не проблема, всякий раз, когда обрабатывается один запрос, другой вызов хранимой процедуры не может быть обработан. - person Codinoz Technologies; 08.11.2019
comment
Я уже говорил вам, почему это происходит. Не вызывайте все свои хранимые процедуры из одного и того же метода. Вы проверили все остальные запросы? - person IrishChieftain; 08.11.2019
comment
Проблема не связана с запросом. Некоторые запросы требуют времени, это не проблема. Но я хочу одновременно выполнять несколько хранимых процедур. Я уже пытался сделать отдельную функцию для моего первого вызова хранимой процедуры, но все равно она остается прежней. То есть sqladapter.fill не выполняется, когда предыдущий sqladapter.fill не завершен. - person Codinoz Technologies; 09.11.2019
comment
Да, но вы его изолировали? Мы не можем видеть весь ваш код. В любом случае не следует использовать один метод для вызова всех хранимых процедур. Одновременное выполнение нескольких хранимых процедур не должно быть проблемой. Как я уже сказал, это не похоже на проблему параллелизма, скажем... - person IrishChieftain; 09.11.2019
comment
Я нашел проблему и обновил вопрос. Пожалуйста, проверь это. - person Codinoz Technologies; 09.11.2019
comment
Удачи. Я все еще не понимаю вопроса. - person IrishChieftain; 09.11.2019
comment
Проблема в том, что все мои вызовы хранимых процедур выполняются с помощью sqlconnection CON. Поэтому, когда в данный момент работает один вызов хранимой процедуры, мы не можем использовать тот же CON для другого вызова хранимой процедуры. Я попытался создать SqlConnection, и он работает - person Codinoz Technologies; 09.11.2019

Вот самая простая версия, которую я могу создать для вас. Важно: чтобы понять, вы должны прочитать об асинхронном ожидании.

Вы можете начать с документов Microsoft C# Async-Await.

// TODO set up your connection string
    private string connectionString = "<your connection string>";

    // Gets data assyncronously
    public static async Task<DataTable> GetDataAsync(string procedureName, object[,] ParamArray)
    {
        try
        {
            var asyncConnectionString = new SqlConnectionStringBuilder(connectionString)
            {
                AsynchronousProcessing = true
            }.ToString();

            using (var conn = new SqlConnection(asyncConnectionString))
            {
                using (var SqlCommand = new SqlCommand())
                {
                    SqlCommand.Connection = conn;
                    SqlCommand.CommandText = procedureName;
                    SqlCommand.CommandType = CommandType.StoredProcedure;

                    string ParamName;
                    object ParamValue;
                    for (int i = 0; i < ParamArray.Length / 2; i++)
                    {
                        ParamName = ParamArray[i, 0].ToString();
                        ParamValue = ParamArray[i, 1];
                        SqlCommand.Parameters.AddWithValue(ParamName, ParamValue);
                    }

                    conn.Open();
                    var data = new DataTable();
                    data.BeginLoadData();
                    using (var reader = await SqlCommand.ExecuteReaderAsync().ConfigureAwait(true))
                    {
                        if (reader.HasRows)
                            data.Load(reader);
                    }
                    data.EndLoadData();
                    return data;
                }
            }
        }
        catch (Exception Ex)
        {
            // Log error or something else
            throw;
        }
    }

    public static async Task<DataTable> GetData(object General, object Type, string FromDate, string ToDate)
    {
        object[,] parArray = new object[,]{
        {"@BranchID",General.BranchID},
        {"@FinancialYearID",General.FinancialYearID},
        {"@Type",Type},
        {"@FromDate",DateTime.ParseExact(FromDate, "dd/MM/yyyy", System.Globalization.CultureInfo.InvariantCulture)},
        {"@ToDate",DateTime.ParseExact(ToDate, "dd/MM/yyyy", System.Globalization.CultureInfo.InvariantCulture)}
        };

        return await DataBaseHelper.GetDataAsync("SelectOutstandingReportDetailed", parArray);
    }

    // Calls database assyncronously
    private async Task ConsumeData()
    {
        DataTable dt = null;

        try
        {
            // TODO configure your parameters here
            object general = null;
            object type = null;
            string fromDate = "";
            string toDate = "";

            dt = await GetData(general, type, fromDate, toDate);
        }
        catch (Exception Ex)
        {
            // do something if an error occurs
            System.Diagnostics.Debug.WriteLine("Error occurred: " + Ex.ToString());
            return;
        }

        foreach (DataRow dr in dt.Rows)
        {
            System.Diagnostics.Debug.WriteLine(dr.ToString());
        }
    }

    // Fired when some button is clicked. Get and use the data assyncronously, i.e. without blocking the UI.
    private async void button1_Click(object sender, EventArgs e)
    {
        await ConsumeData();
    }
person richardsonwtr    schedule 08.11.2019
comment
Я попробую с этим, но мне видится это очень сложно, потому что я совсем не знаком с этим. Я буду стараться изо всех сил с этим. Если вы можете, пожалуйста, помогите мне с моим кодом - person Codinoz Technologies; 08.11.2019
comment
каковы параметры SelectOutstandingReportDetailed? если вы дадите мне, я могу показать вам мой подход. - person richardsonwtr; 08.11.2019
comment
И мне любопытно. Как создать переменную ParamArray? Это похоже на [[@varname,value], [@varname2,value]]? Я не могу понять. - person richardsonwtr; 08.11.2019
comment
object[,] parArray = new object[,]{ {@BranchID,General.BranchID}, {@FinancialYearID,General.FinancialYearID}, {@Type,Type}, {@FromDate,DateTime.ParseExact(FromDate, dd/MM /yyyy, System.Globalization.CultureInfo.InvariantCulture)}, {@ToDate,DateTime.ParseExact(ToDate, dd/MM/yyyy, System.Globalization.CultureInfo.InvariantCulture)} }; Это объект paramarray SelectOutstandingReportDetailed. - person Codinoz Technologies; 08.11.2019
comment
DataTable.Load метод синхронный. Так что основное действие — загрузка данных — в вашем коде происходит синхронно. - person Alexander Petrov; 08.11.2019
comment
Каково решение? @AlexanderPetrov - person Codinoz Technologies; 08.11.2019
comment
Настоящая асинхронная загрузка: stackoverflow.com/a/45106131/5045688 - person Alexander Petrov; 08.11.2019
comment
Отредактировано, чтобы сделать его проще. Может быть полезно. Не проверенный код. - person richardsonwtr; 08.11.2019
comment
Я нашел проблему и обновил вопрос. Пожалуйста, проверь это. - person Codinoz Technologies; 09.11.2019
comment
В моем решении соединение всегда открыто и закрыто внутри функции GetDataAsync. Просто скопируйте и вставьте мой код, задайте для переменной connectionString значение вашей переменной ConnectionString , и все готово. У вас есть время протестировать мое решение? Я думаю, что это будет соответствовать вашим потребностям. Свяжитесь со мной, если вы застряли с какими-либо ошибками - person richardsonwtr; 11.11.2019