カスタム属性再び

カスタム属性の使い道としてデータベースのテーブルやカラム情報を持たせて一元管理したらどうかと、サンプルをこさえてみました。

using System;
using System.Text;
using System.Reflection;

// 構造体のみ対象
// フィールドの順序が変わらないように・・・
[AttributeUsage(AttributeTargets.Struct)]
public class DBTableAttribute : Attribute {
  private string name;
  private string tablespace;
  private int initial;
  private int next;
  private int maxextents;
  private int pctincrease;

  public DBTableAttribute() {}

  // テーブル名
  // 指定しなければ型名になる
  public string Name {
    get { return name; }
    set { name = value; }
  }

  // テーブルスペース
  public string Tablespace { 
    get { return tablespace; } 
    set { tablespace = value; }
  }

  // INITIAL
  public int Initial {
    get { return initial; }
    set { initial = value; }
  }

  // NEXT
  public int Next {
    get { return next; }
    set { next = value; }
  }

  // MAXEXTENTS
  // 0の場合、UNLIMITED
  public int Maxextents {
    get { return maxextents; }
    set { maxextents = value; }
  }

  // PCTINCRESE
  public int Pctincrease {
    get { return pctincrease; }
    set { pctincrease = value; }
  }
}

// フィールドのみ対象
[AttributeUsage(AttributeTargets.Field)]
public class DBColumnAttribute : Attribute {
  private string name;
  private int size;
  private bool nullable;

  public string Name {
    get { return name;}
    set { name = value; }
  }

  public int Size {
    get { return size; }
    set { size = value; }
  }

  public bool Null {
    get { return nullable; }
    set { nullable = value; }
  }
}

// カスタム属性の利用例
// DBTableAttribute属性からcreate文を生成する
public class TableScriptCreator {
  public static string Create(Type type) {
    if (!type.IsDefined(typeof(DBTableAttribute), false))
      return null;
    DBTableAttribute tabatt = 
      type.GetCustomAttributes(typeof(DBTableAttribute), false)[0] as DBTableAttribute;
    string s, t;
    StringBuilder sb = new StringBuilder();
    s = tabatt.Name != null ? tabatt.Name : type.Name;
    sb.AppendFormat("CREATE TABLE {0} (\n", s.ToUpper());
    foreach (FieldInfo fi in type.GetFields()) {
      if (!fi.IsDefined(typeof(DBColumnAttribute), false))
        continue;
      DBColumnAttribute colatt =
        fi.GetCustomAttributes(typeof(DBColumnAttribute), false)[0] as DBColumnAttribute;
      if (fi.FieldType == typeof(int)) {
        t = String.Format("NUMBER({0})", colatt.Size);
      }
      else if (fi.FieldType == typeof(DateTime)) {
        t = "DATE";
      }
      else {
        t = String.Format("VARCHAR2({0})", colatt.Size);
      }

      s = (colatt.Name != null) ? colatt.Name : fi.Name;
      sb.AppendFormat("{0}{1}{2},\n", 
        s.ToUpper().PadRight(12), t.PadRight(16), colatt.Null ? "NULL" : "NOT NULL");
    }
    sb.Remove(sb.Length - 2, 1);
    sb.Append(")\n");
    sb.AppendFormat("TABLESPACE {0} \n", tabatt.Tablespace);
    sb.AppendFormat("STORAGE(INITIAL {0} NEXT {1} MAXEXTENTS {2} PCTINCREASE {3});",
      tabatt.Initial, 
      tabatt.Next, 
      (tabatt.Maxextents > 0) ? tabatt.Maxextents.ToString() : "UNLIMITED", 
      tabatt.Pctincrease);

    return sb.ToString();
  }
}

// テスト用構造体
[DBTable(Name="UserMaster",Tablespace="USERS",Initial=1024*512,Next=1024*256,Maxextents=0,Pctincrease=0)]
public struct User {
  // DB格納対象
  [DBColumn(Size=10,Null=false)]
  public int id;
  [DBColumn(Name="FIRSTNAME",Size=32,Null=false)]
  public string first;
  [DBColumn(Name="LASTNAME",Size=32,Null=false)]
  public string last;
  [DBColumn(Size=8,Null=false)]
  public DateTime birth;
  [DBColumn(Size=255,Null=true)]
  public string email;

  // DB格納対象外
  public DateTime loginTime;
}

public class App {
  public static void Main() {
    Console.WriteLine(TableScriptCreator.Create(typeof(User)));
  }
}

/* 結果
CREATE TABLE USERMASTER (
ID          NUMBER(10)      NOT NULL,
FIRSTNAME   VARCHAR2(32)    NOT NULL,
LASTNAME    VARCHAR2(32)    NOT NULL,
BIRTH       DATE            NOT NULL,
EMAIL       VARCHAR2(255)   NULL
)
TABLESPACE USERS
STORAGE(INITIAL 524288 NEXT 262144 MAXEXTENTS UNLIMITED PCTINCREASE 0); */

DBTableAttribute属性とDBColumnAttribute属性にデータベーステーブルを作るために必要な情報を持たせています。ユニークキーや外部キーなどの制約条件用のカスタム属性とかを用意したりすれば、結構使えそうな気がします。更にテーブルとオブジェクトのマッピングを行うクラスとか色々充実させていけば、一財産になりそーなんですが、仕事じゃ全く.NETを使う機会がないので、そこまで時間かけられないのが残念。うーん、.NETの開発やりたいです・・・
実際問題、世の中にはO/Rマッピングの優れたライブラリが沢山あるので、わざわざ自作しなくても良いのですが、個人的にこの手は好きなので、いつかやってしまいそう。作ったら会社に持っててみようかしら?(^^;