Ask Learn
Preview
Ask Learn is an AI assistant that can answer questions, clarify concepts, and define terms using trusted Microsoft documentation.
Please sign in to use Ask Learn.
Sign inThis browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
では引き続き、Entity Framework Core の利用方法を解説していきます。
■ 基本的な Entity Framework Core の利用方法
従来の Entity Framework と異なり、EF Core では O/R マッパーファイル(*.dbml)を使うことができません。このため、O/R マッピング(データベーステーブルのどこを構造体クラスのどこにマッピングするのか?)はすべてコードで指定する必要があります。ツールを利用して自動生成させることも(ある程度は)可能ですが、現時点(2016/07/02)では、手で書いてしまったほうがやりやすいと思います。
まずは以下 2 つのファイルを用意します。
Pubs.cs ファイルに、データを取り出すための構造体クラスを記述し、そこに O/R マッピング情報を記述していきます。今回は、全テーブルをやるとキリがないので、以下 6 つのテーブルについてだけ取り出してマッピングしてみます。
最終的なコードは以下の通りです(※ OnConfiguring() メソッド内のパス情報は適宜書き換えてください)。えらい長いコードで恐縮ですが;、順番に説明していきます。
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace Decode2016.WebApp.Models
{
public partial class PubsEntities : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlServer(
@"data source=(LocalDB)\mssqllocaldb;attachdbfilename=|DataDirectory|\pubs.mdf;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"
.Replace("|DataDirectory|", @"C:\Users\nakama\Documents\Visual Studio 2015\Projects\Decode2016.WebApp\src\Decode2016.WebApp\App_Data"));
}
public DbSet<Author> Authors { get; set; }
public DbSet<Title> Titles { get; set; }
public DbSet<Publisher> Publishers { get; set; }
public DbSet<Store> Stores { get; set; }
public DbSet<Sale> Sales { get; set; }
public DbSet<TitleAuthor> TitleAuthors { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Sale>().HasKey(s => new { s.StoreId, s.OrderNumber, s.TitleId });
modelBuilder.Entity<TitleAuthor>().HasKey(ta => new { ta.AuthorId, ta.TitleId });
modelBuilder.Entity<Sale>().HasOne(s => s.Title).WithMany(t => t.Sales).IsRequired();
modelBuilder.Entity<Sale>().HasOne(s => s.Store).WithMany(s => s.Sales).IsRequired();
modelBuilder.Entity<Publisher>().HasMany(p => p.Titles).WithOne(t => t.Publisher).IsRequired();
modelBuilder.Entity<Author>().HasMany(a => a.TitleAuthors).WithOne(ta => ta.Author).IsRequired();
modelBuilder.Entity<Title>().HasMany(t => t.TitleAuthors).WithOne(ta => ta.Title).IsRequired();
}
}
[Table("authors")]
public partial class Author
{
[Column("au_id"), Required, MaxLength(11), Key]
public string AuthorId { get; set; }
[Column("au_fname"), Required, MaxLength(20)]
public string AuthorFirstName { get; set; }
[Column("au_lname"), Required, MaxLength(40)]
public string AuthorLastName { get; set; }
[Column("phone"), Required, MaxLength(12)]
public string Phone { get; set; }
[Column("address"), MaxLength(40)]
public string Address { get; set; }
[Column("city"), MaxLength(20)]
public string City { get; set; }
[Column("state"), MaxLength(2)]
public string State { get; set; }
[Column("zip"), MaxLength(5)]
public string Zip { get; set; }
[Column("contract"), Required]
public bool Contract { get; set; }
[Column("rowversion"), Timestamp, ConcurrencyCheck]
public byte[] RowVersion { get; set; }
public ICollection<TitleAuthor> TitleAuthors { get; set; }
}
[Table("publishers")]
public partial class Publisher
{
[Column("pub_id"), Required, MaxLength(4)]
public string PublisherId { get; set; }
[Column("pub_name"), MaxLength(40)]
public string PublisherName { get; set; }
[Column("city"), MaxLength(20)]
public string City { get; set; }
[Column("state"), MaxLength(2)]
public string State { get; set; }
[Column("country"), MaxLength(30)]
public string Country { get; set; }
public ICollection<Title> Titles { get; set; }
}
[Table("titles")]
public partial class Title
{
[Column("title_id"), Required, MaxLength(6), Key]
public string TitleId { get; set; }
[Column("title"), Required, MaxLength(80)]
public string TitleName { get; set; }
[Column("type"), Required, MaxLength(12)]
public string Type { get; set; }
[Column("price")]
public decimal? Price { get; set; }
[Column("advance")]
public decimal? Advance { get; set; }
[Column("royalty")]
public int? Royalty { get; set; }
[Column("ytd_sales")]
public int? YeatToDateSales { get; set; }
[Column("notes"), MaxLength(200)]
public string Notes { get; set; }
[Column("pubdate"), Required]
public DateTime PublishedDate { get; set; }
[Column("pub_id"), MaxLength(4)]
public string PublisherId { get; set; }
public Publisher Publisher { get; set; }
public ICollection<Sale> Sales { get; set; }
public ICollection<TitleAuthor> TitleAuthors { get; set; }
}
[Table("sales")]
public partial class Sale
{
// ※ 複合キーは Data Annotation で指定できないため、Fluent API を使う
[Column("stor_id"), Required, MaxLength(4)]
public string StoreId { get; set; }
[Column("ord_num"), Required, MaxLength(20)]
public string OrderNumber { get; set; }
[Column("ord_date"), Required]
public DateTime OrderDate { get; set; }
[Column("qty"), Required]
public int Quantity { get; set; }
[Column("payterms"), Required, MaxLength(12)]
public string PayTerms { get; set; }
[Column("title_id"), Required, MaxLength(6)]
public string TitleId { get; set; }
public Store Store { get; set; }
public Title Title { get; set; }
}
[Table("stores")]
public partial class Store
{
[Column("stor_id"), Required, MaxLength(4), Key]
public string StoreId { get; set; }
[Column("stor_name"), Required, MaxLength(40)]
public string StoreName { get; set; }
[Column("stor_addr"), Required, MaxLength(40)]
public string Address { get; set; }
[Column("city"), Required, MaxLength(20)]
public string City { get; set; }
[Column("state"), Required, MaxLength(22)]
public string State { get; set; }
[Column("zip"), Required, MaxLength(5)]
public string Zip { get; set; }
public ICollection<Sale> Sales { get; set; }
}
[Table("titleauthor")]
public partial class TitleAuthor
{
[Column("au_id"), Required]
public string AuthorId { get; set; }
[Column("title_id"), Required]
public string TitleId { get; set; }
[Column("au_ord")]
public byte AuthorOrder { get; set; }
[Column("royaltyper")]
public int RoyaltyPercentage { get; set; }
public Author Author { get; set; }
public Title Title { get; set; }
}
}
■ コードの構造について
上記のソースの各クラスの役割は以下の通りです。
おおまかなコードの構造は以下の通りです。(リレーションシップの指定などに関しては少し重要なので、後述します。)
public partial class PubsEntities : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
// ここに接続文字列を書く
}
// ここにテーブル一覧を書く
public DbSet<Author> Authors { get; set; }
public DbSet<Title> Titles { get; set; }
public DbSet<Publisher> Publishers { get; set; }
public DbSet<Store> Stores { get; set; }
public DbSet<Sale> Sales { get; set; }
public DbSet<TitleAuthor> TitleAuthors { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// ここにデータアノテーションで指定できない O/R マッピング情報を書く
}
}
[Table("authors")]
public partial class Author
{
[Column("au_id"), Required, MaxLength(11), Key]
public string AuthorId { get; set; }
[Column("au_fname"), Required, MaxLength(20)]
public string AuthorFirstName { get; set; }
...
}
[Table("publishers")]
public partial class Publisher
{
[Column("pub_id"), Required, MaxLength(4)]
public string PublisherId { get; set; }
...
}
...
■ リレーションシップの O/R マッピング方法について
リレーションシップについては、以下のように実装します。
[Table("publishers")]
public partial class Publisher
{
[Column("pub_id"), Required, MaxLength(4)]
public string PublisherId { get; set; }
[Column("pub_name"), MaxLength(40)]
...
public ICollection<Title> Titles { get; set; }
}
[Table("titles")]
public partial class Title
{
[Column("title_id"), Required, MaxLength(6), Key]
public string TitleId { get; set; }
[Column("title"), Required, MaxLength(80)]
public string TitleName { get; set; }
...
[Column("pub_id"), MaxLength(4)]
public string PublisherId { get; set; }
public Publisher Publisher { get; set; }
}
modelBuilder.Entity<Publisher>().HasMany(p => p.Titles).WithOne(t => t.Publisher).IsRequired();
■ LINQ クエリの記述
O/R マッピングファイルができたら、あとは LINQ クエリを実行します。今回は簡単のため、Startup.cs の app.Run() 内を書き換えて実行してみましょう。
app.Run(async (context) =>
{
using (PubsEntities pubs = new PubsEntities())
{
var query = from a in pubs.Authors where a.State == "CA" select a;
await context.Response.WriteAsync(query.Count().ToString());
}
});
正しく動作すれば、15 件という結果が帰ってくるはずです。
なお、EF で必要となる LINQ クエリの記述方法についてはここでは説明しませんが、LINQ クエリは EF を扱う上での必須技術であるため、まだ知らないという方は必ず学習してください。拙著「LINQ テクノロジ入門」は EF の前進となる LINQ to SQL という技術を使って書かれていますが、LINQ クエリの書き方そのものは基本的に変わりません。こちらの本をざっと流し読みしていただければ、LINQ の基本的な考え方などは学習できると思います。
■ 従来の EF からの大きな変更点について
(ここはちょっと難しいのでわかる人だけ読んでください) Entity Framework Core では様々な変更が入っていますが、実用側面から見た場合、以下の 2 つは非常に大きな変更点ですので、ここで解説しておきます。
Lazy Loading の廃止
従来の EF では、リレーションシップの先にあるデータを、クエリ実行後に後から手繰れるというトンデモ仕様が含まれていました。現場側の人間からすると、誰だこの学術的機能を入れた人は;、と全力でツッコミたかったわけですが、EF Core ではこの仕様が廃止されました。このため、以下のクエリは実行時に例外が発生します。
もちろん、場合によっては「クエリ実行時に、リレーションシップの先までデータを取得しておいてほしい」ということもあるはずです。この場合には、クエリ発行前に、明示的に Include, ThenInclude 命令で取り込む対象を指定してください。
非同期処理
従来の EF では、ToList() や FirstOrDefault() などによるクエリ実行は同期的にしか実行できませんでしたが、こうしたクエリ実行命令に、非同期処理版が追加されました。このため、以下のようなクエリは await/async 構文を利用して、以下のように記述できるようになりました。
var query = from a in pubs.Authors where a.State == "CA" select a;
var result = query.ToList();
↓
var result = await query.ToListAsync();
EF のこのような機能拡張に合わせて、ASP.NET MVC のアクションメソッドや Web API でも、async 構文を使った定義ができるようになりました。
[HttpGet]
public ActionResult ShowTitlesByPublishers()
{
using (PubsEntities pubs = new PubsEntities())
{
var query = pubs.Publishers....;
ViewData["TitlesByPublisher"] = await query.ToList();
}
return View();
}
↓
[HttpGet]
public async Task<ActionResult> ShowTitlesByPublishers()
{
using (PubsEntities pubs = new PubsEntities())
{
var query = pubs.Publishers....;
ViewData["TitlesByPublisher"] = await query.ToListAsync();
}
return View();
}
この新機能については様々なところでよく紹介されていますが、クライアント側(UI 処理)における async/await とは意味合いがずいぶん違うので注意してください。ざっくり説明すると、以下の通りです。
サーバサイドではもともとマルチスレッドで処理が行われているため、「サーバが固まる」という現象が起こるわけではないですし、仮に async/await を明示的に行わなかったとしても、OS のマルチスレッド制御機能により、自動的に CPU リソースが他のスレッドに回されますので、すぐさま問題が起こるというわけではありません。ただ、ASP.NET ランタイムが Windows OS 以外でも動作するという話になってくると、OS によってはこのマルチスレッド制御の機能が貧弱なケースも考えられ、そのような場合には「明示的なリソース解放」を行わないと性能が出ない、というケースが出てくるかもしれません。正直、今どきの OS であればそうそう問題が起こるケースはないだろうとは思いますが、とはいえお作法としては、async/await 処理をきちんと書いた方が、環境依存のトラブルが出にくくなるという意味では安心です。
いずれにしても、サーバ側の async/await は、クライアント側の async/await とは利用目的が違う、という点は知っておくとよいでしょう。
■ 接続文字列の管理方法について
先の例では、接続文字列をハードコーディングしましたが、実際のアプリではいくつか課題があります。解決策をいくつかここで示しておきます。
public class Startup
{
public static string App_Data { get; set; }
public Startup(IHostingEnvironment env)
{
App_Data = Path.Combine(env.ContentRootPath, "App_Data");
}
...
public partial class PubsEntities : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlServer(
@"data source=(LocalDB)\mssqllocaldb;attachdbfilename=|DataDirectory|\pubs.mdf;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"
.Replace("|DataDirectory|", Startup.App_Data));
}
...
#if DEBUG
options.UseSqlServer(
@"data source=(LocalDB)\mssqllocaldb;attachdbfilename=|DataDirectory|\pubs.mdf;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"
.Replace("|DataDirectory|", Startup.App_Data));
#else
options.UseSqlServer(
@"Server=tcp:xxxxxxxx.database.windows.net,1433;Database=pubs;User ID=xxxxxxxx@xxxxxxxx;Password=xxxxxxxx;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;");
#endif
なお、ASP.NET Core では構成設定値を web.config ファイルに書きません。この点は従来の ASP.NET Web Forms から大きく変わっている点ですのでご注意ください。背景は以下の通りです。
■ その他の制約事項・注意事項について
その他、現在の Entity Framework Core 1.0 に関する注意点は以下の通りです。
以上が EF Core 1.0 の基本的な使い方になります。その他、より詳しい情報については、de:code 2016 DEV-003 セッション 「新しく生まれ変わったデータアクセステクノロジ~Entity Framework Core 1.0 の全貌~」などを見ていただくとよいと思います。
Ask Learn is an AI assistant that can answer questions, clarify concepts, and define terms using trusted Microsoft documentation.
Please sign in to use Ask Learn.
Sign in