C#と諸々

C#がメインで他もまぁ諸々なブログです
おかしなこと書いてたら指摘してくれると嬉しいです(´・∀・`)
つーかコメント欲しい(´・ω・`)

2009/01/14 13:13
こんなに手軽なものだったんだー、知らなかった
個人向けデスクトップアプリで DB 使いたい時なんかに便利かもかも


方法 : SQL Server Compact 3.5 データベースをアプリケーションと共に配置する

極小SQL Server Compactでデータベース・アプリをお手軽作成 - @IT


再配布権の登録というのが必要らしいのでそこは気を付けよう (@IT の記事の3ページ目のコラム参照)
スポンサーサイト



2009/01/05 18:15

実践!ソフトウェアアーキテクチャ VisualStudioとASP.NETによる業務システム開発方法

この本でユニットテスト時に SMO を使ってテスト用のデータベースを構築しているのを見て、僕も取り入れてみたので自分のためにメモ。
# 本に載ってる方法とは少し違う方法。

ユニットテストだけでなく、インストーラのカスタム動作でも使えるので便利。


まず、データベース管理用のクラスライブラリプロジェクトを作成。ここでは Hoge.Databases と名付けることにする。

次に、全てのテーブルを (再) 作成するためのクエリーファイルをプロジェクト内に作成。ビルドアクションは "埋め込まれたリソース" に設定。ここでは Create.sql と名付けることにする。

SET ANSI_NULLS ON
GO


-- テーブルを削除。子テーブルを先に削除すること。
IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Table2]') AND type in (N'U'))
    DROP TABLE [dbo].[Table2]
GO

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Table1]') AND type in (N'U'))
    DROP TABLE [dbo].[Table1]
GO


-- テーブルを作成。親テーブルを先に作成すること。
CREATE TABLE [dbo].[Table1]
(
    [PrimaryKey] [bigint] IDENTITY(1,1) NOT NULL,
    [Id] [nvarchar](255) NOT NULL,
    [Name] [nvarchar](255) NOT NULL,
    
    CONSTRAINT [PK_Table1] PRIMARY KEY CLUSTERED ([PrimaryKey]),
    CONSTRAINT [IX_Table1_Id] UNIQUE NONCLUSTERED ([Id])
) ON [PRIMARY]
GO

CREATE TABLE [dbo].[Table2]
(
    [PrimaryKey] [bigint] IDENTITY(1,1) NOT NULL,
    [Table1PrimaryKey] [bigint] NOT NULL,
    [Value] [nvarchar](255) NOT NULL,
    
    CONSTRAINT [PK_Table2] PRIMARY KEY CLUSTERED ([PrimaryKey]),
    CONSTRAINT [FK_Table2_Table1] FOREIGN KEY([Table1PrimaryKey])
        REFERENCES [dbo].[Table1] ([PrimaryKey])
        ON UPDATE CASCADE
        ON DELETE CASCADE
) ON [PRIMARY]
GO


次に、SMO を使ってデータベースを管理するクラスを作成。

using System;
using System.IO;
using System.Reflection;
using Microsoft.SqlServer.Management.Smo;

namespace Hoge.Databases
{
    /// <summary>
    /// Hoge データベースを管理します。
    /// </summary>
    public sealed class HogeDatabase
    {
        #region Static Members

        /// <summary>
        /// Hoge データベースに含めるテーブルを生成するための SQL クエリーファイルのリソースパスを取得します。
        /// </summary>
        private const string CreateScriptResourcePath = "Hoge.Databases.Create.SQL";

