GroupByのグルーピング対象を動的に変更する

.NET LinqでGroupByを使用して、クエリ内容をグルーピングする際に、場合によって、グルーピング対象を動的に変更したい場合がある。

これを実装するのに一番簡単な方法を見つけたのでメモ。

Enumerable.GroupBy拡張メソッドのオーバーロードの1つに、以下の物がある。

GroupBy<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>, IEqualityComparer<TKey>)

このオーバーロードのパラメータ、IEqualityComparer<TKey>をインプリメントしたクラスを作ることで、グルーピング単位を外部から指定できるようになる。

例えば、以下のようなクラスのリストを「年」+「月」でグルーピングしたり、「年」+「月」+「セールスマン」でグルーピングしたりしたい場合、

public partial class Sales
{
    public int Year { get; set; }            // 年
    public int Month { get; set; }           // 月
    public string SalesMan { get; set; }     // セールスマン
    public string ProductName { get; set; }  // 商品名
    public long Qty { get; set; }            // 販売数
}

IEqualityComparer<Sales>をインプリメントして、以下のようなクラスを作る。

class SalesEqaulityComparer : IEqualityComparer<Sales> {
    public bool useYear { get; set; }
    public bool useMonth { get; set; }
    public bool useSalesMan { get; set; }
    public bool useProductName { get; set; }
    // 比較方法(IEqualityComparer<T>)
    public bool Equals(Sales x, Sales y) {
        if (useYear && !x.Year.Equals(y.Year)) return false;
        if (useMonth && !x.Month.Equals(y.Month)) return false;
        if (useSalesMan && !x.SalesMan.Equals(y.SalesMan)) return false;
        if (useProductName && !x.ProductName.Equals(y.ProducName)) return false;
        return true;
    }
    // HashCodeの取得(IEqualityComparer<T>)
    public int GetHashCode(Sales s) {
        int hash = 0;
        if (useYear) hash ^= s.Year.GetHashCode();
        if (useMonth) hash ^= s.Month.GetHashCode();
        if (useSalesMan) hash ^= s.SalesMan.GetHashCode();
        if (useProductName) hash ^= s.ProductName.GetHashCode();
        return hash;
    }
}

実際にグルーピングするときは、このクラスインスタンスを作成し、グルーピングしたいuseXXXにtrueを設定することにより、指定した項目でグルーピングができる。

例えば、「年」,「月」でグルーピングしたい場合は、useYearとuseMonthにtrueを設定し、その他はfalseに設定。「年」,「月」,「セールスマン」でグルーピングしたい場合は、useYear,useMonth,useSalesManにtrueを設定して、その他はfalseを設定して、以下のようなクエリを発行する。

List<Sales> SalesList = new SalesList();
・・・
var salescomparer = new SalesEqaulityComparer();
// グルーピングしたい項目の指定
salescompare.useYear = true;
salescompare.useMonth = true;
・・・
var g = SalesList.GroupBy(s=>s,salescomparer);
foreach(var itm in g) {
	Console.WriteLine($"{itm.First().Year},{itm.First().Month},・・・{itm.Sum(v=>v.Qty)}}");
}

意外と使えます。

カテゴリー: .NET, C#, LINQ, 技術系 | コメントする

またまた、deprecateパッケージ発見してしまった・・・

私が知らなかっただけなのかもしれないが、Microsoft.EntityFrameworkCore.SqlServer.DesignやMicrosoft.EntityFrameworkCore.Sqlite.Design等のパッケージがdeprecate対象となっていた。

今まで、SQL ServerやSqlite等をEF Coreを使用してscaffoldする場合やmigrationするのに、無条件でこれらのパッケージ追加していたんだが、いつの間にやら、このパッケージ無しでもscaffoldやmigrationできるようになっていたみたいだね・・・

Pomelo.EntityFrameworkCore.MySql.Designとかはnuget.orgで見る限りではdeprecate対象にはなっていないようだが、更新日が2017年と古い。試してはいないけど、DBプロバイダ.Designパッケージは必要無くなったのかな・・・

カテゴリー: .NET, Entity Framework, 技術系 | 2件のコメント

.NET7 PreviewでのSQL Serverへの接続

