エンティティフレームワークと名前ルール

覚えておいて、海辺の魔法使いのウルスラ・ル・グインと:「彼の名前について決して人に尋ねないでください。 絶対に電話しないでください。」 残念ながら、Entity Frameworkは「すぐに使える」この素晴らしいルールに完全にガイドされておらず、データベーススキーマ(Database First戦略)に基づいてクラスを生成する場合、データベース内の対応するテーブルが指定されているクラス、クラスプロパティ、およびナビゲーションプロパティを正確に参照します。



そして、t_tablenameテンプレート(例:t_order_product)に従って、何らかのテンプレートに従ってテーブルに名前が付けられている既存のデータベースに対して新しいプロジェクトを開発するタスクがある場合はどうでしょうか。 そして、プロジェクトは完全に異なる命名契約を採用し、開発者は「人間」を自分たちの観点から見ると、名前(OrderProduct)を見たいと思っています。 もちろん、適切な管理上の決定を下すことで状況から抜け出すことができますが、時には美の感覚に逆らうことを望まないこともあります。



さらに、Entity Frameworkにはコード生成テンプレートが登場しています。 開発者がデータベーススキーマに基づいてクラスを作成するプロセスを制御できるように、このために登場しました。 ただし、いくつかのパターンに限定することはできませんでしたが、それについては以下で詳しく説明します。



そのため、タスクは次のとおりです。データモデルの作成/更新時にエンティティの名前を自動的に変更することです。 簡単にするために、列名は受け入れ可能な形式であり、テーブル名のみで操作すると仮定します。



テーブル名は次の名前を生成します。







T4テンプレート自体はファイル名のみを生成し、Model.edmxファイルから他のすべてを描画します。 このファイルには、データベースエンティティの説明、概念モデル、および相互のマッピングが含まれています。 理論的には、テンプレートに変更されたエンティティ名を生成させることができますが、これは悲しい結果につながります。 .edmxファイルに新しい名前と一致するものがないため、データベースクエリは失敗します。SQLクエリを構成するQueryProviderは、クラス名でテーブル名を見つけられません。 これを確認するには、クラスの名前を自動リファクタリングで自動的に変更するだけで十分です。 これから、簡単な結論に従います-.edmxファイル自体を変更する必要があります。



実際、このタスクは退屈なほど難しくありません。 .edmx構造の知識と、必要なものに対応するものの理解。 さらに、すべてが可能な限り最良の方法であるように、場合によっては名前を複数形に翻訳する必要があります。 DbContextを使用すると、すべてが簡単になります:取得して翻訳します。 ナビゲーションプロパティはより複雑です。関係を分析し、この接続がどちら側に「来る」かを判断する必要があります。 .edmxファイルに加えて、.edmx.diagramファイルも変更する必要があります。これには、モデルデザイナーで表示できる美しいプレートの説明が保存されます。



上記の問題を解決した結果はEntityTransformerクラスであり、これに注目したいと思います。 彼が行うことは、.edmxファイルをダウンロードし、その内容を分析し、対応する属性の値を変更し、変更されたファイルを保存することだけです。 独自の目的で使用する場合は、エンティティの名前を変更するためのルールを定義するTransform(string inputString, bool pluralize)



メソッドTransform(string inputString, bool pluralize)



を変更するだけです。



変換を開始する方法はいくつかあります。たとえば、外部コンソールアプリケーションの作成などです。 ただし、「シームレス」ソリューションを取得し、テンプレート(存在する場合)を使用したかったため、クラスはテンプレートに設計され、メインコード生成テンプレートに接続されました。 接続自体は簡単です。EntityTransformer.ttincludeファイルをプロジェクトに追加し、下のテンプレートをその中にコピーして、強調表示された行をModel.ttファイルに追加します。







さらに、プロジェクトにSystem.Data.Entity.Design



へのリンクを追加する必要があります-これはPluralizationService



