最近、いろんなお誘いメールがケータイに舞い込んできて、いやもうマジクリックしちゃうぞコノヤロー。そんなに誘惑するんじゃないよ!
なんか迷惑メールが地震以降強烈に増えましたね。
結局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含んじゃってるのでちょっと大きいですがご容赦を。