        /// <summary>
        /// 指定したリソースパスに含まれるリソースを文字列として読み出します。
        /// </summary>
        /// <param name="resourcePath">リソースパス。</param>
        /// <returns></returns>
        static private string ReadResourceText(string resourcePath)
        {
            Assembly thisAssembly = Assembly.GetExecutingAssembly();
            using (Stream resourceStream = thisAssembly.GetManifestResourceStream(resourcePath))
            using (StreamReader resourceReader = new StreamReader(resourceStream))
            {
                return resourceReader.ReadToEnd();
            }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// HogeDatabase クラスの新しいインスタンスを初期化します。
        /// </summary>
        /// <param name="server">SQL Server のインスタンス名。</param>
        /// <param name="database">データベース名。</param>
        public HogeDatabase(string instanceName, string databaseName)
        {
            this._server = new Server(instanceName);
            this._database = new Database(this._server, databaseName);
        }

        #endregion

        #region Fields

        /// <summary>
        /// SQL Server のインスタンスの管理オブジェクトを取得します。
        /// </summary>
        private readonly Server _server;

        /// <summary>
        /// データベースの管理オブジェクトを取得します。
        /// </summary>
        private readonly Database _database;

        #endregion

        #region Properties

        /// <summary>
        /// Hoge データベースが存在するかどうかを示す値を取得します。
        /// </summary>
        /// <returns>Hoge データベースが存在するかどうかを示す値。</returns>
        public bool Exists
        {
            get
            {
                this._database.Refresh();
                return (this._database.State == SqlSmoState.Existing);
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Hoge データベース、各テーブルを作成し、ASPNET アカウントをログイン登録します。
        /// </summary>
        /// <exception cref="InvalidOperationException">データベースは既に存在します。</exception>
        public void Create()
        {
            this.CreateDatabase();
            this.CreateTables();
            this.CreateLoginForAspnet();
        }

        /// <summary>
        /// Hoge データベースを作成します。
        /// </summary>
        /// <exception cref="InvalidOperationException">データベースは既に存在します。</exception>
        public void CreateDatabase()
        {
            if (this.Exists)
            {
                throw new InvalidOperationException("データベースは既に存在します。");
            }
            this._database.Refresh();
            this._database.Create();
        }

        /// <summary>
        /// Hoge データベースの各テーブルを作成します。
        /// テーブルが既に存在する場合は、テーブルを削除してから作成を行います。
        /// </summary>
        /// <exception cref="InvalidOperationException">データベースは存在しません。</exception>
        public void CreateTables()
        {
            if (!this.Exists)
            {
                throw new InvalidOperationException("データベースは存在しません。");
            }
            string createScript = ReadResourceText(CreateScriptResourcePath);
            this._database.ExecuteNonQuery(createScript);
        }

        /// <summary>
        /// 結果を返さない SQL コマンドを実行します。
        /// </summary>
        /// <param name="sqlCommand">SQL コマンド。</param>
        /// <exception cref="InvalidOperationException">データベースは存在しません。</exception>
        public void ExecuteNonQuery(string sqlCommand)
        {
            if (!this.Exists)
            {
                throw new InvalidOperationException("データベースは存在しません。");
            }
            this._database.ExecuteNonQuery(sqlCommand);
        }

        /// <summary>
        /// Hoge データベースを削除します。
        /// </summary>
        /// <exception cref="InvalidOperationException">データベースは存在しません。</exception>
        public void Drop()
        {
            if (!this.Exists)
            {
                throw new InvalidOperationException("データベースは存在しません。");
            }
            this._server.Refresh();
            this._server.KillDatabase(this._database.Name);
            System.Data.SqlClient.SqlConnection.ClearAllPools();
        }

        /// <summary>
        /// IIS 5.1 上に配置された ASP.NET アプリケーションから DB にアクセス (R/W) できるよう、
        /// ASPNET アカウントをログイン登録します。
        /// </summary>
        public void CreateLoginForAspnet()
        {
            if (!this.Exists)
            {
                throw new InvalidOperationException("データベースは存在しません。");
            }

            string name = string.Format(@"{0}\{1}", Environment.MachineName, "aspnet");
            this._server.Refresh();
            if (!this._server.Logins.Contains(name))
            {
                Login login = new Login(this._server, name);
                login.LoginType = LoginType.WindowsUser;
                login.Create();
            }

            this._database.Refresh();
            User user = new User(this._database, name);
            if (!this._database.Users.Contains(name))
            {
                user.Login = name;
                user.Create();
            }
            if (!user.IsMember("db_datareader"))
            {
                user.AddToRole("db_datareader");
            }
            if (!user.IsMember("db_datawriter"))
            {
                user.AddToRole("db_datawriter");
            }
        }

        #endregion
    }
}




ユニットテストクラスでの使用例
private const string InstanceName = @"localhost\sqlexpress";
private const string DatabaseName = "HogeDatabase_Test";

private HogeDatabase _testDatabase;

[TestFixtureSetUp]
public void CreateDatabase()
{
    this._testDatabase = new HogeDatabase(InstanceName, DatabaseName);
    if (this._testDatabase.Exists)
    {
        this._testDatabase.Drop();
    }
    this._testDatabase.Create();
}

[SetUp]
public void CreateTables()
{
    this._testDatabase.CreateTables();
}

[TestFixtureTearDown]
public void DropDatabase()
{
    if (this._testDatabase.Exists)
    {
        this._testDatabase.Drop();
    }
}

private void InsertTable1(string id, string name)
{
    const string insertQueryFormat = "INSERT INTO [dbo].[Table1] ([Id], [Name]) VALUES ('{0}', '{1}')";
    string insertQuery = string.Format(insertQueryFormat, id.Replace("'", "''"), name.Replace("'", "''"));

    this._testDatabase.ExecuteNonQuery(insertQuery);
}

[Test]
public void テスト1()
{
    for (int i = 0; i < 3; i++)
    {
        InsertTable1(i.ToString(), string.Format("Test {0:000}", i));
    }

    // テストコード
}


// 2009/01/14
ユニットテストの SetUp と TearDown で DB の作成・削除を実行すると、2 度目以降の DB 作成に失敗してしまうため、DB の作成・削除は TestFixtureSetUp と TestTearDown で行い、テーブルの再作成だけ SetUp で行うように、HogeDatabase クラスとユニットテストを変更。
HogeDatabase クラスの実装方法も全体的に変更。
ついでに ASPNET アカウントの R/W 権限を登録するメソッドも追加。(IIS 5.1 のみ正常に機能する)

// 2009/02/02
テストケースが複数あって DB の作成・削除が2度行われる場合、SqlConnection のプールが残ってしまう関係でエラーが発生する。そのため、DB の削除時に ClearAllPools メソッドを呼び出すよう修正。
参考 : DBバックアップ→DBリストア→テーブル内容参照時にException - Insider.NET
2008/01/31 00:26
今まで LINQ to Object の基本くらいしか知らなかったので、今日は大収穫を得た。
一番の収穫はやはり LINQ to SQL。
つか、LINQ to SQL の場合、where 句のラムダ式が SQL の where 句に変換されるというのは、正直半信半疑だった。
で、帰宅して早速試してみた。

static void Main(string[] args)
{
    DataContext context = new DataContext(Settings.Default.Database1ConnectionString);
    var query =
        from target in context.GetTable<Table1>()
        where (target.Column1 == "a")
        select target;

    string queryText = query.ToString();
    Console.WriteLine(queryText);
}

[Table(Name = "Table1")]
class Table1
{
    [Column(IsPrimaryKey = true, IsDbGenerated = true)]
    public int PrimaryKey;

    [Column]
    public string Column1;
}

実行結果
SELECT [t0].[PrimaryKey], [t0].[Column1]
FROM [Table1] AS [t0]
WHERE [t0].[Column1] = @p0


Σ(゚д゚lll)ガーン

ホントに where 句が where 句に・・・!
そういや、LINQ to SQL のラムダ式はデリゲートではなく System.Linq.Expressions.Expression オブジェクトに変換されると言っていたな。
逆コンパイルしてみると、確かに・・・つーか結構複雑なことやってるな。target.Column1 == "a" というラムダ式から、target とか Column1 とか == 演算子なんかのメタデータを採取して、それを元に Expression オブジェクトを構築するようなコードへと変換されている。
なるほどなるほど。

でもまぁこんなクエリー式はさすがに無理だろ。

var query =
    from target in context.GetTable<Table1>()
    where (target.Column1[1].ToString() == "a")
    where (target.Column1.IndexOf("b") <= 3)
    select target;

実行結果
SELECT [t0].[PrimaryKey], [t0].[Column1]
FROM [Table1] AS [t0]
WHERE ((
    (CASE
        WHEN (DATALENGTH(@p0) / 2) = 0 THEN CONVERT(BigInt,0)
        ELSE CONVERT(BigInt,(CONVERT(Int,CHARINDEX(@p0, [t0].[Column1]))) - 1)
     END)) <= @p1) AND ((CONVERT(NVarChar(MAX),CONVERT(NChar(1),SUBSTRING([t0].[Column1], @p2 + 1, 1)))) = @p3)




( ゚Д゚ ) ・・・。



タグ: .NET C# ADO.NET LINQ
2007/08/02 00:57
日経BPソフトプレス から 「プログラミング Microsoft ADO.NET 2.0」 が発刊されたそうです。

日経BP書店|商品詳細 - プログラミングMicrosoft ADO.NET 2.0

定価は 8,925 円 とやや高めですが、MCP 資格を保有している人は以下のページにて 10% OFF の 8,033 円 で購入できます。

プログラミング Microsoft ADO.NET 2.0 ( 日経BPソフトプレス )

これはもう買うしかないですね。僕は財布と相談もせずに注文してしまいました ^^;


[ 情報元 ]
The road to C# master trapemiya




# あれ、Windows Live ID にサインインしていなくても、割引が適用されるんだけど・・・。
# もしかして MCP 資格持ってなくても割引価格で購入できんの・・・?
タグ: .NET C# ADO.NET 書籍