WebSurfer's Home

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

DB のアタッチ

by WebSurfer 2010年9月13日 23:56

SQL Server の既定のインスタンスに接続するためには、まず当該 DB をデータベースエンジンにアタッチしなければなりません。無知な素人(自分のこと)は、たぶんここでハマります。また時間を無駄にすることがないように備忘録を残しておきます。

ポイントは ACL の設定です。アタッチする前に、SQL Server のサービスアカウントに、データ/ログファイル (.mdf, .ldf) があるフォルダに対するフルコントロール権限を与えることが必要です。

ACL の設定

そのことは、MSDN ライブラリ「Windows サービス アカウントの設定」(SQL Server 2005 の記事ですが、このあたりは最新版でも同じ)に書いてあったのですが、隅っこのほうに小さく書いてあって気がつくのにかなり時間がかかりました。(笑)

「SQL Server サービスに対する Windows 権限の確認」のセクションに以下のように書いてあります。

"MSSQLServer サービスの開始アカウント: アカウントは、データまたはログ ファイル (.mdf、.ndf、.ldf) が常駐するフォルダーに対するフル コントロール権限を持っている必要があります。"

なお、Express Edition の場合は、上記の MSDN ライブラリのページに書いてあるアカウント名、グループ名を以下のように読み替えてください。(下の「2013/7/1 追記」に注意)

アカウント: MSSQL$SQLEXPRESS(正確には NT SERVICE\MSSQL$SQLEXPRESS)
グループ:  SQLServerMSSQLUser$<computer_name>$SQLEXPRESS

ASP.NET 開発環境で作った mdf ファイルを App_Data フォルダごとサーバーにコピーして、それをアタッチするのであれば、App_Data フォルダへのフルアクセス権を与えなければなりません。上の画面は、その場合の設定です。

ホスティングサービスが提供する SQL Server を使う場合は知らなくて済む話ですが、自サーバーを構築しようとするとこのあたりは避けて通れません。

ただ、ホスティングサービスの場合、mdf ファイルをコピーしてサーバーに張り付けることができないところに別の苦労があるのですけどね。

------- 2013/7/1 追記 -------

SQL Server の Express 版をインストールすると、デフォルトでは「名前つきインスタンス」となり、インスタンス名は SQLEXPRESS になります。(詳しくは、別の記事 SQLEXPRESS は「名前つきインスタンス」名 を参照してください)

上記のアカウント名 MSSQL$SQLEXPRESS、グループ名 SQLServerMSSQLUser$ComputerName$SQLEXPRESS はデフォルトで SQL Server Express をインストールした場合のものです。

Express 版でも、既定のインスタンスまたは SQLEXPRESS 以外の名前つきインスタンスとしてインストールできますが、その場合は上記の名前とは異なってきますので注意してください。具体的には以下のようになるはずです。

<アカウント名>
既定のインスタンス: NT SERVICE\MSSQLSERVER
名前つきインスタンス: NT SERVICE\MSSQL$<instance_name>

<グループ名>
既定のインスタンス: SQLServerMSSQLUser$<computer_name>$MSSQLSERVER
名前つきインスタンス: SQLServerMSSQLUser$<computer_name>$<instance_name>

------- 2014/6/12 追記 -------

正しく権限が設定できていても、アタッチしたデーターベースが「読み取り専用」となることがあります。

その場合は以下のようにすると「読み取り専用」を解除することができます。

SQL Server Management Studio を起動する ⇒ オブジェクトエクスプローラに表示されている当該データベースを右クリック ⇒ 出てくるコンテキストメニューの[プロパティ(R)]をクリック ⇒ 「データーベースのプロパティ」ダイアログが表示される ⇒ 「ページの選択」ウィンドウで[オプション]を選択 ⇒ 「その他のオプション/状態/読み取り専用データベース」を False に設定(下の画像参照)

「読み取り専用」の解除

Tags: ,

SQL Server

ユーザー権限の設定

