WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

Multiple Active Result Sets (MARS)

by WebSurfer 2022年9月15日 19:24

SQL Server の Multiple Active Result Sets (MARS) というのは何で、どのような場合に必要かという話を書きます。

MARS は SQL Server 2005 以降で利用できる機能で、Microsoft のドキュメント「複数のアクティブな結果セットの有効化」によると "複数のバッチを単一の接続で実行することができます" ということです。(分かりやすい説明を下の方に追記)

接続文字列で MultipleActiveResultSets を true に設定することにより利用できます。

.NET Framework 2.0 からサポートされていたのですが、昔はそのような設定を見かけることは無かったです。それが ADO.NET Entity Data Model ウィザードで自動生成される接続文字列などに設定されるのを見かけて、なぜだろうと疑問に思っていました。

接続文字列で MultipleActiveResultSets=True として「複数のアクティブな結果セットの有効化」を行った例を以下に書きます。

string connString1 = "Data Source=lpc:(local)\\sqlexpress;" +
                     "Initial Catalog=NORTHWIND;" +
                     "Integrated Security=True;" +
                     "MultipleActiveResultSets=True";

string query1 = "SELECT CategoryID, CategoryName FROM Categories;";
string query2 = "SELECT ProductID, ProductName FROM Products;";

using (var connection = new SqlConnection(connString1))
{
    connection.Open();
    using (var command = new SqlCommand(query1, connection))
    {
        SqlDataReader reader = command.ExecuteReader();

        while (reader.Read())
        {
            Console.WriteLine("\t{0}\t{1}",
                reader.GetInt32(0), reader.GetString(1));
        }
    }

    using (var command = new SqlCommand(query2, connection))
    {
        SqlDataReader reader = command.ExecuteReader();

        while (reader.Read())
        {
            Console.WriteLine("\t{0}\t{1}",
                reader.GetInt32(0), reader.GetString(1));
        }
    }
}

MARS はデフォルトでは無効になっており、その場合はアプリケーションはバッチごとにすべての結果セットを処理またはキャンセルしないと、同じ接続で他のバッチを実行できません。

どういうことかと言うと、接続文字列の MultipleActiveResultSets=True を削除すると 2 つ目の command.ExecuteReader() で以下のように例外がスローされます。

DataReader エラー

上のコードでは、MARS の設定なしでも、DataReader を閉じるコードを書けば例外は回避できるのですが、Entity Framework が問題です。

例えば、Entity Framework の「遅延読み込み」を行ったりすると、MARS の設定無しでは以下の画像のように例外がスローされます。

遅延読み込み時のエラー

ADO.NET Entity Data Model ウィザードなどで自動生成される接続文字列に MARS の設定が含まれているのはそういう理由であろうと思われます。


2022/9/28 追記

MSDN ライブラリ Visual Studio 2008 の SqlCommand.ExecuteReader メソッドの解説に MARS の分かりやすい説明があるのを見つけましたので以下に書いておきます。

"SQL Server 2005 より前のバージョンの SQL Server の場合、SqlDataReader が使用されている間、関連付けられている SqlConnection は SqlDataReader によって使用されるため、ビジー状態になります。この状態では、SqlConnection に対して、閉じる以外の操作を実行できません。SqlDataReader の Close メソッドを呼び出すまでこの状態が続きます。SQL Server 2005 では、MARS (Multiple Active Result Set) 機能がサポートされ、同一接続を使用して複数の処理を実行できるようになりました"

Tags: , ,

ADO.NET

Chart の X 軸マージン

by WebSurfer 2022年9月9日 18:44

Chart の X 軸にはデフォルトでグラフの左右にマージンが表示されます。下の Chart サンプルの画像を見てください。X 軸の端にはデータはないのですが自動的に 0 と 10 というラベルが追加されています。

Axis Margins

データがないのに X 軸にラベルが表示されること、さらには、例えば数字が月を表しているような場合は存在しない月「0」を表示するのは好ましくなさそうです。

必要のない X 軸のマージンやラベルを表示しないようにする方法を調べましたので、以下に備忘録として書いておきます。

(1) Axis Margins

簡単なのは ChartArea.AxisX.IsMarginVisible プロパティを false に設定することです。上の画像の Chart サンプルで false に設定すると以下のようになります。

IsMarginVisible を false に設定

