最近、いろんなお誘いメールがケータイに舞い込んできて、いやもうマジクリックしちゃうぞコノヤロー。そんなに誘惑するんじゃないよ!
なんか迷惑メールが地震以降強烈に増えましたね。
結局enumはサポートされないことが決定してしまったCodeFirst。しょうがないですね。RCからRTMまでの間で機能追加はないので、ここは潔く諦めましょう。
でも、やっぱりenumを使いたいですよね。プロパティをパースするときにenumかenumのジェネリックは完全にスルーされてデータベーステーブルがScaffoldingされます。なのでComplex Typeとしてenumをラップしたクラスを用意し「オレenumじゃないよ、全然関係ないから!」とEFを騙す必要があります。
Tip 23 – How to fake Enums in EF 4 - Meta-Me - Site Home - MSDN Blogs
古のテクニックですね。2009年です。今もこれしか方法がないみたいです。
コンソールアプリを作りながら同じようにやってみよー!VS2010SP1を前提にしますよ?いいですか?だってSQLCE4使ってみたいでしょ。なのでSQLCE4も入ってる前提で行きます。なきゃないで問題ないので、SQLCE4関連の部分は読み替えてください。
ソリューション作ったらまずは、NuGet!NuGetで必要なパッケージを入れてしまいましょう。以下の2つ。
install-package EFCodeFirst
install-package EFCodeFirst.SqlServerCompact
↑こうです。
WebActivatorは不要なので、参照設定から削除しましょう。WebActivatorについては先日書いたので、気になる人はチェックしてみてね。
無聊を託つ: WebActivatorでお手軽Bootstrapper
App_Start/SQLCEEntityFramework.csもEFCodeFirst 0.8ベースなので少しいじっちゃいます。といっても、DbDatabaseをDatabaseにするのとWebActivatorの属性を削除。あと、SqlCe用のnamespace(System.Data.Entity.Infrastructure)が変わってるので、そこも変更。
これで準備完了。ワクワクするね!ワクワクの強要。
最初にモデルクラスをつくって、次にDbContext派生。行きます。
using System.Data.Entity;
namespace EFEnum
{
public enum Role
{
Unknown,
Sniper,
Captain
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public Role Role { get; set; }
}
public class EFEnumContext : DbContext
{
public DbSet<Person> People { get; set; }
}
}
↑EFEnumContext.cs
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="SampleContext" connectionString="Data Source=|DataDirectory|db.sdf;" providerName="System.Data.SqlServerCE.4.0"/>
</connectionStrings>
<system.data>
<DbProviderFactories>
<remove invariant="System.Data.SqlServerCe.4.0" />
<add name="Microsoft SQL Server Compact Data Provider 4.0" invariant="System.Data.SqlServerCe.4.0" description=".NET Framework Data Provider for Microsoft SQL Server Compact" type="System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" />
</DbProviderFactories>
</system.data>
</configuration>
↑App.config。
とりあえず、enumのまま作ってどういう動作になるのかを見てみます。
using System;
using System.Linq;
using EFEnum.App_Start;
namespace EFEnum
{
class Program
{
static void Main(string[] args)
{
SQLCEEntityFramework.Start();
var db = new EFEnumContext();
var query = from p in db.People
select p;
var count = query.Count();
Console.WriteLine("{0}人いるよ!", count);
if (count == 0)
{
var person1 = new Person
{
Name = "ルフィー",
Role = Role.Captain
};
var person2 = new Person
{
Name = "ウソップ",
Role = Role.Sniper
};
db.People.Add(person1);
db.People.Add(person2);
db.SaveChanges();
}
count = query.Count();
Console.WriteLine("{0}人いるよ!", count);
foreach (var person in query)
{
Console.WriteLine(person.Name + string.Format("({0})", person.Role));
}
Console.ReadLine();
}
}
}
↑main.cs。
public static class SQLCEEntityFramework {
public static void Start() {
Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
// Sets the default database initialization code for working with Sql Server Compact databases
// Uncomment this line and replace CONTEXT_NAME with the name of your DbContext if you are
// using your DbContext to create and manage your database
Database.SetInitializer(new DropCreateCeDatabaseIfModelChanges<EFEnumContext>());
}
}
SQLCEEntityFrameworkのStartメソッドにDbContextのクラスを指定するのも忘れずに。
ちょっと、コード長いけどドンマイ。行くぜ!
クリックして拡大してみてね。ちゃんと名前の横にロールが表示されました。これはAddするインスタンスに設定したものが、そのまま表示されただけです。次に一度終了して、もう一度実行してみます。
そうすると、今度は"Unknown"と出ました。なんでかというと、なんとテーブルには保存されてないからです!そういう仕様なので驚かないでね。VSから確認してみます。
カラム無いですね。ちょっと切ないですね。
さぁ本題です!ちょこちょこっとクラス用意しましょう。
public abstract class EnumWrapper<T>
{
private T _value;
public string Value
{
get
{
return _value.ToString();
}
set
{
_value = (T)Enum.Parse(typeof(T), value, true);
}
}
public static implicit operator string (EnumWrapper<T> value)
{
return value.Value;
}
}
これを基底クラスとしてenumの型毎にクラスを用意していきます。
public class RoleWrapper : EnumWrapper<Role>
{
public static implicit operator RoleWrapper(Role value)
{
return new RoleWrapper { Value = value.ToString() };
}
}
enum型1個しかないので、1個つくりましょう。ホントはimplicit operatorも書きたくないんだけど、こればっかりはしょうがない。何かいい方法ありますかね~?最終的にはT4で自動生成(プロジェクト内のenumを全部列挙してしまえばいいかな)させちゃえばいいでしょう。
そして、モデルクラスをこのクラスを使うように変更します。
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public RoleWrapper Role { get; set; }
}
このままだと、mainのConsole.WriteLineでクラス名がでちゃうのでそこはRole.Valueに変えておきましょう。そうして実行してみると...。
タターン!左が1回目。0件で追加して2件。右が2回目。2件あるから2件表示。ちゃんとロールも表示されますね。テーブル見てみましょう。
カラムも追加されてるし、値も入ってる~。文字列にしてるのはパースしやすいのと、パッと見データ見ただけでわかるからっていうだけの理由です。intがよければそのように。
いいんじゃないでしょうか。こんな感じで。
packages含んじゃってるのでちょっと大きいですがご容赦を。