ASP.NET Core/.NET Core でMySQL/MariaDBにあるデータを操作しようとすると問題が多々発生
Oracleの純正MySQL EntityFramework Coreのライブラリは、いくつか足りないメソッドがあったり、結果が違ったりする。
また、いくつかのメソッドがないので、代用方法で駆使する必要があり、覚えるのが大変なので、ちょっとその当たりを記録しておこうと思う。
環境
MySQL.Data.EntityFrameworkCoreを利用すると起こる現象なので、データベースはMySql/MariaDBどちらでも問題ないと思われる。
- MySql.Data (8.0.15)
- MySql.Data.EntityFrameworkCore (8.0.15)
- .NET Core 2.2
- Windows/Mac/Linuxどれでも問題なく発生してくれる
Index/Uniqueの設定は...
UniqueやIndexのあるテーブルがあり、スキャフォさせるために設定する Index というのが EntityFrameworkにあるということが書かれていたのですが、こちらMySqlEntityFrameworkには見当たらないのです。
正解は、Fluent APIを利用するとのこと。
なお、プライマリキーも1つのときは、[Key]で指定できますが、複数の場合はFluent APIを利用して定義するのが、EntityFrameworkの作法のようです
using Microsoft.EntityFrameworkCore;
using MySql.Data.MySqlClient;
[Table("test")]
public class TestInfo {
[Key]
[Column("id", TypeName = "int(11)")]
public int ID { set; get }
// id, name での複合プライマリキー
[Column("name", TypeName = "varchar(100)")]
public string Name { set; get; }
// email は unique
[Column("email", TypeName = "varchar(255)")]
public string Email { set;get; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Index/unique設定
modelBuilder.Entity<TestInfo>()
.HasIndex( t => t.Email )
.IsUnique();
// 複合プライマリキー
modelBuilder.Entity<TestInfo>()
.HasKey( g => new { g.ID, g.Name } );
}
動作しない Any()
Any() メソッドは、存在する場合 true 存在しない場合 false と、bool型で検証してくれるメソッドです。
var ret = MasterContext.Where( g => g.email == "xxxxx@xxxxxxxx.xxx" ).Any(); // No coercion operator is defined between types 'System.Int16' and 'System.Boolean'.
if( ret ) [true] else [false];
でも、例外が返ります。どうやら、MySQLのEntityFrameworkCoreにはちゃんと実装されてくれてないみたいです。とはいえ、.Count() メソッドを使うと COUNT(*) で数を数えてくれるのでめちゃくちゃ遅いです。そこで、FirstOrDefault() メソッドを利用することで、データが無ければ null を返してくれるという点を利用して実装しました。
// Any()メソッドはbool以外が返ってきて例外がでるので、別回避する必要あり
var ret = ( MasterContext.Where( g => g.Email == "xxxxx@xxxxxxxx.xxx" ).Select( g => new { g.Email } ).FirstOrDefault()) != null;
if( ret ) [true] else [false];
この方法だと、以下のSQLが吐き出されるので多分、Count()メソッド使うよりは早いかと。たぶん...
SELECT g.email WHERE g.email = 'xxxxx@xxxxxxxx.xxx' LIMIT 1;
ロギングしとく
まだ、チューニング方法や猫自身がEntityFrameworkを使うのがお初なので、コーディング方法を確立出来てないので、もしかするとまだまだ問題が出るかも。なので、そのために、そのSQL大丈夫?問いのを確認するために、ロギングする方法。
追加するライブラリ
- Microsoft.Extensions.Logging.Console
生成プロジェクトによるけど、これが入ってないと思いますので。
DbContext の継承クラスにて初期化
public class HogeHogeContext : DbContext
{
// 接続情報を保存するための入れ物
protected MySqlConnectionStringBuilder Config { private set; get; } = new MySqlConnectionStringBuilder();
// MySQLの接続情報を生成するためのおまじない
public HogeHogeContext(AppSettings settings) {
Config.Server = settings.Server;
Config.Port = settings.Port;
Config.UserID = settings.User;
Config.Password = settings.Pass;
Config.Database = settings.Schema;
Config.ConnectionTimeout = settings.ConnectionTimeout;
Config.DefaultCommandTimeout = settings.CommandTimeout;
}
// ログを出力させるためのおまじない
public static readonly LoggerFactory LoggerFactory = new LoggerFactory(
new[] {
new ConsoleLoggerProvider( (category, level ) => category == DbLoggerCategory.Database.Command.Name && level == LogLevel.Information, true );
}
});
// ロギング設定とデータベースの接続設定
protected override void OnConfiguring( DbContextOptionsBuilder optionsBuilder ) =>
optionsBuilder
.UseMySQL(Config.ConnectionsString)
.EnableSensitiveDataLogging()
.UseLoggerFactory(LoggerFactory);
... 以下略
}
上記、dotnet 2.x までらしいので、 dotnet 3.x からは、以下のように書くといいらしい。
using Microsoft.Extensions.Logging;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var logger = LoggerFactory.Create(builder =>
{
builder.AddDebug()
.AddFilter(category: DbLoggerCategory.Database.Command.Name, level: LogLevel.Information);
});
optionsBuilder.UseMySql(Config.ConnectionString).UseLoggerFactory(logger);
... 以下略
あとは、コンソール画面に勝手に出力してくれます。ただ、いまのこのConsoleLoggerProvider() クラスの書き方、古いらしく、新しく書き換えローってIDEに言われます。新しい書き方はまだ調査中。あと、コンソール画面に多数ログが出るので、SQL部分だけファイルで書き込みたいのですが、出来ませんかね...