上の画像のような折れ線グラフのような場合はこれが簡単でよさそうです。

しかし、棒グラフのような場合は、上の画像で言うと X 軸の 1 と 9 上の棒の半分が見切れてしまいます。その場合は下に述べる CustomLabel を使用するのが良さそうです。

(2) Custom Labels

先の記事「WPF で Chart を表示」のコードでは以下の画像のように Chart の X 軸に 0 と 7 が表示されてしまいました。

WPF アプリに Chart 表示

それを CustomLabel クラスを使って以下ようにしてみました。

CustomLabel の使用

そのコードは以下の通りです。基本的には先の記事とほとんど同じで、下の方に「// CostomLabel の設定」とコメントした部分のコードを追加しただけです。

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;

namespace WindowsFormsApp1
{
    public partial class Form8 : Form
    {
        private readonly Chart chart;

        public Form8()
        {
            InitializeComponent();

            this.chart = new Chart
            {
                Name = "chart1",
                Dock = DockStyle.Fill
            };

            this.Controls.Add(chart);
        }

        private void Form8_Load(object sender, EventArgs e)
        {
            Legend legend = new Legend()
            {
                DockedToChartArea = "ChartArea1",
                IsDockedInsideChartArea = false,
                Name = "Legend1"
            };

            chart.Legends.Add(legend);

            Series series = new Series()
            {
                Name = "三吉",
                ChartType = SeriesChartType.StackedColumn,
                CustomProperties = "DrawingStyle=Cylinder",
                IsValueShownAsLabel = true,
                Label = "#PERCENT{P1}",
                Legend = "Legend1",
                XValueMember = "Month",
                YValueMembers = "三吉"
            };
            chart.Series.Add(series);

            series = new Series()
            {
                Name = "春日",
                ChartType = SeriesChartType.StackedColumn,
                CustomProperties = "DrawingStyle=Cylinder",
                IsValueShownAsLabel = true,
                Label = "#PERCENT{P1}",
                Legend = "Legend1",
                XValueMember = "Month",
                YValueMembers = "春日"
            };
            chart.Series.Add(series);

            series = new Series()
            {
                Name = "東雲",
                ChartType = SeriesChartType.StackedColumn,
                CustomProperties = "DrawingStyle=Cylinder",
                IsValueShownAsLabel = true,
                Label = "#PERCENT{P1}",
                Legend = "Legend1",
                XValueMember = "Month",
                YValueMembers = "東雲"
            };
            chart.Series.Add(series);

            series = new Series()
            {
                Name = "府中",
                ChartType = SeriesChartType.StackedColumn,
                CustomProperties = "DrawingStyle=Cylinder",
                IsValueShownAsLabel = true,
                Label = "#PERCENT{P1}",
                Legend = "Legend1",
                XValueMember = "Month",
                YValueMembers = "府中"
            };
            chart.Series.Add(series);

            series = new Series()
            {
                Name = "広島",
                ChartType = SeriesChartType.StackedColumn,
                CustomProperties = "DrawingStyle=Cylinder",
                IsValueShownAsLabel = true,
                Label = "#PERCENT{P1}",
                Legend = "Legend1",
                XValueMember = "Month",
                YValueMembers = "広島"
            };
            chart.Series.Add(series);

            ChartArea chartArea = new ChartArea()
            {
                Name = "ChartArea1",
                AxisY = new Axis() { Title = "売上高" },
                AxisX = new Axis() { Title = "売上月" }
            };
            chart.ChartAreas.Add(chartArea);

            // CostomLabel の設定
            var dic = new Dictionary<int, string>
            {
                { 1, "Jan" }, { 2, "Feb" }, { 3, "Mar" }, { 4, "Apr" },
                { 5, "May" }, { 6, "Jun" }, { 7, "Jul" }, { 8, "Aug" },
                { 9, "Sep" }, { 10, "Oct" }, { 11, "Nov" }, { 12, "Dec" }
            };
            DataTable table = CreateDataTable();
            foreach (DataRow row in table.Rows)
            {
                int m = (int)row["Month"];
                chartArea.AxisX.CustomLabels.Add(m - 0.5d, m + 0.5d, dic[m]);
            }

            chart.DataSource = table.DefaultView;
        }

