本章では、2016年6月27日に発表されたASP.NET Core1.0とMicro-ORM MapperのDapperを利用して、高パフォーマンスなクロスプラットフォームWEB開発を実践します。
はじめに
Microsoftは、以下のアナウンスの通り、オープンソースとして開発されたWEB開発プラットフォーム「ASP.NET Core」を発表しました。
blogs.msdn.microsoft.com
ASP.NET Coreは、従来のASP.NET MVCのパフォーマンスを遥かに凌駕します。
github.com
web.ageofascent.com
プレーンテキストの表示のパフォーマンス結果では、ASP.NET MVCが1秒間に約58,000リクエスト処理能力に対して、ASP.NET Coreでは1秒間に約313,000リクエスト処理能力の結果となっています。同条件で、Node.jsが約127,000リクエスト、Scalaが約176,000リクエストと、他のプラットフォームと比較しても、問題ないパフォーマンス結果です。
今までパフォーマンスが悪いとされてきたASP.NET系ですが、ASP.NET Coreの登場によってこれから様々な場面でASP.NETフレームワークが利活用されるのではと楽しみで仕方ありません。
Dapper
Dapperは、超軽量級のDAOです。
github.com
Entity Frameworkは、エンティティを主軸として考えられ、WEB開発者にとって優しく作られています。一方で、Dapperは、SQLを主軸として、パフォーマンスを重要視して作られています。Dapperでは、SQLを直接書くことが基本となりますので、パフォーマンスの調整も容易です。パフォーマンスを重点的に考えるのであれば、DAOの機構としてDapperは採用の候補の一つに挙がるかと思います。
詳しい使い方については公式や、他のサイトを参考にしてください。
開発の準備
まず最新のVisualStudioを用意します。以下サイトからダウンロード、インストールができます。
Microsoft Visual Studio ホームページ - Visual Studio
そしてプロジェクトテンプレートから「ASP.NET Core Web Application」を選択し、プロジェクトを新規に作成します。
プロジェクトの準備ができたら、NuGetパッケージで「Dapper」をプロジェクトに加えます。
そして以下のライブラリも合わせて追加します。
開発の実践
構成
全体のファイル構成と役割について確認します。
エンティティ・モデル
まずDBに、以下クエリでユーザ情報を格納するUsersテーブルを作ります。
CREATE TABLE Users( Id uniqueidentifier CONSTRAINT NN__Users__Id NOT NULL ,CONSTRAINT PK__Users__Id PRIMARY KEY CLUSTERED (Id ASC) WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ,Name nvarchar(256) CONSTRAINT NN__Users__Name NOT NULL CONSTRAINT UQ__Users__Name UNIQUE(Name) ) ;
次にDBのデータを操作するためのモデルを用意します。BaseEntityは、リポジトリクラスでDBを操作する際に利用します。
public abstract class BaseEntity { } public class Users : BaseEntity { /// <summary> /// ユーザID /// </summary> [Key] public Guid Id { get; set; } /// <summary> /// ログイン名(ユーザ名) /// </summary> [Required] public string Name { get; set; } }
そしてリポジトリクラスを用意します。
public interface IRepository<T1, T2> where T1 : BaseEntity { /// <summary> /// レコードを追加します /// </summary> /// <param name="item"></param> /// <returns></returns> void Add(T1 item); /// <summary> /// レコードを削除します /// </summary> /// <param name="id"></param> void Remove(T2 id); /// <summary> /// レコードを更新します /// </summary> /// <param name="item"></param> bool Update(T1 item); /// <summary> /// 任意のレコードを取得します /// </summary> /// <param name="id"></param> /// <returns></returns> T1 FindByID(T2 id); /// <summary> /// 有効なレコードを全取得します /// </summary> /// <returns></returns> IEnumerable<T1> FindAll(); } public class UsersRepository : IUsersRepository<Users, Guid?> { internal IDbConnection Connection { get { return new SqlConnection(connectionString); } } public UsersRepository(IConfiguration configuration) { connectionString = configuration.GetConnectionString("DefaultConnection"); } public void Add(Users item) { using (IDbConnection db = Connection) { db.Open(); using (var tran = db.BeginTransaction()) { try { db.Execute(@"INSERT INTO [Users] " + "([Id],[Name]) " + "VALUES (NEWID(), @Name)", new { Name = item.Name }, tran); tran.Commit(); } catch { tran.Rollback(); } } } } public IEnumerable<Users> FindAll() { using (var db = Connection) { db.Open(); return db.Query<Users>("SELECT * FROM [Users]"); } } public Users FindByID(Guid? id) { if (id == null) { return null; } using (IDbConnection db = Connection) { db.Open(); return db.Query<Users>("SELECT * FROM [Users] WHERE [Id] = @Id", new { Id = id.Value } ).FirstOrDefault(); } } public void Remove(Guid? id) { if (id == null) { return; } using (IDbConnection db = Connection) { db.Open(); using (var tran = db.BeginTransaction()) { try { db.Execute(@"DELETE FROM [Users] " + "WHERE [Id] = @Id", new { Id = id.Value }, tran); tran.Commit(); } catch { tran.Rollback(); throw; } } } } public bool Update(Users item) { using (IDbConnection db = Connection) { db.Open(); using (var tran = db.BeginTransaction()) { try { var count = db.Execute(@"UPDATE [Users] " + "SET " + "[Name] = @Name" + "WHERE " + "[Id] = @Id", new { Id = item.Id, Name = item.Name }, tran); tran.Commit(); return count > 0; } catch { tran.Rollback(); throw; } } } } }
DI
このままでは、リポジトリクラスを使う際に、常に設定ファイルを読み込み、オブジェクトを生成する必要があります。そこで、設定ファイル読み込みをシングルトンで生成し、リポジトリを生成します。
【Startup.cs】
public class Startup { public static IConfigurationRoot Configuration { get; private set; } public Startup(IHostingEnvironment env) { // appsettings.jsonファイルの読み込み var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) .AddEnvironmentVariables(); Configuration = builder.Build(); } public void ConfigureServices(IServiceCollection services) { // MVCを利用できるように調整 var mvc = services.AddMvc(); // Configurationをシングルトン化 services.AddSingleton<IConfiguration>(Configuration); // DAOをインスタンス化 services.AddScoped<IUsersRepository<Users, Guid?>, UsersRepository>(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { // ルーティングを設定 app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{param?}"); }); } }
なお設定ファイルは以下の通りです。
【appsettings.json】
{ "ConnectionStrings": { "DefaultConnection": "Data Source=localhost;Initial Catalog=HogeHoge;User ID=HogeUser;Password=hogehogePassword;Trusted_Connection = true;Integrated Security = true;" } }
コントローラー
コントローラーでリポジトリを使用する方法について確認します。
public class HomeController : Controller { private IUsersRepository<Users, Guid?> usersRepository; public HomeController(IUsersRepository<Users, Guid?> _usersRepository) { usersRepository = _usersRepository; } public ActionResult Index() { return View("index"); } public ActionResult FindUser(string id) { var user = usersRepository.FindById(name); // // ユーザを検索してhogehogeする // return null; } }