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();
}
}
}
