質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
ASP.NET

ASP.NETは動的なWebサイトやWebアプリケーション、そしてWebサービスを構築出来るようにする為、Microsoftによって開発されたウェブアプリケーション開発フレームワークです。

Q&A

解決済

1回答

3409閲覧

ASP.NET MVCのモデルのプロパティに、クラスを使用したい

haru853

総合スコア38

ASP.NET

ASP.NETは動的なWebサイトやWebアプリケーション、そしてWebサービスを構築出来るようにする為、Microsoftによって開発されたウェブアプリケーション開発フレームワークです。

0グッド

0クリップ

投稿2018/01/29 02:37

編集2018/01/29 07:58

フレームワーク: MVC Core v2.1.4
以下のようなコードで、クラス(Person)を使用したモデル(Member)を作成したいと思ったのですが

c#

1public class Person 2{ 3 public int Id { get; set; } 4 public string Name { get; set; } 5 public int Age { get; set; } 6} 7 8public class Member 9{ 10 public int Id { get; set; } 11 public Person Person { get; set; } 12 public Person Child { get; set; } 13}

スキャフォールディングをおこなうと、データベースにはMemberテーブルとPersonテーブルが作成されます。
以下の表のように、Memberテーブルのみ作成したい(Personテーブルは作成したくない)のですが、どのようにすればよろしいでしょうか?

IdPerson.NamePerson.AgeChild.NameChild.Age
1一郎30太郎11
2次郎40二郎12

追記

PersonのIdは不要です。Idをなしにするとスキャフォールディングがエラーになってしまうため追加しています。
スキャフォールディングは、ModelをベースにController / View のソースを自動生成するという意味で使っています。
(ソリューションエクスプローラー → 追加 → コントローラー → Entity Frameworkを使用したビューがあるMVCコントローラー からおこなっています)

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

x_x

2018/01/29 03:01

PersonのIdはどこで保持するのですか?
haru853

2018/01/29 03:05

すみません。説明が不足していました。PersonのIdは不要です。Idをなしにするとスキャフォールディングがエラーになってしまうため追加しました。
退会済みユーザー

退会済みユーザー

2018/01/29 07:31

MVC4 のタグが付いてますが Core ではなくて .NET Framework ベースということでいいですか?(以前のスレッドでは MVC Core の話だったので聞いてます)
haru853

2018/01/29 07:38

出来ればCoreで使用可能な方法を知りたいです。質問用のタグに「MVC Core」がなかった為、カテゴリが一番近いと思われる「MVC4」タグを使用しました。まぎらわしくてすみません。記載するべきでした。
退会済みユーザー

退会済みユーザー

2018/01/29 07:39

スキャフォールディングというのはどういう意味で使っていますか? EF Code First の機能を使って Model に定義されたクラスをベースに DB サーバーにテーブルを作ること? 自分は Model などをベースに Controller / View のソースを自動生成することだと理解してますが・・・ こんな感じです → http://surferonwww.info/BlogEngine/post/2017/07/23/creating-controller-and-view-in-mvc-using-scaffolding-function.aspx
haru853

2018/01/29 07:45

ModelをベースにController / View のソースを自動生成するという意味で使っています。ソリューションエクスプローラー → 追加 → コントローラー → Entity Frameworkを使用したビューがあるMVCコントローラー からおこなっています。
退会済みユーザー

退会済みユーザー

2018/01/29 07:48

> 出来ればCoreで使用可能な方法を知りたいです。 ← 「出来れば」というのでは困ります。.NET Framework ベースで回答したのに、やっぱり Core ベースでないとダメと言われては(多分そうなると思います)、回答者の労力と時間が無駄になりますので。Core ベースの質問なら、タグは ASP.NET に変更して、質問文の一番最初に MVC Core であることとそのバージョンを明記してください。
haru853

2018/01/29 08:10

失礼しました。アプリケーション全体の作成がCoreでは不可能だった場合は、.NET Frameworkで作成しようと考えている為、「出来れば」と記載してしまっていました。まぎらわしいのでCoreに限定させていただきました。
guest

回答1

0

ベストアンサー

EF Code First で Model の Member, Person, Child クラスをベースに DB にテーブルを生成してそれらを使うのですよね?

Model の Member クラスにナビゲーションプロパティを定義して(今は定義されてないように見えます)、それをたどって Person.Name, Person.Age, Child.Name, Child.Age を取得することを検討されてはいかがでしょう?

【2018/1/31 12:00 追記】