by WebSurfer 2010年9月12日 20:55

開発環境でユーザーインスタンスを使って DB に接続するのではなく、DB ファイル(mdf ファイル)を SQL Server データーベースエンジンにアタッチして、それにアクセスして使えるようにするには、いろいろ面倒なことがいっぱいあります。

今回は、DB のアタッチとか、ログインユーザーの作成は済んでいるとして、ログインユーザーへの権限の与え方について備忘録を書いておきます。

ログインユーザーのサーバーロール設定

まず左の画像ですが、BGLB\SQLUser というログインユーザーへサーバーロールを与えているところです。クリックすると拡大画像が表示されます。

サーバーロールを与えるというより、デフォルトで public 権限が与えられていて、自分では何も設定していないのですが。(画像のように public にはデフォルトでチェックが入っていて、それを外そうとしても外せません)

public のサーバーレベルでの権限は、デフォルトで VIEW ANY DATABASE 権限と、既定のエンドポイント(TSQL Local Machine、TSQL Named Pipes、TSQL Default TCP、TSQL Default VIA)に対する CONNECT 権限のみです。権限は変更できるのですが、よほどの事情がない限り変更する必要はなさそうです。権限を追加したりすると、ログインユーザー全員にサーバーレベルでその権限を与えてしまうことになるので、注意が必要です。

ログインユーザーには、public の他に、dbcreator など 固定サーバーロール の権限を与えることができますが、データベースの更新、削除、実行、接続、選択、挿入をするだけの一般ユーザーに対しては、与えるサーバーロールは public のみで不足はないと思います。

ときどき、ADO.NET を使ったアプリケーションが出す "dbcreator 権限がありません" というようなエラーメッセージに惑わされ(?)て、ログインユーザーに dbcreator のセキュリティ特権を与えて(上の画像で言うと[サーバーロール (S):]下の一覧の中の dbcreator にチェックを入れて)解決したという記事を Web で目にしますが、それはやりすぎです。そのユーザーにサーバー全体で dbcreator 権限を与えてしまうことになりますので。

ログインユーザーの作り方は割愛しますが、何故 BGLB\SQLUser というドメ���ンユーザーアカウントを使っているかについて、ちょっと説明します。

自分の試験環境ではドメインコントローラに SQL Server をインストールしていますが(サーバーを 1 台しか持っていないので、苦肉の手段です)、その場合、NETWORK SERVICE マシンアカウントを SQL Server の実行アカウントに使用できないそうです(やってみましたが、実際できませんでした)。

そういうわけで、BGLB\SQLUser というドメインユーザーアカウントを設けて、それを SQL Server の実行アカウントにしています。

また、NETWORK SERVICE は何故かログインアカウントに設定できなかったので、BGLB\SQLUser をログインアカウントにしています。Web アプリケーション(IIS のワーカープロセス)から SQL Server にアクセスするために、BGLB\SQLUser を偽装しています。

ユーザーマッピング

次にユーザーマッピングの設定です。「このログインにマップされたユーザー(D)」でアクセスする DB を指定します。

アクセスする DB にチェックをつけるだけで、その他はデフォルトで OK です。「UserDatabase のデータベースロールメンバーシップ(R)」もデフォルトの public のみとしておきます。

アクセス権限の細かい設定は、それぞれの DB で行います。もちろん、それぞれの DB で行うより上位のレベル(即ち、ログインアカウントでの設定)でも可能ですが、それでは権限の与えすぎになってしまうようです。

その設定は、当該 DB を右クリックして表示される「データベースのプロパティ」ダイアログで行います。次の画像を見てください。

ユーザーマッピング

「ユーザーまたはロール(U)」でアカウントを選んで(画像では BGLB\SQLUser のみですが)、「BGLB\SQLUser の権限(P)」で「明示的」タブを選択します。

そこで、更新、削除、実行、接続、選択、挿入にチェックを入れれば、ほとんどの場合 OK だと思います。