        private DataTable CreateDataTable()
        {
            string connString = @"接続文字列";
            string selectQuery = @"SELECT Month, 
                SUM(CASE WHEN Name = N'三吉' THEN Sales ELSE 0 END) AS 三吉,
                SUM(CASE WHEN Name = N'春日' THEN Sales ELSE 0 END) AS 春日,
                SUM(CASE WHEN Name = N'東雲' THEN Sales ELSE 0 END) AS 東雲,
                SUM(CASE WHEN Name = N'府中' THEN Sales ELSE 0 END) AS 府中,
                SUM(CASE WHEN Name = N'広島' THEN Sales ELSE 0 END) AS 広島
                FROM Shop GROUP BY Month";

            using (var connection = new SqlConnection(connString))
            {
                using (var command = new SqlCommand(selectQuery, connection))
                {
                    var adapter = new SqlDataAdapter(command);
                    var table = new DataTable();
                    adapter.Fill(table);
                    return table;
                }
            }
        }        
    }
}

上のコードの「// CostomLabel の設定」とコメントした部分を説明します。

CreateDataTable メソッドの中の SELECT クエリからは以下の結果が得られ、それが DataTable に格納されて返されます。

SELECT クエリの結果

その DataTable の Month 列のデータを使って CustomLabel を生成し AxisX.CustomLabels で取得できる CustomLabelsCollection オブジェクトに追加しています。

Add メソッドの第 1 引数は CustomLabel.FromPosition を、第 2 引数は CustomLabel.ToPosition を設定します。それらは Midrosoft のドキュメント「CustomLabel.FromPosition プロパティ」に以下のように書いてある通りラベルの表示位置を設定するものです。

"The difference between the values of the ToPosition and FromPosition properties determines where the label text will be displayed. For example, suppose the X-axis has a scale that ranges from 1 to 5 (1, 2 ,3 , 4, 5). In order to display a custom label just underneath the second point, you would set the ToPosition property to 1.5 and the FromPosition property to 2.5."

Add メソッドの第 3 引数はラベルのテキストを設定するものです。上のコードでは Dictionary を作って Month 列の int 型の数字を Jan, Feb, Mar ... という文字列にしています。

Tags: , ,

.NET Framework

SQL Server オブジェクトエクスプローラー

by WebSurfer 2022年8月13日 14:09

自分の環境の Visual Studio 2022 では下の画像のように SQL Server オブジェクトエクスプローラーが表示されていますが、その中の各項目が何かを調べたので備忘録として書いておきます。

SQL Server オブジェクトエクスプローラー

まず、SQL Server オブジェクト エクスプローラーとは何かですが、CodeZin の記事 使わなきゃ損! SQL Serverの新たな開発ツール「SQL Server Data Tools」によると SQL Server Data Tools (SSDT) の機能で、"開発者がSQL Server Management Studioで実施していたテーブルの作成や変更などのデータベース関連タスクを、Visual Studioで完結することを目的としています" というものだそうです。

Visual Studio 2022 に SSDT をインストールするには、Microsoft のドキュメント SQL Server Data Tools (SSDT) for Visual Studio のダウンロードに書いてあるように、ワークロードの「データの保存と処理」を追加し、そのオプションの「SQL Server Data Tools」にチェックを入れます。

そして、Visual Studio 2022 のメニューバーから[表示(V)]⇒[SQL Server オブジェクト エクスプローラー(S)]をクリックすれば SQL Server オブジェクトエクスプローラーが表示されます。

SQL Server オブジェクトエクスプローラーを表示

前置きが長くなりましたが、上の SQL Server オブジェクトエクスプローラーの画像に表示されている各項目が何かを説明します。

(1) (local)\sqlexpress (SQL Server 11.0.2100 ...)

ローカルにインストールした SQL Server 2012 Express の名前付きインスタンスです。

SQL Server の Express 版をインストールすると、デフォルトでは「名前つきインスタンス」となり、インスタンス名は SQLEXPRESS になります。

(記憶にないですが、たぶん、自分で SQL Server オブジェクト エクスプローラーを操作して追加したものだと思います)

(2) (localdb)\MSSQLLocalDB (SQL Server 13.0.4001 ...)

SQL Server 2016 LocalDB の自動インスタンスです。13.0.4001 から SQL Server 2016 ベースであることが分かります。

他に名前付きインスタンスというのもあります。詳しくは Microsoft のドキュメント SQL Server Express LocalDB を見てください。