下の私の 2018/01/31 10:50 のコメントで「コメント欄には書ききれないので回答欄に追記します」と書きましたが、それを以下に書きます。

質問者さんの DB の詳細は回答者にとっては不明ですので、誰でも入手できる Microsoft のサンプルデータベース Northwind をベースに話をしたいと思います。

Customers, Employees, Suppliers テーブルにある Address, City, Region, PostalCode, Country は各テーブル固有の情報(住所が個人個人で違うのは当たり前)なので、これらの情報を抜き出して別テーブルにまとめて正規化(?)するというようなことは無意味というよりやるべきではない・・・という認識は質問者さんも自分も一致していると理解しています。

で、質問者さんのやりたいことは、DB に別テーブルを作るという訳ではなく、

Address・City・Region・PostalCode・Country をクラスなどにまとめてスマートに記述する

ということですが、Customers, Employees, Suppliers テーブルのレベルで考えて、それらからデータを取得する場合は、そういう方法はないと思います。

全テーブルの Address, City, Region, PostalCode, Country データを List<T> 型のオブジェクトなどにまとめることは可能ですが、無理にやってもデメリットばかりで何の意味もない(少なくともスマートに利用できる方法はない)と思います。

意味があるのは Orders テーブルなどの一つ上のレベルのテーブルです。Orders テーブルは正規化されていて、EDM を作れば Customers, Employees, Suppliers クラスが自動生成されて利用できます。

VS2015 で .NET Framework ベースて作った EDM の Orders クラスは以下のようになります。Customers, Employees というナビゲーションプロパティが定義されているところに注目してください。上の私レスで提案したのはこれを使うことです。

namespace Mvc5App { using System; using System.Collections.Generic; public partial class Orders { [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] public Orders() { this.Order_Details = new HashSet<Order_Details>(); } public int OrderID { get; set; } public string CustomerID { get; set; } public Nullable<int> EmployeeID { get; set; } public Nullable<System.DateTime> OrderDate { get; set; } public Nullable<System.DateTime> RequiredDate { get; set; } public Nullable<System.DateTime> ShippedDate { get; set; } public Nullable<int> ShipVia { get; set; } public Nullable<decimal> Freight { get; set; } public string ShipName { get; set; } public string ShipAddress { get; set; } public string ShipCity { get; set; } public string ShipRegion { get; set; } public string ShipPostalCode { get; set; } public string ShipCountry { get; set; } public virtual Customers Customers { get; set; } public virtual Employees Employees { get; set; } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public virtual ICollection<Order_Details> Order_Details { get; set; } public virtual Shippers Shippers { get; set; } } }

例えば、質問者さんの質問の例にならって、Orders の OrderID, Customers の City と Country, Employees の City と Country の一覧を取得したい場合は、Orders クラスには、Customers と Employees というナビゲーションプロパティが定義されているので、以下のようにできます。個人的には、これが Entity Framework と Ling to Entity の機能を最大限活用した最もスマートな例だと思います。

(注)Include() というメソッドは指定したプロパティを先読みするためのものです。無くても結果は同じですが、無いと遅延評価されるのでクエリを 2 度、3 度投げることになります。質問者さんのアップされたコードではナビゲーションプロパティに virtual が付与されないようなので、話が違うかも。(未検証・未確認です)

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Data.Entity; namespace ConsoleAppJoinByLinq { class Program { static void Main(string[] args) { NORTHWINDEntities db = new NORTHWINDEntities(); var orders2 = db.Orders.Include(c => c.Customers).Include(e => e.Employees); foreach (var item in orders2) { Console.WriteLine("OrderID: {0}, CustomerCity: {1}, CustomerCountry: {2}, EmployeeCity: {3}, EmployeeCountry: {4}", item.OrderID, item.Customers.City, item.Customers.Country, item.Employees.City, item.Employees.Country); } } } }

ナビゲーションプロパティが定義されてない場合は以下のように join して同じ結果が得られます。

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Data.Entity; namespace ConsoleAppJoinByLinq { public class OrderCustomerEmployee { public int OrderID { get; set; } public string CustomerCity { get; set; } public string CustomerDountry { get; set; } public string EmployeeCity { get; set; } public string EnployeeCountry { get; set; } } class Program { static void Main(string[] args) { NORTHWINDEntities db = new NORTHWINDEntities(); var orders = from o in db.Orders join c in db.Customers on o.CustomerID equals c.CustomerID join e in db.Employees on o.EmployeeID equals e.EmployeeID select new OrderCustomerEmployee { OrderID = o.OrderID, CustomerCity = c.City, CustomerDountry = c.Country, EmployeeCity = e.City, EnployeeCountry = e.Country }; foreach (var item in orders) { Console.WriteLine("OrderID: {0}, CompanyName: {1}, ContactName: {2}, FirstName: {3}, LastName: {4}", item.OrderID, item.CustomerCity, item.CustomerDountry, item.EmployeeCity, item.EnployeeCountry); } } } }

