C#,WPF mdbに接続してデータを読み込む


社内の既存システムによって作られているmdbファイルに接続してデータの読込がしたかった

# mdbとの接続
接続にはOleDbConnection、OleDbCommand 、OleDbDataReader とかを使うらしい

## 1.OleDbConnectionの準備

```csharp
string provider = "Microsoft.Jet.OLEDB.4.0;";
string dataSource = "mdbファイルのパス";
OleDbConnection connection = new OleDbConnection("Provider = " + provider + ";Data Source = " + dataSource + ";");
```

OleDbConnectionコンストラクタの引数はConnectionStringで、接続先の情報とかを指定する

providerはデータベースを制御するデータベース・エンジンの種類のことらしい
mdbファイル ⇨ "Provider=Microsoft.Jet.OLEDB.4.0"
accdbファイル ⇨ "Microsoft.Ace.OLEDB.12.0"
でOK

dataSourceは接続するmdbファイルのパスを指定すればOK

## 2.OleDbCommandの準備

```csharp
string queryString = "SQL文";
OleDbCommand command = new OleDbCommand(queryString,connection);
```

OleDbCommandコンストラクタは第一引数にstringで記述したSQL文、第二引数にOleDbConnectionを指定する

## 3.接続して読み込む

```csharp
connection.Open();
var dataReader = command.ExecuteReader();
```

OleDbConnectionのOpen()で接続する
OleDbCommandのExecuteReader()でデータをOleDbDataReader型に読み込む

## 4.データを整形する

### そのままDataTableへ

```csharp
var table = new DataTable();
table.Load(dataReader);
```

データをそのままDataTableにするならDataTableのLoad()でOleDbDataReaderを指定するだけでOK
WPFのDataGridとかに表示させるだけなら多分これが一番簡単

### 一つづつ取り出す

```csharp
while (dataReader.Read())
{
    dataReader.GetValue(ordinal)
}
```

OleDbDataReaderのRead()でレコードを読み込む
こいつは次のレコードがなくなればfalseを返すのでwhileの条件に入れている
OleDbDataReaderのGetValue()でデータが取得できる
引数には列番号をintで指定する
列とか関係なく全部取得するなら

```csharp
while (dataReader.Read())
{
    for(int i =0; i< reader.FieldCount; i++)
    {
        Console.WriteLine(reader.GetValue(i));
    }
}
```

でOK
ただ、これだと整形しづらい

例えばこんなDBがあるとして

| ID |     名前    | 年齢 | 性別 |
|:--:|:---------:|:----:|:----:|
| 1  | 鈴木 健太 | 23   |   男  |
| 2  |   中村 明  | 26   |   男  |
| 3  | 山下 久美 | 21   |   女  |

こんなクラスのコレクションにデータを入れたい時

```csharp
public class Person
{
    public string Name { get; set; } = "";
    public int Age { get; set; }
    public string Gender { get; set; } = "";
}
```

mdbへの接続から一連の流れも含めて書いてみる
(WPFでReactivePropertyを使って実装)

**Model**

```csharp
public class Model
{
    public ReactiveCollection<Person> PersonList { get; set; } = new ReactiveCollection<Person>();

    public void ConnectTomdb(string path)
    {
        string provider = "Microsoft.Jet.OLEDB.4.0;";
        string queryString = "SELECT 名前,年齢,性別 FROM テーブル1";
        OleDbConnection connection = new OleDbConnection("Provider = " + provider + ";Data Source = " + path + ";");
        OleDbCommand command = new OleDbCommand(queryString, connection);
        try
        {
            connection.Open();
            var dataReader = command.ExecuteReader();
            PersonList.Clear();
            foreach (var person in DataReaderConvertToClass(dataReader))
            {
                PersonList.Add(person);
            }
        }
        catch (Exception e)
        {
            MessageBox.Show(e.Message);
        }
        finally
        {
            connection.Close();
        }
    }

    private IEnumerable<Person> DataReaderConvertToClass(OleDbDataReader dataReader)
    {
        while (dataReader.Read())
        {
            var person = new Person();
            var properties = person.GetType().GetProperties();
            for (int i = 0; i < properties.Count(); i++)
            {
                var data = dataReader.GetValue(i);
                if (!data.GetType().Equals(DBNull.Value.GetType())) properties[i].SetValue(person, data);
                //これでもOK
                //if (!string.IsNullOrWhiteSpace(data.ToString())) properties[i].SetValue(person, data);
            }
            yield return person;
        }
    }
}

public class Person
{
    public string Name { get; set; } = "";
    public int Age { get; set; }
    public string Gender { get; set; } = "";
}
```

OleDbDataReaderはDataReaderConvertToClassメソッドに渡して読み込み、
そこでPersonクラスを作って読み込んだ値をプロパティに詰めてPersonクラスをIEnumerableで返している
返ってきた値はPersonListにAddしている

Personクラスの各プロパティは、インスタンスにGetType()してGetProperties()すると取得できる
OleDbDataReaderのGetValue()でもindexを指定するのでfor文を使って各プロパティにSetValue()している

※GetProperties()すると上から順にプロパティを取得するので、DBの取得する列に合わせてプロパティを設定する必要が有るので注意

ちなみに、DBの値が空白だとGetValue()した時にSystem.DBNullが返ってきて、それをSetValueすると例外がでるので回避しとくこと

**ViewModel**

```csharp
public class ViewModel
    {
        public Model _Model { get; set; } = new AccessDBModel();
        public ReactiveProperty<string> _Path { get; set; } = new ReactiveProperty<string>();
        public ReactiveCommand C_getDB { get; } = new ReactiveCommand();

        public ViewModel()
        {
            C_getDB = _Path
                .Select(x => !string.IsNullOrWhiteSpace(x))
                .ToReactiveCommand();
            C_getDB.Subscribe(_ => _Model.ConnectTomdb(_Path.Value));
        }
    }
```

とても単純
\_PathはmdbファイルのパスでTextBoxとかにバインド
C_getDB はデータを取得するコマンドで、\_Pathが空白出ない時のみ有効にしている
コマンドではModelのConnectTomdbを呼び出し、\_Pathの値を渡している

**View**

```xml
<DataGrid x:Name="dataGrid" ItemsSource="{Binding _Model.PersonList}" HeadersVisibility="Column" >
    <DataGrid.CellStyle>
        <Style TargetType="DataGridCell">
            <Setter Property="TextBlock.TextAlignment" Value="Center"/>
        </Style>
    </DataGrid.CellStyle>
</DataGrid>
<TextBox Text="{Binding _Path.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="Load" Command="{Binding testCommand, Mode=OneWay}"/>
```

自動生成に任せるならこんな感じ
Collectionにしてバインドすると自分で列を指定していろいろカスタムすることもできる
また、DataTriggerを設定したら特定の値のある行に色をつけたりとかもできる

パスをいれてボタンを押すとこんな感じで取得できる
![2017-06-15_09h31_41.jpg](https://qiita-image-store.s3.amazonaws.com/0/145898/fc4f04c7-9fdd-0cbf-7d78-bcc34cea437d.jpeg)

# おわりに
読み込むだけならかなり簡単
とりあえず書き込みをすることはないので必要になれば調べるかも
同時接続とかは検証してない(・・;

参考:[MSDN/OleDbConnection](https://msdn.microsoft.com/ja-jp/library/system.data.oledb.oledbconnection(v=vs.110).aspx)