この画面は、「明示的」タブでの選択結果を「有効」タブで確認しているところです。

本当は、データを操作するのに必要なストアドを作って、ストアドに権限を設定するのがよいのだそうです(DB 全体に対する権限を与えるのではなくて)。

でも、そこまではとてもできないので、特定のアカウントに対して、特定の DB 全体を CONNECT, EXECUTE, SELECT, INSERT, DELETE, UPDATE できる 6 つの権限を与えてみました。それでは権限の与えすぎかもしれませんが。

以上、特定のデーターベースユーザーに権限を与える方法を書きましたが、データベースロールを使う方法もあります。

例えば、データベースロールの中にも public ロールがあり、これに権限を与えると当該データベースのデータベースユーザー(ログインユーザーではなくて当該データベースにマップされた特定のユーザーのみである点に注意)全員にその権限が適用されます。

それではあるユーザーに対しては権限の与えすぎになってしまう場合は、そのユーザーの権限を「拒否」して調整することができます。

その方法については、SQL Server 2012 自習書の記事 3.8 オブジェクト権限の状態(GRANT/DENY/REVOKE)が参考になると思います。

Tags:

SQL Server

GridView に複数のテーブルを表示

by WebSurfer 2010年9月10日 13:10

あるテーブルを表示している GridView に 1 列追加し、そこに別テーブルからその行に関連する複数のレコード/フィールドを取得して表示するという話です。

結果は以下のような画面になります。MaterialDetails が追加した列です。

実行結果の画面

以下に具体的な例を説明します。

データベースで、「製品」は Products テーブルにて、「原料」は Materials テーブルにて管理されているとします。「製品」に使用されている「原料」の情報は Products や Materials テーブルには埋め込まず、関連を Relations という別テーブルで管理するとします。以下のような感じです。

製品テーブル (Products)
ProductID     int
ProductName   nvarchar(50)
UnitPrice     money

関連テーブル (Relations)
ProductID     int
MaterialID    int

原料テーブル (Materials)
MaterialID    int
MaterialName  nvarchar(50)
SupplierID    int

Products テーブルをベースに作った「製品」一覧の GridView に 1 列追加して、その列に、「製品」に使われている「原料」を表示します。

Materials テーブルから、関連するレコードの MaterialID, MaterialName, SupplierID をRelations テーブルを基に抽出し、追加した列に表示します。

製品 X には原料 a, b が、製品 Y には原料 a, e, f, h が使われているというように、追加した列に表示する Materials テーブルのレコード数は各行で一定ではありません。それに対応するための工夫が必要です。

具体的な手順は、以下のようになります。「原料」で表示するのが 1 フィールドだけでよいということであれば、もう少し簡単にできますが、基本的には以下の手順と同様です。

(1) Products の 型付 DataSet + TableAdapter

ソリューションにデータセット(xsd ファイル)を追加します。それにツールボックスから TableAdapter をドラッグ&ドロップします。

自動的に「TableAdapter 構成ウィザード」が起動しますので、そのウィザードを利用して Products テーブル用の型付 DataSet + TableAdapter を作成します。

基になる SELECT クエリは下記のようにします。

SELECT ProductID, ProductName, UnitPrice
FROM   Products

ウィザードが完了したら xsd ファイルを保存してください。その後、デザイナ画面のテーブル部分を右クリックして、[追加]→[列]で MaterialDetails 列を追加します。型はデフォルトで String になるはずです。

(2) Materials の型付 DataSet + TableAdapter

上記と同じ xsd ファイルの画面にツールボックスからもう一つ TableAdapter をドラッグ&ドロップし、クエリビルダで Materials テーブルと Relations テーブルの二つを追加します。

基になる SELECT クエリは下記のようにし、ウィザードを完了させます。

SELECT m.MaterialID, m.MaterialName, m.SupplierID
FROM   Materials AS m
       INNER JOIN Relations AS r
       ON m.MaterialID = r.MaterialID
WHERE  (r.ProductID = @ProductID)