ひょっとして、上のコードの OrderCustomerEmployee クラスが質問者さんの言うまとめるためのクラスなのでしょうか?

投稿2018/01/29 08:55

編集2018/01/31 03:04
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

haru853

2018/01/30 00:59

回答ありがとうございます。 せっかく回答いただいたのですが、簡単に参照をしたいという意図ではありません。 以下のコードにした場合のように、データを一つのテーブルにまとめて格納したいのです。 public class Member { public int Id { get; set; } public string PersonName { get; set; } public int PersonAge { get; set; } public string ChildName { get; set; } public int ChildAge { get; set; } } ただし、Member以外のモデルにも使用する為、NameとAgeをクラスでまとめようと考えました。 ところが、MemberテーブルとPersonテーブルが別々に生成されてしまったので、他に方法があるのではないかと思い質問させてもらいました。
退会済みユーザー

退会済みユーザー

2018/01/30 01:43

データベースの正規化の知識をお持ちですか? 知識がないもしくは曖昧ということでしたら一度以下の記事などに目を通すことを進めします。 データベースの正規化の基礎 https://support.microsoft.com/ja-jp/help/283878/description-of-the-database-normalization-basics > 以下のコードにした場合のように、データを一つのテーブルにまとめて格納したいのです。 データベースの正規化を考えて Member テーブルと Person テーブルが生成されるようにしたのではないのですか? 正規化とか考えずに、Member テーブルだけ作りたいということであれば、最初から、Model には、 public class Member { public int Id { get; set; } public string PersonName { get; set; } public int PersonAge { get; set; } public string ChildName { get; set; } public int ChildAge { get; set; } } だけを定義すればよいのでは? 何を考えておられるのか分かりません。
haru853

2018/01/30 02:42

回答ありがとうございます。 例えば以下のモデルが必要として、Shareholder・Manager・Memberの人の重複はありえない場合は正規化の必要がないと考えていたのですが、それがおかしいでしょうか。 Personテーブルのレコード数だけが肥大化するより、各テーブルに分散されたほうが速度面などでもメリットがあるのではないか?と考えていたのですが。 public class Shareholder { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } } public class Manager { public int Id { get; set; } public string Address { get; set; } public string Name { get; set; } public int Age { get; set; } } public class Member { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } public string ChildName { get; set; } public int ChildAge { get; set; } }
退会済みユーザー

退会済みユーザー

2018/01/30 02:54 編集

> 例えば以下のモデルが必要として、Shareholder・Manager・Memberの人の重複はありえない場合 意味が分かりません。最初の質問で出てきた Person とか Child はどこに行ってしまったのですか? 話が変わってきてませんか? 最初の質問から話がつながるように説明願います。
haru853

2018/01/30 04:43

SurferOnWwwさんが > 正規化とか考えずに、Member テーブルだけ作りたいということであれば、最初から、Model には、 > ・・・ > だけを定義すればよいのでは? 何を考えておられるのか分かりません。 とおっしゃられたので、私の記載した「Member以外のモデルにも使用する為、NameとAgeをクラスでまとめようと考えました」の意図が伝わっていないと思い、意図を伝える為にShareholder・Manager・Memberでの例を記載しました。 以下のモデルが必要として public class Shareholder { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } } public class Manager { public int Id { get; set; } public string Address { get; set; } public string Name { get; set; } public int Age { get; set; } } public class Member { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } public string ChildName { get; set; } public int ChildAge { get; set; } } NameとAgeが重複している為、以下のようなコードにすれば良いのではないかと考えたわけです。 (継承は使用しません) public class Shareholder { public int Id { get; set; } public Person Person { get; set; } } public class Manager { public int Id { get; set; } public string Address { get; set; } public Person Person { get; set; } } public class Member { public int Id { get; set; } public Person Person { get; set; } public Person Child { get; set; } } public class Person { public string Name { get; set; } public int Age { get; set; } } ところが、考えに反してPersonテーブルが作成されたので最初の質問をさせていただいたという事です。 つまり「正規化とか考えずに、Memberテーブルを作りたい」のですが、Memberテーブルだけではなく他のテーブルにもName・Ageを含むテーブルがある為、Personクラス等を使ってスマートに記述する方法がないかという事を知りたかったという事です。
退会済みユーザー