.NET7 Preview版で、SQL Serverに接続する場合、デフォルトで暗号化がTrueに設定されるようになったようで、今までの接続文字列では以下のようなエラーが出て、接続出来ない。

"Data Source=<server>;Initial Catalog=<db>;User ID=<ID>;Password=<PWD>"

A connection was successfully established with the server, but then an error occurred during the pre-login handshake. (provider: TCP Provider, error: 35 – An internal exception was caught)

これを回避するには、接続文字列に、”encrypt=false“を入れれば良い。

"Data Source=<server>;Initial Catalog=<db>;User ID=<ID>;Password=<PWD>;encrypt=false"

この情報を探すのに結構手間取ってしまったので、メモ。

SQL Serverからdotnet ef dbcontext scaffoldしたかっただけなのだけど・・・

カテゴリー: .NET, Entity Framework, 技術系 | コメントする

Microsoft.Graph.AuthがDeprecateになっていた

しばらく、Microsoft Graph APIを触っていなかったのだが、Graph APIライブラリを使用して作成したアプリケーションのターゲット.NETフレームワークバージョンがサポートされなくなるため、再ビルドをしようとして、使用しているパッケージのバージョンを上げようと、パッケージをnuget.orgを見たら、Microsoft.Graph.AuthがDeprecate候補(元々Preview版だったけど)になっていたので、MSのサイトで調べたら、ここにAzure.Identityライブラリを使用しろやと書かれていた。

で、修正してみたのが以下のコード。(ユーザー名/パスワード プロバイダーの例)

/* Microsoft.Graph.Authは削除対象となったので、Azure.Identityに変更
この部分は不必要となった(以下見づらくて済まソ)
IPublicClientApplication app = PublicClientApplicationBuilder
    .Create(ServiceId)
    .WithAuthority(TenantId)
    .Build();
*/

// 必要とする権限(Microsoft.Graph.Auth⇒Azure.Identity) 指定フォーマットが変わった
//string [] scopes = new string[] { "https://graph.microsoft.com/User.Read", "https://graph.microsoft.com/Group.Read.All" };
string [] scopes = new string[] { "User.Read", "Group.Read.All" };

/* Graph API Authorization Providerの作成(Microsoft.Graph.Auth⇒Azure.Identity)
Providerは不要となり、GraphServiceClientにはCredentialとScopeを指定する。
var provider = new UsernamePasswordProvider(app,scopes);
// Graph API Clientの作成
var graphClient = new GraphServiceClient(provider);
*/

