【dnlib】DLL(.NET assembly/module) からちょっとした情報を抽出したい(1)
ソースを解析する際に、人力でコードを追う方法以外に、
DLL のメタ情報から欲しい情報を抽出出来ないかと調べた所、
↓のような素敵なライブラリが合ったので試しに使ってみました。
github.com
www.nuget.org
まずは、nuget にて dnlib をインストール。
ライブラリ自体は dll への read/write 両方とも出来るようですが、
一先ず、色々と抽出したいので read に絞って実験。
private static IEnumerable<Tuple<MethodDef, IMethodDefOrRef>> GetUsedMethodsAndProperties(IEnumerable<string> moduleFiles) { // func を再帰呼び出しする為に定義と実装を分離する。(ここは定義) Func<MethodDef, HashSet<MethodDef>, IEnumerable<Tuple<MethodDef, IMethodDefOrRef>>>? func = null; // ここは実装 func = (method, hashSet) => { var result = new List<Tuple<MethodDef, IMethodDefOrRef>>(); // 無限ループになる可能性がある為、1度処理したメソッドは処理しない。 if (method == null || hashSet.Contains(method)) return result; hashSet.Add(method); var methodBody = method.Body; if (methodBody == null) return result; foreach (var calledMethodOrProperty in methodBody.Instructions .Where(m => m.Operand is IMethodDefOrRef) .Select(m => (IMethodDefOrRef)m.Operand)) { // 出力したい条件を↓に指定する。 // ToString を呼んでいる処理を抽出 if (calledMethodOrProperty.Name.Contains("ToString")) result.Add(Tuple.Create(method, calledMethodOrProperty)); result.AddRange(func!(calledMethodOrProperty.ResolveMethodDef(), hashSet)); } return result; }; var h = new HashSet<MethodDef>(); return moduleFiles .Select(m => ModuleDefMD.Load(m)) .SelectMany(m => m.GetTypes()) .SelectMany(m => m.Methods) .SelectMany(m => func(m, h)); }
GetUsedMethodsAndProperties の引数は解析したい DLL のフルパス。
こんな感じで、一先ず欲しい情報は抽出出来た。
これからは、 example や readme を参考にもう少し調べてみる。
【Entity Framework】LINQ と Oracle の関数でマップされていないものを利用する(3)※固定値を関数に引数にとる場合
今回は前回より複雑なSQLを発行します。
TO_CHAR(DATE, 'D') で、指定した日付が何曜日かが取れます。
今回は上記の関数を投げて、特定の曜日のみ抽出する処理を目標とします。
流れとしては、途中まで1回目の流れです。
saboten-sakura.hatenablog.com
.net のメソッドを定義(Oracle 関数とマッピング用)
返す型は、 TO_CHAR なので string を返します。
引数は、TO_CHAR 2で二つなんですが、第1引数は任意の値で、第2引数は'D'を渡します。
なので、 .net では DateTime を引数として渡します。
public static string DayOfWeekString(this DateTime? value) => throw new NotImplementedException();
.net で定義したメソッドを Oracle 関数とマッピング
GetMethod で定義した MethodInfo を取得します。
var method = typeof(StringExtension).GetMethod(nameof(StringExtension.DayOfWeekString), new Type[] { typeof(DateTime) })!;
取得した MethodInfo と Oracle の関数を定義付けします。
modelBuilder.HasDbFunction(method).HasTranslation(translation => new SqlFunctionExpression( "TO_CHAR", new SqlExpression[] { new SqlConstantExpression( Expression.Constant("D"), new StringTypeMapping( "VARCHAR2", DbType.String)), translation.First() }, true, new bool[] { false, false }, typeof(string), new StringTypeMapping( "VARCHAR2", DbType.String) ) );
上記を定義した後に、以下のコードを実装。
var context = new SampleDbContext(); var items = (from m in context.SampleTables where m.SampleDate.DayOfWeekString() == "3" select m); foreach (var item in items) { Console.WriteLine($"{item.Id} - {item.SampleDate?.ToString("ddd")}"); }
で、出力した SQL がこちら。
SELECT "s"."ID", "s"."SAMPLEDATE", "s"."SAMPLENUMBER" FROM "SAMPLETABLE" "s" WHERE TO_CHAR("s"."SAMPLEDATE", 'D') = '3'
【Entity Framework】LINQ と Oracle の関数でマップされていないものを利用する(2)※Select句で利用する。
投稿のちょっとした続きです。
前回は Where句 に独自関数を追加しましたが、
今回は Select句 で Oracle の関数を呼んでみます。
var context = new SampleDbContext(); var items = (from m in context.SampleTables where m.SampleNumber.Sqrt() > 3 && m.SampleNumber.Sqrt() < 7 select new { test1 = m.SampleNumber, test2 = m.SampleNumber.Sqrt() }); foreach (var item in items) { // SampleNumber の表示と、 SampleNumber の平方根を表示。 Console.WriteLine($"{item.test1} - {item.test2}"); }
で、出力した SQL がこちら。
SELECT "s"."ID", "s"."SAMPLENUMBER", "SQRT"("s"."SAMPLENUMBER") "test2" FROM "SAMPLETABLE" "s" WHERE (("SQRT"("s"."SAMPLENUMBER") > 3) AND ("SQRT"("s"."SAMPLENUMBER") < 7))
【Entity Framework】LINQ と Oracle の関数でマップされていないものを利用する(1)
LINQ 正規関数にマッピングされていない Oracle 関数の使い方。
幾つか実装方法はありますが、簡単な方法から。
環境
- Visual Studio 2022
- .net 6.0
- EF Core 6.0.29
- Oracle EF Core 6.21.1.40
- NLog (ログを確認の為、出力)
実装
主要部分のみで、内容に意味は無いです。
"こうすれば動くよ!"という事を抜粋して記載します。
流れとしては以下の通りです。
.net のメソッドを定義(Oracle 関数とマッピング用)
.net のメソッドをサクッと定義
※平方根を算出する "SQRT" とマッピング予定
public static class StringExtension { public static int Sqrt(this int? value) => throw new NotImplementedException(); }
.net で定義したメソッドを Oracle 関数とマッピング
DbContext を継承し OnModelCreating を override します。
.net で定義したメソッドの MethodInfo を取得します。
HasDbFunction の引数に渡して、HasName で何の関数にマッピングするか設定。
※.net のメソッドと Oracle 関数が 1:1 なのでこれで十分
シグネチャが同じであれば、"SQRT" 以外のOracle関数でもマッピング可能。
.net のメソッドと Oracle の関数は役割が近く名前も似ていた方が把握し易くなりますね。
protected override void OnModelCreating(ModelBuilder modelBuilder) { var sqrt = typeof(StringExtension).GetMethod("Sqrt", new Type[] { typeof(int) })!; modelBuilder.HasDbFunction(sqrt).HasName("SQRT"); base.OnModelCreating(modelBuilder); }
.net のメソッドを利用し SQL を発行し結果を確認
エンティティのメンバとか全無視して期待した SQL が発行されるか確認。
// SampleNumber の値に、 2, 16, 40, 6423 が設定されたレコード有 // 平方根の結果が、3より大きく 7未満の場合 var context = new SampleDbContext(); var items = (from m in context.SampleTables where m.SampleNumber.Sqrt() > 3 && m.SampleNumber.Sqrt() < 7 select m); foreach (var item in items) { // 16 // 40 Console.WriteLine(item.SampleNumber); }
上記で発行された SQL をログ出力した結果
SELECT "s"."ID", "s"."SAMPLENUMBER" FROM "SAMPLETABLE" "s" WHERE (("SQRT"("s"."SAMPLENUMBER") > 3) AND ("SQRT"("s"."SAMPLENUMBER") < 7))
マッピングされ期待した結果が無事取得出来ました。
ユーザーが独自に定義した関数も可能だったり、
.net のメソッドと Oracle 関数を 1:n でマッピングだったり出来るので気分が向いたら追記します。
【Entity Framework】テーブルの主キーの値を渡して、レコードを取得する方法(ナビゲーションの値も含む)
以下のような実装で取得可能。
次の実装は呼び出し元
SampleTable というテーブルがあり、
主キーに相当するプロパティが SampleId になります。
SampleSubTable と SampleTable はナビゲーションプロパティが定義しています。
static void Main(string[] args) { SampleTable data = null; using (var context = new Sample01Entities()) { context.Configuration.ProxyCreationEnabled = false; data = GetSampleTableData(context, 2); } Console.WriteLine(data.SampleId); Console.WriteLine(data.SampleName); Console.WriteLine(data.SampleSubTable.Count); Console.ReadLine(); } private static SampleTable GetSampleTableData( DbContext context, int key1) { return DbContextUtility.GetData<SampleTable>( context, new SampleTable() { SampleId = key1 }); }
次の実装は呼び出し先
リファクタリングの余地はあるが、
実現したいことはできていると思います。
public static class DbContextUtility { #region EntityType private static Dictionary<Type, EntityType> entityTypes = new Dictionary<Type, EntityType>(); private static EntityType GetEntityType(DbContext context, Type type) { EntityType result; if (entityTypes.TryGetValue(type, out result)) return result; var objectContext = ((IObjectContextAdapter)context).ObjectContext; var metadata = objectContext.MetadataWorkspace; result = metadata.GetItem<EntityType>(type.FullName, DataSpace.OSpace); entityTypes.Add(type, result); return result; } #endregion public static T GetData<T>( DbContext context, T entity) where T : class { var type = typeof(T); var entityType = GetEntityType(context, type); var keyProperties = entityType.KeyMembers.Select(k => type.GetProperty(k.Name)); var values = keyProperties.Select(m => m.GetValue(entity)).ToArray(); var result = (T)context.Set<T>().Find(values); GetEntity(context, result, new HashSet<object>()); return (T)result; } private static void GetEntity( DbContext context, object entity, HashSet<object> values) { if (values.Contains(entity)) return; values.Add(entity); var type = entity.GetType(); var entityType = GetEntityType(context, type); var navproperties = entityType.NavigationProperties.Select( m => entity.GetType().GetProperty(m.Name)); foreach (var nav in navproperties) { if (nav.PropertyType.IsGenericType) { context.Entry(entity).Collection(nav.Name).Load(); var enumerable = (IEnumerable)nav.GetValue(entity); foreach(var e in enumerable) GetEntity(context, e, values); } else { context.Entry(entity).Reference(nav.Name).Load(); var value = nav.GetValue(entity); GetEntity(context, value, values); } } } }
【Entity Framework】Set やら Find やら使って、ナビゲーションプロパティも含めてDBから取得する方法
調べた結果、できそうな雰囲気。
眠いので今週中位に記事を書く予定。
EntityType から、いろいろな情報が取得できるので、
面白い事ができそうです。
DB(SQL Server や Oracle)に、
依存しない形で汎用的な部分を作りこめるのはうれしいですね。
【ASP.NET MVC】モデルのバインドに関して (2)
前回投稿した記事で、
モデルのバインドに関して調べました。
saboten-sakura.hatenablog.com
以下のように複数の Name が存在した場合。
@using (Html.BeginForm("Send", "Home", null, FormMethod.Post)) { <input type="text" name="a" /> <input type="checkbox" name="a" value="true" /> <input type="hidden" name="a" value="false" /> <input type="submit" value="送信" /> }
以下のような実装をする事でフォーム上の値が変数にバインドされます。
※引数の型が配列になっています。
public class HomeController : Controller { public ActionResult Send(bool[] a) { return this.Index(); } }
bool 型なので、 true or false のみですが、
string 型 や int 型などでもキャスト可能であれば利用可能です。