退会済みユーザー

2018/01/30 06:18 編集

> つまり「正規化とか考えずに、Memberテーブルを作りたい」のですが、 いえ、DB の整合性・保守性なども考えて、逆に、最初からきちんと正規化を考えるべきでしょう。(趣味でやっていて、正規化などどうでもいいので、とにかくそうしたいというなら話は別ですが) Microsoft のサンプルデータベースの Northwind とか AdventureWorks などを参考にしてみてください。 例えば、Northwind のように、人に関するデータは顧客、従業員などに分けて別々のテーブルで管理し、そのデータを参照する注文テーブルのフィールドには、顧客テーブル・従業員テーブルの ID (主キー) のみを格納する。さらに外部キー制約をかけて不正な ID の値が入らないようにするといったことを考えてください。 EF Code First で DB を生成するなら、上記のことを考えたうえで Model のクラス定義をするという話になるはずです。
退会済みユーザー

退会済みユーザー

2018/01/30 09:01 編集

【追伸】 Shareholder・Manager・Memberで共通に使うデータを抜き出して Person クラスにまとめたということらしいですが、とするとそのデータが DB にないと何ともならない(DB にデータを保持する Person テーブルを作るのは必須)ではないですか? 「正規化とか考えずに」ということですが、共通に使うデータを抜き出して Person クラスにまとめたということが正規化のステップの一つだと思うのですが、質問者さんの認識は違いますか?
haru853

2018/01/30 09:25

おこないたいことは、Shareholder・Manager・Memberテーブル自体でName・Ageの値を保持したいという事です。 (Shareholder・Manager・MemberテーブルでPersonのID(主キー)を格納するのではなく) 質問に記載した表のようなテーブルを作成したいという事です。 Personクラスにまとめたのは、正規化の為ではなく、NameとAgeの重複コードをまとめたかったからです。(保守性を高める為) 「正規化とか考えずに」というのはSurferOnWwwさんの回答を引用させてもらったのでそう書きましたが、Shareholder・Manager・Memberの人は重複しないので「正規化の必要がないので、Personを別のテーブルにしないで」と言ったほうが正しいでしょうか。 いずれにせよ、正規化についてというよりも、Name・Ageをまとめるにあたってスマートに記述する方法を知りたかったというのが質問の本質です。 例えば、ご紹介いただいたNorthwindの場合、Customer・Employee・Supplierテーブルで、Address・City・Region・PostalCode・Countryを使用していますので、そのまま書くとモデルは以下のようになるかと思います。 public class Customer { public int Id { get; set; } public string CompanyName { get; set; } ... public string Address { get; set; } public string City { get; set; } public string Region { get; set; } public string PostalCode { get; set; } public string Country { get; set; } } public class Employee { public int Id { get; set; } public string LastName { get; set; } ... public string Address { get; set; } public string City { get; set; } public string Region { get; set; } public string PostalCode { get; set; } public string Country { get; set; } } public class Supplier { public int Id { get; set; } public string CompanyName { get; set; } ... public string Address { get; set; } public string City { get; set; } public string Region { get; set; } public string PostalCode { get; set; } public string Country { get; set; } } それを、Address・City・Region・PostalCode・Countryをクラスなどにまとめてスマートに記述する方法がないかという事を知りたかったという事です。 上記のコードのままだと、変数名やアノテーション・検証属性などに変更があった場合に、それぞれを変更しないといけませんので、バグの元にもなりかねないと考えたわけです。
退会済みユーザー

退会済みユーザー

2018/01/30 09:48

話が通じてないかも。 Northwind から Visual Studio のウィザードを使って DB First で EDM(.edmx ファイル他)を作ってみてください。それを見てもらってから話をした方がよさそうです。
退会済みユーザー

退会済みユーザー

2018/01/30 10:17

【追伸】 忘れてました。Core でしたね。Core では作ったことがないので自分が考えている結果とは違うかもしれませんが、とにかく作ってみてどうなるかを教えていただけると幸いです。
haru853

2018/01/31 00:52