// Credentialの作成(Microsoft.Graph.Auth⇒Azure.Identity)
var options = new TokenCredentialOptions
{
    AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var cred = new UsernamePasswordCredential(username,pwd,TenantId,ServiceId,options); // テナントIDの指定形式も変わっている(https://・・・が要らない)

// Graph API Clientの作成(Microsoft.Graph.Auth⇒Azure.Identity)
var graphClient = new GraphServiceClient(cred,scopes);

var me = await graphClient.Me
    .Request()
    //.WithUsernamePassword(username,secPwd) Microsoft.Graph.Auth⇒Azure.Identity
    .GetAsync();

チョット見ない間にMSのページまで変更されていたので、焦りました・・・

ユーザー名/パスワード プロバイダーに関しては、SecureStringを使用しなくても良くなった点は良いね。

カテゴリー: .NET, Microsoft Graph, 技術系 | コメントする

Blazor Hybrid

.NET6からBlazor Hybridというものが追加された。

これは、MAUIやWPF,Windows FormsでRazorコンポーネントを動かそうと言う物だが、実際には、Razorコンポーネントが動作する、WebViewコントロールを画面に配置して、画面の一部として、Razorコンポーネントを実行するものである。

今のところ、このコントロールとホスト側の連携機能はほぼ無い(動作させるRazorコンポーネントをホスト側から指定することは可能。)

下記の画面キャプチャはWindows Forms上での例。

まぁ、まだ出たばかりなので、ホスト側との連携機能などは今後に期待したいところだ。

カテゴリー: .NET, Blazor, 技術系 | コメントする

asp.net coreのDIにDbContextを追加する

asp.net coreはDIが使えるが、DbContextをDIさせるのに少々手間取ったので備忘録としてメモ。

何に戸惑ったのかというと、DbContextインスタンスを作成するときに、appsettings.jsonから接続文字列を取り出して、それを接続に使う方法。

ドキュメントをちゃんと読んでいれば直ぐ分かるのだけどねぇ・・・

で、結局以下のような感じで接続文字列を取得して、インスタンス作成が可能。(UseSqlServerの部分は実際のDBMS用ドライバに合わせてねっ)

using Microsoft.EntityFrameworkCore;
using XXXContextNamespace;
・・・
var builder = WebApplication.CreateBuilder(args);
・・・
builder.Services.AddDbContext<XXXContext>(opt=>
    opt.UseSqlServer(builder.Configuration.GetConnectionString("XXXConnection")));

こうしておくと、例えばwebappならば、PageModelのコンストラクタから、このDbContextのインスタンスを取得することができる。

using XXXContextNamespace;
・・・
private readonly ILogger<IndexModel> _logger;
private readonly XXXContext _ctx;
public class IndexModel : PageModel {
	public IndexModel(ILogger<IndexModel> logger, XXXContext ctx) {
		_logger = logger;
		_ctx = ctx;
		・・・
	}
}

Blazor Serverなら、こんな感じ

@page "/"
@using XXXContextNamespace
@inject XXXContext XXXCtx
・・・
XXX:<select>
@if (XXXMasters != null) {
	foreach(var itm in XXXMasters) {
		<option value="@itm.Key">@itm.Name</option>
	}
</select>
・・・
@code {
	protected List<XXXMaster> XXXMasters = null!;
	・・・
	protected async Task OnInitializeAsync() {
		var q = XXXCtx.XXXMaster・・・;
		XXXMasters = await q.ToListAsync();
	}
}
カテゴリー: .NET, asp.net core, Entity Framework, Web, 技術系 | コメントする

JavaScript promiseとasync await

最近、殆どJavaScriptなんぞ使っていなかったので、知ったときはちょっと驚きw

まぁ、殆どの人は知っていると思うが、JavaScriptでpromiseを使ったライブラリ等があった場合、以前だと以下のような感じで処理を書いていたと思うが、処理内でまた、promiseを使ったライブラリを呼び出す場合はネストが深くなって見づらいし、結構面倒だよね。

// promiseを使用した一般的な例
function xxx(prm) {
	// hoge関数はpromiseを返す
	// moge関数もpromiseを返す
	hoge(prm)
		.then( e => {
			// hoge正常完了時時の処理
			moge(e).then( e => {
				・・・// moge正常完了時の処理
			}).catch(
				err => {
				・・・	// moge異常完了時の処理
			});
		}).catch( 
			err => {
			・・・	// hoge異常完了時の処理
		});
}

昨今のJavaScriptではこれをC#と同様にasync/awaitを使った形で記述することが可能となった。以下のような感じですな。

// async/await版
async function xxx(prm) {
	try {
		var e = await hoge(prm);
		var x = await moge(e);
		・・・
	} catch(err) {	// どこかの処理が失敗すると、例外がスローされる
		// 例外処理
	}
}

C#まんまじゃん・・・

これなら、node.jsとかで開発しても少しは楽かな。などと思っている今日この頃・・・

カテゴリー: javascript, Web, 技術系 | コメントする

2022/06 Windows Update不具合について

2022/06のWindows UpdateをWindows Server 2012 R2に適用したところ、以下のような不具合が出た。Web検索してみたのだけど、同じ現象は引っかからなかった。同じような運用をしている絶対数が少ないのだろうか。最初はサーバそのものの問題だと思ったのだが、2台に適用して、両方とも同じ現象が起きたので、Windows Updateのせいだろう。

環境
Windows Server 2012 R2 + RAS(VPN+ルーティング構成)

現象
RASを起動しても、VPNが繋がらない。また、RASを起動すると、そのサーバーからLAN上のリソースが見えなくなる。また、他のマシンからのRDP接続も不可となる。

クライアントの方はWiFiスポット云々とか言う現象が起きているようだが、ルーティング関連の不具合だろうか・・・

※Windows Updateをアンインストールすると、キチンと動作する。ただ、時間が無くて、3つインストールされている内のどれが問題なのか迄は把握できていない・・・
実際に稼働しているシステムなので、あまり時間がとれないので・・・

MSもちゃんと試験してからリリースして欲しいものだ。

カテゴリー: Windows Update | 1件のコメント

.NET Reflection

.NETにしろJavaにしろ、リフレクションを使うのは、あまり実行時の効率が良くないのだろうが、呼出回数が多くなければ、それほど気にならない。

私の場合、どんなときに使うかというと、クラスを特定しない、コンバーターライブラリなどに使用している。

例えば、CSVと定義ファイルから、クラスインスタンスにCSVの項目を動的にマッピングさせるライブラリなど。

以下のような感じで使用している。

<!--
	マッピング定義
	Column -- CSVカラム
		Index : カラム位置
		MapProperty : 設定するプロパティ名
		FuncName : 変換メソッド
-->
<Mapping OutputColumns="5">
  <Column Index="0" MapProperty="EmpNo" FuncName="ConvertEmpNo"/>
  <Column Index="1" MapProperty="ExecDate" />
	・・・
</Mapping>
/// <summary>
/// クラスインスタンスへの値セット(Reflection版)
/// </summary>
/// <param name="instance">インスタンス</param>
/// <param name="prop">プロパティ情報</param>
/// <param name="value">値</param>
/// <typeparam name="U">インスタンスの型</typeparam>
public static void SetProp<U>(U instance, string prop, object value) {
    var p = typeof(U).GetProperty(prop);
    if (p == null) {
        throw new ArgumentException($"クラス「{typeof(U).ToString()}」にはプロパティ{prop}が存在しません");
    }
    p!.SetValue(instance,value);
}

ちなみに、FuncNameのメソッドも同様にReflectionを使用して呼び出す。

他にExpressionを使用する方法もあるのだが、式をコンパイルした物をどこかに保存しない場合はReflectionの方がまだ効率が良いかなと・・・

まぁ、データ数がそれほど多くなければ実行速度はあまり気にならない程度。作りやすさや作成時間と実行時間とのトレードオフといったところかな。

カテゴリー: .NET, C#, 技術系 | コメントする

ASP.NET Core SignalR-厳密に型指定されたハブ

以前、SignalRのHUBについてチョット書いたことがあるけれど、見落としていたことがあったので、今更ながら情報を追加しておく(汗)

SignalRのHUBだが、クライアントへメッセージを渡すメソッドに関して、Interfaceを定義しておけば、nameofを使用して文字列ベタ打ちじゃなくても済むというようなことを書いたが、実はもっと、簡単な方法があった。(HUBに関してだけだけど・・・)

その方法とは、作成するHubのベースクラスをHubではなく、Hub<T>にすること。このTはインターフェイスを表す。例えば、以下のような感じ。

/// <summary>
/// Hub Methods(Called by Client)
/// </summary>
public interface IHubMethods {
    Task LogOn(string UserName);
    Task SendMessage(string UserName, string Message);
    Task LogOff();
}
/// <summary>
/// Client Methods(Called by Hub)
/// </summary>
public interface IClientMethods {
    Task ReceiveMessage(string UserName, string Message);
    Task LogOnMessage(string UserName);
    Task LogOffMessage(string UserName);
}
publc class MyHub : Hub<IClientMethods>, IHubMethods {
	・・・
	public async Task SendMessage(string UserName, string Message) {
		// 普通なら、
		// await Clients.All.SendAsync(nameof(IClientMethods.ReceieveMessage),UserName,Message);
		// と書くのが普通だが、下記で上記と同様となる。(I/Fのデフォルト実装でもしているのかな?
		// チョット気持ち悪いんだが・・・)
		await Clients.All.ReceieveMessage(UserName,Message);
	}
	・・・
}

I/Fのデフォルト実装でも勝手にしているのか、I/Fに定義されたシグネチャで呼び出すとSendAsyncが呼ばれるようだ。私的には裏で何かしらやられるのはあまり好きでは無いが、確かにこれなら、メソッド名とパラメータを間違えることはないね。

SignalRを使うために結構MSのサイト見ていたつもりなのだけど、見落としていました・・・

※ちなみに、この方法を使うと、Clients.XXX.SendAsyncは使用不可となるので注意。

カテゴリー: .NET, asp.net core, C#, SignalR, 技術系 | コメントする