を使用するために必要PluralizationService



。 複数に変換する必要がない場合は、リンクを追加できず、対応するコード行をテンプレートから除外できません。



テンプレートを起動するには、テンプレートを開いて保存する必要があり、Model.ttとModel.Context.ttの2回行う必要があります。 それ以外の場合、元の名前(テーブル名)がDbContextまたはエンティティクラスに残ります。 残念ながら、テンプレートを自動的に開始する方法が見つかりませんでした。



EntityTransformer.ttincludeテンプレート
 <#@ assembly name="System.Data.Entity.Design" #> <#@ import namespace="System" #> <#@ import namespace="System.Data.Entity.Design.PluralizationServices" #> <#@ import namespace="System.Globalization" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text.RegularExpressions" #> <#@ import namespace="System.Xml.Linq" #> <#+ public class EntityTransformer { readonly PluralizationService _pluralizationService = PluralizationService.CreateService(CultureInfo.GetCultureInfo("en-US")); const string DESIGNER_NAMESPACE = "http://schemas.microsoft.com/ado/2009/11/edmx"; private string Transform(string inputString, bool pluralize) { string result = string.Empty; const string PREFIX = "t_"; Regex regex = new Regex(string.Format(@"(?<namespace>\w+?\.)*(?<prefix>{0})*(\w+)", PREFIX)); var groups = regex.Match(inputString).Groups; string namespc = groups["namespace"].Value; string[] parts = groups[1].Value.Split(new[] {"_"}, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < parts.Length; i++) { string addingPart = FirstCharToUpper(parts[i]); if (pluralize && i == parts.Length - 1) addingPart = _pluralizationService.Pluralize(addingPart); result += addingPart; } result = namespc + result; return result; } private string Transform(string inputString) { string result = Transform(inputString, false); return result; } private void Transform(XAttribute attribute) { attribute.Value = Transform(attribute.Value); } private void Transform(XAttribute attribute, bool pluralize) { attribute.Value = Transform(attribute.Value, pluralize); } public void TransformEntities(string inputFile) { XDocument document = XDocument.Load(inputFile); const string SSDL_NAMESPACE = "http://schemas.microsoft.com/ado/2009/11/edm/ssdl"; const string CSDL_NAMESPACE = "http://schemas.microsoft.com/ado/2009/11/edm"; const string MSL_NAMESPACE = "http://schemas.microsoft.com/ado/2009/11/mapping/cs"; XElement ssdl = document.Descendants(XName.Get("Schema", SSDL_NAMESPACE)).First(); XElement csdl = document.Descendants(XName.Get("Schema", CSDL_NAMESPACE)).First(); XElement msl = document.Descendants(XName.Get("Mapping", MSL_NAMESPACE)).First(); XElement designerDiagram = document.Descendants(XName.Get("Designer", DESIGNER_NAMESPACE)).First(); TransformCsdl(csdl, ssdl); TransformMsl(MSL_NAMESPACE, msl); TransformDesigner(DESIGNER_NAMESPACE, designerDiagram, inputFile); document.Save(inputFile); } private void TransformDesigner(string designerNamespace, XElement designerDiagram, string modelFilePath) { Action<XElement> transformDesigner = diagram => { var shapes = diagram.Descendants(XName.Get("EntityTypeShape", designerNamespace)); foreach (var item in shapes) Transform(item.Attribute("EntityType")); }; transformDesigner(designerDiagram); string diagramFilePath = string.Format("{0}.diagram", modelFilePath); if (File.Exists(diagramFilePath)) { XDocument document = XDocument.Load(diagramFilePath); designerDiagram = document.Descendants(XName.Get("Designer", DESIGNER_NAMESPACE)).First(); transformDesigner(designerDiagram); document.Save(diagramFilePath); } } private void TransformMsl(string mslNamespace, XElement msl) { var entityContainerMapping = msl.Element(XName.Get("EntityContainerMapping", mslNamespace)); if (entityContainerMapping == null) throw new Exception("Element EntityContainerMapping not found."); foreach (var entitySetMapping in entityContainerMapping.Elements(XName.Get("EntitySetMapping", mslNamespace))) { Transform(entitySetMapping.Attribute("Name"), true); foreach (var entityTypeMapping in entitySetMapping.Elements(XName.Get("EntityTypeMapping", mslNamespace))) Transform(entityTypeMapping.Attribute("TypeName")); } } private void TransformCsdl(XElement csdl, XElement ssdl) { string csdlNamespace = csdl.GetDefaultNamespace().NamespaceName; Func<XElement, string, IEnumerable<XElement>> getElements = (root, localName) => root.Elements(XName.Get(localName, csdlNamespace)); var entityContainer = csdl.Element(XName.Get("EntityContainer", csdlNamespace)); if (entityContainer == null) throw new Exception("Element EntityContainer not found."); foreach (var entitySet in getElements(entityContainer, "EntitySet")) { Transform(entitySet.Attribute("Name"), true); Transform(entitySet.Attribute("EntityType")); } foreach (var associationSet in getElements(entityContainer, "AssociationSet")) foreach (var end in getElements(associationSet, "End")) Transform(end.Attribute("EntitySet"), true); foreach (var entityType in getElements(csdl, "EntityType")) Transform(entityType.Attribute("Name")); foreach (var association in getElements(csdl, "Association")) foreach (var end in getElements(association, "End")) Transform(end.Attribute("Type")); TransformNavigationProperties(csdl, ssdl); } private void TransformNavigationProperties(XElement csdl, XElement ssdl) { string ssdlNamespace = ssdl.GetDefaultNamespace().NamespaceName; string csdlNamespace = csdl.GetDefaultNamespace().NamespaceName; var associationSets = ssdl.Descendants(XName.Get("AssociationSet", ssdlNamespace)); foreach (XElement associationSet in associationSets) { var association = ssdl.Descendants(XName.Get("Association", ssdlNamespace)) .Single(a => a.Attribute("Name").Value == associationSet.Attribute("Name").Value); var roles = association.Elements().Where(e => e.Name.LocalName == "End"); var manyRole = roles.FirstOrDefault(e => e.Attribute("Multiplicity").Value == "*"); var csdlAssotiationSet = csdl.Descendants(XName.Get("AssociationSet", csdlNamespace)) .Single(e => e.Attribute("Name").Value == associationSet.Attribute("Name").Value); string associationName = csdlAssotiationSet.Attribute("Association").Value; var navigationProperties = csdl.Descendants(XName.Get("NavigationProperty", csdlNamespace)) .Where(e => e.Attribute("Relationship").Value == associationName); foreach (XElement navigationProperty in navigationProperties) { bool pluralize = manyRole != null && navigationProperty.Attribute("ToRole").Value == manyRole.Attribute("Role").Value; Transform(navigationProperty.Attribute("Name"), pluralize); } } } private static string FirstCharToUpper(string input) { if (String.IsNullOrEmpty(input)) throw new ArgumentException("Empty string"); return input.First().ToString().ToUpper() + input.Substring(1); } } #>
      
      









テンプレートをテストするために、最も一般的な状況(1対多、1対1、多対多の関係、2つのテーブル間の複数の関係)をエミュレートするデータベースを使用しました。



テストテーブルを作成するためのスクリプト
 CREATE TABLE [dbo].[t_address]( [AddressId] [int] IDENTITY(1,1) NOT NULL, [AddressName] [nvarchar](500) NOT NULL, CONSTRAINT [PK_t_address] PRIMARY KEY CLUSTERED ( [AddressId] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO CREATE TABLE [dbo].[t_customer]( [CustomerId] [int] IDENTITY(1,1) NOT NULL, [CustomerName] [nvarchar](50) NOT NULL, [LocationAddressId] [int] NULL, [PostalAddressId] [int] NULL, PRIMARY KEY CLUSTERED ( [CustomerId] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO CREATE TABLE [dbo].[t_customer_info]( [CustomerId] [int] NOT NULL, [CustomerDescription] [nvarchar](50) NULL, CONSTRAINT [PK_t_customer_info] PRIMARY KEY CLUSTERED ( [CustomerId] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO CREATE TABLE [dbo].[t_order]( [OrderId] [int] IDENTITY(1,1) NOT NULL, [CustomerId] [int] NOT NULL, [CreateDate] AS (getdate()), CONSTRAINT [PK__t_Order__C3905BCFC0AF501C] PRIMARY KEY CLUSTERED ( [OrderId] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO CREATE TABLE [dbo].[t_order_product]( [OrderId] [int] NOT NULL, [ProductId] [int] NOT NULL, [Count] [int] NOT NULL, CONSTRAINT [PK_t_order_product] PRIMARY KEY CLUSTERED ( [OrderId] ASC, [ProductId] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO CREATE TABLE [dbo].[t_product]( [ProductId] [int] IDENTITY(1,1) NOT NULL, [ProductName] [nvarchar](100) NOT NULL, [ProductPrice] [decimal](10, 2) NOT NULL, CONSTRAINT [PK_t_product] PRIMARY KEY CLUSTERED ( [ProductId] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO CREATE TABLE [dbo].[t_test_person]( [TestId] [int] IDENTITY(1,1) NOT NULL, CONSTRAINT [PK_t_test_person] PRIMARY KEY CLUSTERED ( [TestId] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO ALTER TABLE [dbo].[t_customer] WITH CHECK ADD CONSTRAINT [FK_t_customer_t_address] FOREIGN KEY([LocationAddressId]) REFERENCES [dbo].[t_address] ([AddressId]) GO ALTER TABLE [dbo].[t_customer] CHECK CONSTRAINT [FK_t_customer_t_address] GO ALTER TABLE [dbo].[t_customer] WITH CHECK ADD CONSTRAINT [FK_t_customer_t_address1] FOREIGN KEY([PostalAddressId]) REFERENCES [dbo].[t_address] ([AddressId]) GO ALTER TABLE [dbo].[t_customer] CHECK CONSTRAINT [FK_t_customer_t_address1] GO ALTER TABLE [dbo].[t_customer_info] WITH CHECK ADD CONSTRAINT [FK_t_customer_info_t_customer] FOREIGN KEY([CustomerId]) REFERENCES [dbo].[t_customer] ([CustomerId]) GO ALTER TABLE [dbo].[t_customer_info] CHECK CONSTRAINT [FK_t_customer_info_t_customer] GO ALTER TABLE [dbo].[t_order] WITH CHECK ADD CONSTRAINT [FK_t_Order_To_t_Customer] FOREIGN KEY([CustomerId]) REFERENCES [dbo].[t_customer] ([CustomerId]) ON DELETE CASCADE GO ALTER TABLE [dbo].[t_order] CHECK CONSTRAINT [FK_t_Order_To_t_Customer] GO ALTER TABLE [dbo].[t_order_product] WITH CHECK ADD CONSTRAINT [FK_t_order_product_t_order] FOREIGN KEY([OrderId]) REFERENCES [dbo].[t_order] ([OrderId]) ON DELETE CASCADE GO ALTER TABLE [dbo].[t_order_product] CHECK CONSTRAINT [FK_t_order_product_t_order] GO ALTER TABLE [dbo].[t_order_product] WITH CHECK ADD CONSTRAINT [FK_t_order_product_t_product] FOREIGN KEY([ProductId]) REFERENCES [dbo].[t_product] ([ProductId]) ON DELETE CASCADE GO ALTER TABLE [dbo].[t_order_product] CHECK CONSTRAINT [FK_t_order_product_t_product] GO
      
      









名前を変更してください!



All Articles