上記 (1), (2) の結果は、以下の画面のようになります。

型付 DataSet + TableAdapter のデザイン画面

(3) GetProductsWithMaterials メソッド

ソリューションにクラスファイルを追加します。Products, Materials テーブルから必要なデータを抽出し、上記 (1) で作った型付 DataTable を初期化して返すメソッドを作成し、クラスファイルに実装します。

Products テーブルの TableAdapter を拡張する形で partial class にしてください。以下のような感じです。

using System;
using System.IO;
using System.ComponentModel;

namespace ProductsDataSetTableAdapters
{
  public partial class ProductsTableAdapter
  {
    [DataObjectMethod(DataObjectMethodType.Select, true)]
    public ProductsDataSet.ProductsDataTable GetProductsWithMaterials()
    {
      ProductsDataSet.ProductsDataTable prodTable = this.GetData();
      MaterialsTableAdapter adapter = new MaterialsTableAdapter();
      foreach (ProductsDataSet.ProductsRow row in prodTable.Rows)
      {
        ProductsDataSet.MaterialsDataTable mtrlTable = 
          adapter.GetData(row.ProductID);
        StringWriter writer = new StringWriter();
        mtrlTable.WriteXml(writer);
        row.MaterialDetails = writer.ToString();
      }
      return prodTable;
    }
  }
}

(4) aspx ファイル

データを表示する Web ページ(aspx ファイル)を作成します。ObjectDataSource と GridView を利用した例は下記の通りです。MaterialDetails 列の xml データは GridView の RowDataBound イベントで適宜書き換えます。

この例では、xml データを DataTable に戻して、GridView の中にもう一つ GridView を作って表示しています。GridView を使わず、文字列で表示するなど、別の方法も検討してみてください。

<%@ Page Language="C#" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Data" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

  protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
  {
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
      string xmlMaterialDetails = 
        (string)((DataRowView)e.Row.DataItem)["MaterialDetails"];
      StringReader reader = new StringReader(xmlMaterialDetails);
      ProductsDataSet.MaterialsDataTable table = 
        new ProductsDataSet.MaterialsDataTable();
      table.ReadXml(reader);
      GridView gv = new GridView();
      gv.DataSource = table;
      e.Row.Cells[3].Controls.Clear();
      e.Row.Cells[3].Controls.Add(gv);
      gv.DataBind();
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:ObjectDataSource ID="ObjectDataSource1" 
      runat="server" 
      OldValuesParameterFormatString="original_{0}" 
      SelectMethod="GetProductsWithMaterials" 
      TypeName="ProductsDataSetTableAdapters.ProductsTableAdapter">
    </asp:ObjectDataSource>
    <asp:GridView ID="GridView1" 
      runat="server" 
      AutoGenerateColumns="False" 
      DataKeyNames="ProductID" 
      DataSourceID="ObjectDataSource1" 
      OnRowDataBound="GridView1_RowDataBound">
      <Columns>
        <asp:BoundField DataField="ProductID" 
          HeaderText="ProductID" 
          InsertVisible="False" 
          ReadOnly="True" 
          SortExpression="ProductID" />
        <asp:BoundField DataField="ProductName" 
          HeaderText="ProductName" 
          SortExpression="ProductName" />
        <asp:BoundField DataField="UnitPrice" 
          HeaderText="UnitPrice" 
          SortExpression="UnitPrice" />
        <asp:BoundField DataField="MaterialDetails" 
          HeaderText="MaterialDetails" 
          SortExpression="MaterialDetails" />
      </Columns>
    </asp:GridView>
  </div>
  </form>
</body>
</html>

以上、ちょっと凝ったことをしましたが、「製品」:「原料」の関係が 1:1 もしくは 1:n (n=一定) の関係なら、3 つのテーブルを INNER JOIN した SELECT クエリをベースに、SqlDataSource と ListView で表示した方が簡単だと思います。

Tags: , ,

ASP.NET

About this blog

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

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar