WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

SQL Server の数値型を LIKE 句を使ってあいまい検索

by WebSurfer 19. June 2021 12:55

SQL Server の数値型は、文字列型と同様に、そのまま直接 LIKE 句を使ってあいまい検索ができるようです。下の画像を見てください。UnitPrice 列は money 型ですが、[UnitPrice] LIKE '%2%' という条件が有効になっています。

SSMS での検索結果

知ってました? 実は何を隠そう自分は最近まで知らなかったです。(汗) 数字型はまず文字列型に変換してから、それに LIKE 句を使うものだと思ってました。

調べてみると、Microsoft のドキュメント「LIKE (Transact-SQL)」に、

"引数が文字列データ型でない場合、SQL Server データベース エンジン は可能であれば引数を文字列データ型に変換します。 If any one of the arguments isn't of character string data type, the SQL Server Database Engine converts it to character string data type, if it's possible."

・・・と書いてあります。実際に試してみると、上の画像のように money 型の UnitPrice 列も LIKE 句を使ってあいまい検索ができました。

Microsoft のドキュメントが言う「可能であれば」がどこまでの範囲か調べ切れていませんが、自分が SQL Server 2012 で試した限りでは int 型と money 型は可能な範囲に入るようです。

ADO.NET + SqlClient を使った .NET Framework のアプリケーションでも同じことができます。パラメータ化も可能です。ただし、パラメータ化する場合は、パラメータは文字列型として扱う必要がありますが。

上の画像と同様な LIKE 句を使って検索を行う .NET Framework コンソールアプリのサンプルコードを以下に載せておきます。ADO.NET + SqlClient を使い、SQL 文はパラメータ化しています。

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;

namespace ConsoleAppLIKE
{
    public class Product
    {
        public int ProductID { get; set; }

        public string ProductName { set; get; }

        // UnitPrice 列は NULL 可なので Nullable とした
        public decimal? UnitPrice { set; get; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            string connString = "接続文字列";
            string selectQuery =
                "SELECT [ProductID] ,[ProductName] ,[UnitPrice] FROM [Products] " +
                "WHERE [ProductName] LIKE N'%' + @ProductName + N'%' AND " +
                "[UnitPrice] LIKE N'%' + @UnitPrice + N'%'";

            string productName = "ch";
            string unitPrice = "2";            
            List<Product> productList = new List<Product>();

            using (var connection = new SqlConnection(connString))
            {
                using (var command = new SqlCommand(selectQuery, connection))
                {
                    var p1 = new SqlParameter("@ProductName", SqlDbType.NVarChar);
                    p1.Value = productName;

                    // UnitPrice 列は money 型だが LIKE 句を使ってあいまい検索
                    // する場合はパラメータの型は文字列とする
                    var p2 = new SqlParameter("@UnitPrice", SqlDbType.NVarChar);
                    p2.Value = unitPrice;

                    command.Parameters.Add(p1);
                    command.Parameters.Add(p2);

                    connection.Open();
                    using (var reader = command.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            var product = new Product
                            {
                                ProductID = reader.GetInt32(0),
                                ProductName = reader.GetString(1),

                                // UnitPrice 列は NULL 可なのでその対応
                                UnitPrice = reader.IsDBNull(2) ?
                                            null : (decimal?)reader.GetDecimal(2)
                            };

                            productList.Add(product);
                        }
                    }
                }
            }

            foreach (Product p in productList)
            {
                Console.WriteLine($"PriductID: {p.ProductID}, " +
                    $"ProductName: {p.ProductName}, UnitPrice: {p.UnitPrice}");
            }
        }
    }
}

サンプルコード中のコメントにも書きましたが、UnitPrice 列は money 型ですが LIKE 句を使ってあいまい検索する場合はパラメータの型は文字列とする必要がありますので注意してください (例えば、SqlDbType.NVarChar を SqlDbType.Deciaml にするとエラーになります)。

上のコードの実行結果は以下の通りで、上の画像の SSMS での実行結果と同じになっています。

ADO.NET での検索結果

Tags: , , ,

SQL Server

localhost への同時接続数

by WebSurfer 18. June 2021 15:03

.NET Framework を使って Web にアクセスする WinForms などのアプリ開発時に、検証用に自分の開発マシンに Web アプリを作ってアクセスすることがあると思います。その際、localhost への要求の場合は同時接続数の制約が外れることに注意が必要という話を書きます。(検証したのは HttpWebRequest のみで HttpClient は未検証。Core はすべて未検証です)

localhost の場合

(元の話は teratail のスレッド「C#にて並列かつ非同期でWebRequestを使用する方法について」です)

HTTP 1.1 仕様では同時接続は 2 つまでとなっています。HttpWebRequest を使った場合にもその制約が適用されるようですが、(1) localhost への要求の場合で、かつ (2) ユーザーが同時接続数の設定 (ServicePointManager.DefaultConnectionLimit) を変えない場合、Int32.MaxValue になります。

上の画像がその例です。要求先はローカルの IIS Express で動く検証用の ASP.NET Web アプリで、要求を受けて 10 秒後に OK という文字列を返します。その URL を、この記事の下の方に記載したサンプルコードにあるように、マルチスレッドで HttpWebRequest を使って同時に 5 回要求した結果です。end の時間が 5 つとも同じになっているのに注目してください。

同時接続が 2 つまでに制限されていれば、一度にサーバーに要求が出るのは 2 つまでで、3 つ目以降の要求は 10 秒経って先の応答が戻ってこないと出ないので、2 つおきに end の時間が 10 秒ずつ遅れるという結果になるはずです。そうなっていないのは、localhost への要求なので同時接続数の制限が外れたからです (ServicePoint.ConnectionLimit が Int32.MaxValue になっています)。

自分が使っているレンタルサーバー (当然 localhost ではない) で動く ASP.NET Web Forms アプリに、要求を受けて 10 秒後に Hello World という文字列を返す HTTP ジェネリックハンドラを作って、それを下のサンプルコードで同時に 5 回要求した場合は下のようになります。

surferonwww.info の場合

デフォルトの同時接続数 2 は有効になっていて (ServicePoint.ConnectionLimit が 2 になっています)、2 を超えた分は先の応答が返ってきてからでないと要求が出ず、上の画像の end を見ると分かりますが 2 要求毎に 10 秒ずつ待たされる���いう結果になりました。

ただし、Fiddler を使うと話が違ってきます。Fiddler は IP アドレス 127.0.0.1 のプロキシなのですが、どこかで IP アドレスから localhost と判定されているようで、同時接続数は制限されないという結果になりました。下の画像がその結果です。

Fiddler 経由で surferonwww.info を要求

ServicePoint.ConnectionLimit を取得するとデフォルトの同時接続数 2 となっていますが、実際の動きを見るとそれは無視されていて (Fiddler の画面を見ていると一度に 5 つ要求が出るのが分かります)、応答が返ってきた時間(end の時間)が 5 つとも同じになっています。

開発時に Fiddler で要求・応答をキャプチャして見るということはよく行うと思いますが、Fiddler の有無で同時接続数が異なるというのも要注意と思いました。

もう一つ、開発マシンのローカル IIS で動く ASP.NET Web Forms アプリに、hosts ファイルで 127.0.0.1 に websiteproject.com というホスト名を付けて、そのホスト名で呼び出せるようにし、それを下のサンプルコードで同時に 5 回要求した場合はどうなるを試してみました。結果は以下の画像の通りです。

websiteproject.com の場合

画像の end を見るとデフォルトの同時接続数 2 は有効になっているようで、2 要求毎に 10 秒ずつ待たされるという結果になっています。要求先の IP アドレスは 127.0.0.1 なのですが、.NET Framework ライブラリは IP アドレスでではなくて localhost という名前で判定しているのでしょうか。

ただし、ServicePoint.ConnectionLimit が Int32.MaxValue になっているのが解せません。

さらに、Fiddler を使うとどうなるかと言うと、上のケースと同様に同時接続数は制限されないという結果になりました。下の画像がその結果です。

Fiddler 経由で websiteproject.com を要求

この時は、ServicePoint.ConnectionLimit が何故か 2 になるのですが、これも解せません。

ソースコードを Microsoft のサイト ServicePoint.csServicePointManager.cs で見ることができるのですが、localhost か否かの判定はどのタイミングでどのようにしているのか、何がどのタイミングで ConnectionLimit プロパティの getter を呼び出して同時接続数を Int32.MaxValue に設定しているのか等々は読み切れませんでした。

ServicePointManager.DefaultConnectionLimit や ServicePoint.ConnectionLimit の値など、いろいろ不可解な動きに見えるのですが、ServicePoint.cs のソースコードのコメントに、

3. If ServicePoint.DefaultConnectionLimit is set, then take that value

4. If ServicePoint is localhost, then set to infinite (TO Should we change this value?)

・・・と書いてある通りで、とにかく localhost はデフォルトでは同時接続数は Int32.MaxValue になるということは間違いなさそうです。

最後に、検証に使った HttpWebRequest のコンソールアプリのコードを載せておきます。Visual Studio 2019 のテンプレートで .NET Framework 4.8 で作ったものです。

using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Net;
using System.Linq;
using System.Text;
using System.Threading;

namespace ConsoleAppAsync4
{
    class Program
    {
        static async Task Main(string[] args)
        {
            // localhost
            // ローカルの IIS Express で動くASP.NET MVC5 アプリの
            // アクションメソッド。
            // 要求を受けて 10 秒後に OK という文字列を返す
            var uri = "https://localhost:44365/Home/sample";

            // surferonwww.info
            // レンタルサーバーの IIS で動く ASP.NET Web Forms ア
            // プリの HTTP ジェネリックハンドラ。
            // 要求を受けて 10 秒後に Hello World という文字列を返す
            //var uri = "http://surferonwww.info/Test/Sample.ashx";

            // websiteproject.com
            // ローカル IIS で動く ASP.NET Web Forms アプリの HTTP
            // ジェネリックハンドラ。
            // hosts ファイルで 127.0.0.1 に websiteproject.com と
            // いうホスト名を付けたのでそのホスト名で呼び出せる。
            // 要求を受けて 10 秒後に Hello World という文字列を返す
            //var uri = "http://websiteproject.com/Sample.ashx";

            Console.WriteLine($"ServicePointManager.DefaultConnectionLimit = " +
                              $"{ServicePointManager.DefaultConnectionLimit}");
            
            var sp = ServicePointManager.FindServicePoint(new Uri(uri));
            Console.WriteLine($"ServicePoint.ConnectionLimit = " +
                              $"{sp.ConnectionLimit}");

            var encoding = new UTF8Encoding(false);
            var tasks = new List<Task>();

            // 同じ URL を 5 回同時に要求する。実際に Web サーバーに
            // いくつ同時に要求が出るかは ConnectionLimit による (はず)
            foreach (var i in Enumerable.Range(0, 5))
            {
                var task = Task.Run(async () =>
                {
                    // ThreadId と開始時刻
                    int id = Thread.CurrentThread.ManagedThreadId;
                    string start = $" / ThreadID = {id}, " +
                                   $"start: {DateTime.Now:ss.fff}, ";
                    
                    var request = (HttpWebRequest)WebRequest.Create(uri);
                    using (var response = await request.GetResponseAsync())
                    using (var stream = response.GetResponseStream())
                    using (var memory = new System.IO.MemoryStream())
                    {
                        await stream.CopyToAsync(memory);
                        var text = encoding.GetString(memory.ToArray());

                        // 終了時刻と ServicePoint.ConnectionLimit
                        string end = $"end: {DateTime.Now:ss.fff}";
                        string limit = $", limit: {sp.ConnectionLimit}";
                        text += start + end + limit;

                        Console.WriteLine(text);
                    }
                });

                tasks.Add(task);
            }

            await Task.WhenAll(tasks);

            Console.WriteLine("Finish");
            Console.ReadLine();
        }
    }
}

Tags: , ,

.NET Framework

Settings.settings の使い方

by WebSurfer 5. June 2021 15:09

.NET Framework アプリで利用できる Settings.settings からの情報の取得方法と、アプリからの情報の設定方法を備忘録として書いておきます。

Settings.settings

例えば Windows Forms アプリでウィンドウの表示位置とサイズを設定するため、上の画像のように Settings.settings に値を格納したとします。

それを以下のコードのように利用できます。

Form1.cs のコード

初回にユーザーがアプリを立ち上げた時、Settings.settings で指定した位置とサイズでフォームが表示されるよう、コンストラクタ Form1 で構成ファイルから値を取得して位置とサイズを設定しています。

その後、ユーザーが表示位置とサイズを変更した場合、次回アプリを立ち上げた時に変更した位置とサイズで表示できるよう、Form をクローズするときのイベントハンドラ Form1_FormClosing で位置とサイズ情報を構成ファイルに書き込んで保存しています。

以上で本題の話は終わりなのですが、その時使われる構成ファイルがどうなっているかについても調べましたので、オマケで書いておきます。

基本的には先の記事「構成ファイルの保存場所」に書いた通りです。それを画像を使ってもう少し詳しく書きます。

一番上の画像のように Settings.settings に値を格納すると、それらは自動的に App.config に反映されます。下の画像を見てください。

App.config

同時に Settings.Designer.cs に、Settings.settings に設定した Name と同じ名前でプロパティが設定され、Value の値がデフォルト値として設定されます。

Settings.Designer.cs

Settings.settings の Type に設定したとおりプロパティは int に型付けられていることに注意してください。型付けられていることが Settings.settings を使うことのメリットでもあります。Scope を User にするとプロパティには setter も含まれるようになり、プログラムから値を変更することができます(Scope が Application の場合は getter のみ)。

アプリをビルドすると、生成される .exe ファイルと同じ場所に、App.config の内容をそのままコピーして xxxxx.exe.config という名前のファイルが作られます。(xxxxx はアプリケーション名。Visual Studio のバージョンによってはデバッグ用はデフォルトで xxxxx.vshost.exe.config と異なるので注意してください)

xxxxx.exe.config

アプリを起動すると App.config からではなく、上記の構成ファイルから情報を取得し、それに指定されている位置とサイズでフォームを表示します。ただし、それは初回だけです。

上のコードの画像を見てください。フォームをクローズすると、イベントハンドラ Form1_FormClosing でクローズ時点での位置とサイズ情報を保存するようにしています。そのとき、user.config という構成ファイルを新たに作って情報を書き込みます。(App.config でも xxxxx.exe.config でもない点に注意してください)

user.config

2 回目以降アプリを立ち上げた際は user.config から位置とサイズ情報を取得してフォームを表示します。ユーザーが位置とサイズを変更すればフォームがクローズされる都度その時点での位置とサイズ情報が user.config に書き込まれます。

というわけで、.NET Framework のアプリでは以上の仕組みでユーザーが設定した情報が保持されるようになっているようです。

なお、.NET Core 3.1 の WinForms アプリでも、自分が試した限りですが、上記とほぼ同様に Settings.settings を利用できました。ただし、user.config は .NET Framework 版アプリと同様に生成されるものの、その他の構成ファイルがどのようになっているのかが調べ切れていません。今後の課題ということで・・・

Tags:

.NET Framework

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  September 2021  >>
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910

View posts in large calendar