EDMの作り方がわからなかった為、以下の手順でNorthwindからコードを生成してみました。 https://docs.microsoft.com/en-us/ef/core/get-started/aspnetcore/existing-db こちらでもよろしいでしょうか? EDMでないとダメでしたら再度調べますのでおっしゃってください。 public partial class Customers { public Customers() { CustomerCustomerDemo = new HashSet<CustomerCustomerDemo>(); Orders = new HashSet<Orders>(); } public string CustomerId { get; set; } public string CompanyName { get; set; } public string ContactName { get; set; } public string ContactTitle { get; set; } public string Address { get; set; } public string City { get; set; } public string Region { get; set; } public string PostalCode { get; set; } public string Country { get; set; } public string Phone { get; set; } public string Fax { get; set; } public ICollection<CustomerCustomerDemo> CustomerCustomerDemo { get; set; } public ICollection<Orders> Orders { get; set; } } public partial class Employees { public Employees() { EmployeeTerritories = new HashSet<EmployeeTerritories>(); InverseReportsToNavigation = new HashSet<Employees>(); Orders = new HashSet<Orders>(); } public int EmployeeId { get; set; } public string LastName { get; set; } public string FirstName { get; set; } public string Title { get; set; } public string TitleOfCourtesy { get; set; } public DateTime? BirthDate { get; set; } public DateTime? HireDate { get; set; } public string Address { get; set; } public string City { get; set; } public string Region { get; set; } public string PostalCode { get; set; } public string Country { get; set; } public string HomePhone { get; set; } public string Extension { get; set; } public byte[] Photo { get; set; } public string Notes { get; set; } public int? ReportsTo { get; set; } public string PhotoPath { get; set; } public Employees ReportsToNavigation { get; set; } public ICollection<EmployeeTerritories> EmployeeTerritories { get; set; } public ICollection<Employees> InverseReportsToNavigation { get; set; } public ICollection<Orders> Orders { get; set; } } public partial class Suppliers { public Suppliers() { Products = new HashSet<Products>(); } public int SupplierId { get; set; } public string CompanyName { get; set; } public string ContactName { get; set; } public string ContactTitle { get; set; } public string Address { get; set; } public string City { get; set; } public string Region { get; set; } public string PostalCode { get; set; } public string Country { get; set; } public string Phone { get; set; } public string Fax { get; set; } public string HomePage { get; set; } public ICollection<Products> Products { get; set; } } public partial class NorthwindContext : DbContext { public virtual DbSet<Categories> Categories { get; set; } public virtual DbSet<CustomerCustomerDemo> CustomerCustomerDemo { get; set; } public virtual DbSet<CustomerDemographics> CustomerDemographics { get; set; } public virtual DbSet<Customers> Customers { get; set; } public virtual DbSet<Employees> Employees { get; set; } public virtual DbSet<EmployeeTerritories> EmployeeTerritories { get; set; } public virtual DbSet<OrderDetails> OrderDetails { get; set; } public virtual DbSet<Orders> Orders { get; set; } public virtual DbSet<Products> Products { get; set; } public virtual DbSet<Region> Region { get; set; } public virtual DbSet<Shippers> Shippers { get; set; } public virtual DbSet<Suppliers> Suppliers { get; set; } public virtual DbSet<Territories> Territories { get; set; } ...
退会済みユーザー

退会済みユーザー

2018/01/31 01:50

> こちらでもよろしいでしょうか? すみませんが VS2017 は持っていないので確認できないです。 でも、質問者さんがアップされた DbContext クラスや DB のテーブルを表すクラスは、VS2015 で .NET Framework ベースで作ったものと微妙に違うものの(ナビゲーションプロパティに virtual が付与されてないところ)ほぼ同じです。 なので、機能的には同じ&基本的なところが違うということはまずありえないであろうと仮定してレスします。 コメント欄には書ききれないので回答欄に追記します。
haru853

2018/01/31 05:02

回答ありがとうございます。 > で、質問者さんのやりたいことは、DB に別テーブルを作るという訳ではなく、 > Address・City・Region・PostalCode・Country をクラスなどにまとめてスマートに記述する > ということですが、Customers, Employees, Suppliers テーブルのレベルで考えて、それらからデータを取得する場合は、そういう方法はないと思います。 まさしくこれが質問でお聞きしたかった事です。 > ひょっとして、上のコードの OrderCustomerEmployee クラスが質問者さんの言うまとめるためのクラスなのでしょうか? これは違います。(使うときではなく、EF Code Firstのモデルの一部のプロパティをまとめたかった) また、ナビゲーションプロパティについては、MVC5も使用した経験がありますので理解しています。 virtualの有無については、現状のEF Coreは遅延ローディングがサポートされていませんので(議論はされているようですが、Microsoftの方針としてはサポートしそうもない?)付けても付けなくても変わらないのではないかと思います。 ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問