自動インスタンスとは SQL Server の既定のインスタンスに該当するもののようで、開発時にはそれに接続して使うようにします。

Microsoft のドキュメントに書いてある通り、自動インスタンスの名前は MSSQLLocalDB になります。(SQL Server 2014 で変更されたそうです。その前は、文字 v の後に LocalDB とバージョン番号を付けたものでした)

Visual Studio 2019 では SQL Server 2016 LocalDB が、Visual Studio 2022 では SQL Server 2019 LocalDB が一緒にインストールされます。

自分の PC には Visual Studio 2019 / 2022 両方をインストールしており、一緒にインストールされた LocalDB は以下のようになっています。(一番上の SQL Server 2014 Express LocalDB は Visual Studio 2015 と一緒にインストールされたもの) 

インストールされている LocalDB

それなのに、なぜ Visual Studio 2022 の SQL Server オブジェクトエクスプローラーに表示されている自動インスタンスが SQL Server 2019 LocalDB のものではないのでしょう? それはたぶん以下のような話ではないかと思います。

上に紹介した Microsoft のドキュメントに以下のように書いてあります。

"ユーザーのコンピューターにインストールされているどのバージョンの LocalDB についても、LocalDB の自動インスタンスが 1 つ存在します"(PC 内に複数の自動インスタンスは存在しないということ)

"あるコンピューター上でユーザーが初めて LocalDB への接続を試みるときは、自動インスタンスを作成し、なおかつ開始する必要があります"

ということで、先に Visual Studio 2019 で作業したとき SQL Server 2016 LocalDB で自動インスタンスが作成され、Visual Studio 2022 でも先に作成された自動インスタンスがそのまま使われているということだと思います。

その自動インスタンスを、コマンド ライン管理ツール: SqlLocalDB.exe を使って SQL Server 2019 LocalDB にアップグレードすることはできるようです。

ググって調べると、Upgrade Visual Studio 2019’s LocalDB to SQL 2019 とか Upgrading SQL Server LocalDb などの方法を書いた記事がヒットします。

その方法というのは、(a) 既存の自動インスタンスを削除、(b) 新たに SQL Server 2019 LocalDB の自動インスタンスを作成、(c) 既存のデーターベースを新たに作成した自動インスタンスにアタッチする・・・ということになるようです。

上の (c) にリスクがありそうです。SQL Server 2019 LocalDB の自動インスタンスが必須というわけではない現状では、アップグレードには手を出さない方が良さそうな感じです。

(3) (localdb)\ProjectModels (SQL Server 15.0.4153 ...)

これは自分で作った記憶がなくて、Visual Studio 2022 で SSDT 関係の操作をしたとき自動的に作られたもののようです。

Microsoft の Developer Community の記事 (localdb)\ProjectsV13 not setup when installing Visual Studio 2022 に説明がありました。

自分の環境では、Visual Studio 2019 で、一緒にインストールされた SQL Server 2016 LocalDB をベースに、(localdb)\ProjectsV13 という名前のインスタンスが作られて、それがそのまま残っている。その後 Visual Studio 2022 を使うようになって、一緒にインストールされた SQL Server 2019 LocalDB をベースに (localdb)\ProjectModels という名前のインスタンスが作られたということのようです。

Visual Studio にインストールした SSDT が使うもので、開発者がアプリで使うものではないようです。

(4) (localdb)\ProjectsV13 (SQL Server 13.0.4001 ...)

上にも書きましたが、Visual Studio 2019 で一緒にインストールされた SQL Server 2016 LocalDB をベースに (localdb)\ProjectsV13 という名前のインスタンスが作られたようです。

一体それは何かですが、Stsckoverflow の記事 Purpose of ProjectsV13 LocalDB instance に以下のように書いてありました。

"The primary reason is to avoid conflicts with any "production" databases on MSSQLLocalDB. SSDT creates a new database for every database project you open. If your project is called Adventureworks, this might conflict with an Adventureworks database created by web projects or that are used by local ASP.NET applications you are running / debugging. Since SSDT does this automatically, in the background, it was felt that there was too high a risk of conflict. Hence, a separate instance is used."

上の (3) の (localdb)\ProjectModels も同じだと思います。

Tags: , , ,

DevelopmentTools

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年5月  >>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar