DbMetal
DbLinqに含まれるDbMetal(DbMetal.exe)を使用することで、作成したSQLite DB「NerdDinner.sqlite」に対応するいくつかのクラス(DataContextクラス、各テーブルに対応するエンティティクラス)を含むC#コードを生成できます。
DbMetal実行前に構成情報ファイル(DbMetal.exe.config)の準備、また「Mono.Data.Sqlite.dll」をDbMetalと同じディレクトリ(もしくは環境変数MONO_PATHに設定したディレクトリ)に配置する必要があります。
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <section name="providers" type="DbMetal.Configuration.ProvidersSection, DbMetal" /> </configSections> <connectionStrings> <add name="DbMetal.Properties.Settings.TempOneConnectionString" connectionString="Data Source=.\sqlexpress;Initial Catalog=TempOne;Integrated Security=True" providerName="System.Data.SqlClient" /> </connectionStrings> <appSettings> <add key="ClientSettingsProvider.ServiceUri" value="" /> </appSettings> <providers> <providers> <provider name="MySQL" dbLinqSchemaLoader="DbLinq.MySql.MySqlSchemaLoader, DbLinq.MySql" databaseConnection="MySql.Data.MySqlClient.MySqlConnection, MySql.Data" /> <provider name="Oracle" dbLinqSchemaLoader="DbLinq.Oracle.OracleSchemaLoader, DbLinq.Oracle" databaseConnection="System.Data.OracleClient.OracleConnection, System.Data.OracleClient" /> <provider name="OracleODP" dbLinqSchemaLoader="DbLinq.Oracle.OracleSchemaLoader, DbLinq.Oracle" databaseConnection="Oracle.DataAccess.Client.OracleConnection, Oracle.DataAccess" /> <provider name="PostgreSQL" dbLinqSchemaLoader="DbLinq.PostgreSql.PgsqlSchemaLoader, DbLinq.PostgreSql" databaseConnection="Npgsql.NpgsqlConnection, Npgsql" /> <provider name="SQLite" dbLinqSchemaLoader="DbLinq.Sqlite.SqliteSchemaLoader, DbLinq.Sqlite" databaseConnection="Mono.Data.Sqlite.SqliteConnection, Mono.Data.Sqlite" /> <provider name="Ingres" dbLinqSchemaLoader="DbLinq.Ingres.IngresSchemaLoader, DbLinq.Ingres" databaseConnection="Ingres.Client.IngresConnection, Ingres.Client" /> <provider name="Firebird" dbLinqSchemaLoader="DbLinq.Firebird.FirebirdSchemaLoader, DbLinq.Firebird" databaseConnection="FirebirdSql.Data.FirebirdClient.FbConnection, FirebirdSql.Data.FirebirdClient" /> </providers> </providers> <system.web> <membership defaultProvider="ClientAuthenticationMembershipProvider"> <providers> <add name="ClientAuthenticationMembershipProvider" type="System.Web.ClientServices.Providers.ClientFormsAuthenticationMembershipProvider, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" /> </providers> </membership> <roleManager defaultProvider="ClientRoleProvider" enabled="true"> <providers> <add name="ClientRoleProvider" type="System.Web.ClientServices.Providers.ClientRoleProvider, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" cacheTimeout="86400" /> </providers> </roleManager> </system.web> </configuration>
$ ln -s /opt/mono/2.4.2.3/lib/mono/2.0/Mono.Data.Sqlite.dll . $ mono DbMetal.exe -namespace:NerdDinner.Models -provider:SQLite -conn:"Data Source=NerdDinner.sqlite" -code:NerdDinner.DbMetal.cs DbLinq Database mapping generator 2008 version 0.19 for Microsoft (R) .NET Framework version 3.5 Distributed under the MIT licence (http://linq.to/db/license) >>> Reading schema from SQLite database <<< writing C# classes in file 'NerdDinner.DbMetal.cs'
生成されたC#コードは、NerdDinnerでの使用に合わせるために多少の修正を施す必要があります。
using System; using System.Data; using System.Data.Linq.Mapping; using System.Diagnostics; using System.Reflection; using DbLinq.Data.Linq; using DbLinq.Vendor; using System.ComponentModel; namespace NerdDinner.Models { public partial class NerdDinnerDataContext : DbLinq.Data.Linq.DataContext { public NerdDinnerDataContext() : this(global::System.Configuration.ConfigurationManager.ConnectionStrings["NerdDinnerConnectionString"].ConnectionString) { } public NerdDinnerDataContext(string connectionString) : this(new Mono.Data.Sqlite.SqliteConnection(connectionString)) { } public NerdDinnerDataContext(IDbConnection connection) : this(connection, new DbLinq.Sqlite.SqliteVendor()) { } public NerdDinnerDataContext(IDbConnection connection, IVendor vendor) : base(connection, vendor) { } public Table<Dinner> Dinners { get { return GetTable<Dinner>(); } } public Table<RSVP> RSVPs { get { return GetTable<RSVP>(); } } } ... }
DbLinq(r1208)ではエンティティクラス内のOnValidateメソッドによるValidation(検証)の起動は有効にならないようです。ということでDinnerクラスのValidationの起動を変更する必要があります(「Controllers」で後述)。
/* コメント化 partial void OnValidate(ChangeAction action) { if (!IsValid) throw new ApplicationException("Rule violations prevent saving"); } */
Models and Helpers
元々のNerdDinner DBで定義されているユーザー定義関数は、パラメータとして与えられた位置(経緯度)から100km圏内のDinner情報を返すというものです。そこで、新たに2地点間の距離を測るメソッド(DinnerRepositoryHelper#DistanceBetween)を追加して、DinnerRepository#FindByLocationメソッドで使用されているLINQに組み込みました。
DinnerRepository#FindUpcomingDinnersメソッドの修正については、SQLiteで定義できるデータ型(DateTime型に対応するのはText型)に基因する例外が発生したので、SQL文を直接記述する修正を行いました。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using NerdDinner.Helpers; // 追加 namespace NerdDinner.Models { public class DinnerRepository : NerdDinner.Models.IDinnerRepository { NerdDinnerDataContext db = new NerdDinnerDataContext(); // // Query Methods public IQueryable<Dinner> FindAllDinners() { return db.Dinners; } /* 修正前 public IQueryable<Dinner> FindUpcomingDinners() { return from dinner in FindAllDinners() where dinner.EventDate > DateTime.Now orderby dinner.EventDate select dinner; } public IQueryable<Dinner> FindByLocation(float latitude, float longitude) { var dinners = from dinner in FindUpcomingDinners() join i in db.NearestDinners(latitude, longitude) on dinner.DinnerID equals i.DinnerID select dinner; return dinners; } */ // 修正後 public IQueryable<Dinner> FindUpcomingDinners() { return (db.ExecuteQuery<Dinner>( "SELECT * FROM Dinners WHERE EventDate > datetime('now', 'localtime') ORDER BY EventDate")) .AsQueryable(); } public IQueryable<Dinner> FindByLocation(float latitude, float longitude) { var dinners = from dinner in FindUpcomingDinners() let d = (DinnerRepositoryHelper.DistanceBetween(latitude, longitude, dinner.Latitude, dinner.Longitude) < 100) ? dinner : null where d != null select d; return dinners; } ... } }
using System; namespace NerdDinner.Helpers { public static class ExtMethods { public static double ToRad(this double degrees) { return degrees * Math.PI / 180.0; } public static double Sin(this double degrees) { return Math.Sin(degrees.ToRad()); } public static double Cos(this double degrees) { return Math.Cos(degrees.ToRad()); } public static double Acos(this double cos) { return Math.Acos(cos); } } public class DinnerRepositoryHelper { public static double DistanceBetween(double lat1, double long1, double lat2, double long2) { double earthRadius = 6378.1; // kms double sinLat1 = lat1.Sin(); double cosLat1 = lat1.Cos(); double sinLong1 = long1.Sin(); double cosLong1 = long1.Cos(); double sinLat2 = lat2.Sin(); double cosLat2 = lat2.Cos(); double sinLong2 = long2.Sin(); double cosLong2 = long2.Cos(); double cos1_2 = ( cosLat1 * cosLong1 * cosLat2 * cosLong2 ) + ( cosLat1 * sinLong1 * cosLat2 * sinLong2 ) + ( sinLat1 * sinLat2 ); return earthRadius * cos1_2.Acos(); } } }