Entity Framework 6 中如何获取 EntityTypeConfiguration 的 Edm 信息?(一)
1. 案例1 - 类型和表之间的EF代码优先映射
从EF6.1开始,有一种更简单的方法可以做到这一点。有关 详细信息,请参阅我的新EF6.1类型和表格之间的映射。
直接贴代码了
从EF6.1开始,有一种更简单的方法可以做到这一点。
有关 详细信息,请参阅我的新EF6.1类型和表格之间的映射。
实体框架包括MetadataWorkspace,可让您访问EF保持模型形状的元数据。问题是,映射部分 - 将实体类型映射到表和属性到列的位 - 不是公共的。
我们确实有一个工作项可以在即将发布的版本之一中公开,但在EF6和早期版本中,它仍然是内部版本。你问,为什么它是内部的...因为EF中的元数据API是一团糟。已经公开的元数据API很糟糕......但是映射更糟糕。虽然我们无法证明花时间重写整个API - 我们确实希望在公开之前将其清理一下。
然而,有一种方法可以获得信息 - 虽然非常黑客。对于Code First,您可以将编写的元数据写入EDMX格式(这是设计者使用的xml格式)。从那里,我们可以使用LINQ to XML来获取映射信息。
EF Designer怎么样?
这篇文章中的代码适用于Code First,但是它使用的是EF Designer使用的相同xml格式,因此您可以轻松地使用它来使用设计器创建的EDMX文件。
对象模型
我们将编写一个帮助程序库,以易于使用的格式返回映射信息。我们将使用以下对象模型来表示元数据。
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
using System; using System.Collections.Generic; using System.Reflection; namespace MappingDemo { /// <summary> /// Represents the mapping of an entitiy type to one or mode tables in the database /// /// A single entity can be mapped to more than one table when 'Entity Splitting' is used /// Entity Splitting involves mapping different properties from the same type to different tables /// See http://msdn.com/data/jj591617#2.7 for more details /// </summary> public class TypeMapping { /// <summary> /// The type of the entity from the model /// </summary> public Type EntityType { get ; set ; } /// <summary> /// The table(s) that the entity is mapped to /// </summary> public List<TableMapping> TableMappings { get ; set ; } } /// <summary> /// Represents the mapping of an entity to a table in the database /// </summary> public class TableMapping { /// <summary> /// The name of the table the entity is mapped to /// </summary> public string TableName { get ; set ; } /// <summary> /// Details of the property-to-column mapping /// </summary> public List<PropertyMapping> PropertyMappings { get ; set ; } } /// <summary> /// Represents the mapping of a property to a column in the database /// </summary> public class PropertyMapping { /// <summary> /// The property from the entity type /// </summary> public PropertyInfo Property { get ; set ; } /// <summary> /// The column that property is mapped to /// </summary> public string ColumnName { get ; set ; } } } |
填充对象模型
现在我们有了一个对象模型,我们可以编写一些hacky 不那么直观的有趣代码来填充它。
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
using System; using System.Collections.Generic; using System.Data.Entity; using System.Data.Metadata.Edm; using System.Data.Entity.Infrastructure; using System.IO; using System.Linq; using System.Xml; using System.Xml.Linq; namespace MappingDemo { /// <summary> /// Represents that mapping between entity types and tables in an EF model /// </summary> public class EfMapping { /// <summary> /// Mapping information for each entity type in the model /// </summary> public List<TypeMapping> TypeMappings { get ; set ; } /// <summary> /// Initializes an instance of the EfMapping class /// </summary> /// <param name="db">The context to get the mapping from</param> public EfMapping(DbContext db) { this .TypeMappings = new List<TypeMapping>(); var metadata = ((IObjectContextAdapter)db).ObjectContext.MetadataWorkspace; // Conceptual part of the model has info about the shape of our entity classes var conceptualContainer = metadata.GetItems<EntityContainer>(DataSpace.CSpace).Single(); // Storage part of the model has info about the shape of our tables var storeContainer = metadata.GetItems<EntityContainer>(DataSpace.SSpace).Single(); // Object part of the model that contains info about the actual CLR types var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace)); // Mapping part of model is not public, so we need to write to xml and use 'LINQ to XML' var edmx = GetEdmx(db); // Loop thru each entity type in the model foreach ( var set in conceptualContainer.BaseEntitySets.OfType<EntitySet>()) { var typeMapping = new TypeMapping { TableMappings = new List<TableMapping>() }; this .TypeMappings.Add(typeMapping); // Get the CLR type of the entity typeMapping.EntityType = metadata .GetItems<EntityType>(DataSpace.OSpace) .Select(e => objectItemCollection.GetClrType(e)) .Single(e => e.FullName == set .ElementType.FullName); // Get the mapping fragments for this type // (types may have mutliple fragments if 'Entity Splitting' is used) var mappingFragments = edmx .Descendants() .Single(e => e.Name.LocalName == "EntityTypeMapping" && e.Attribute( "TypeName" ).Value == set .ElementType.FullName) .Descendants() .Where(e => e.Name.LocalName == "MappingFragment" ); foreach ( var mapping in mappingFragments) { var tableMapping = new TableMapping { PropertyMappings = new List<PropertyMapping>() }; typeMapping.TableMappings.Add(tableMapping); // Find the table that this fragment maps to var storeset = mapping.Attribute( "StoreEntitySet" ).Value; tableMapping.TableName = ( string )storeContainer .BaseEntitySets.OfType<EntitySet>() .Single(s => s.Name == storeset) .MetadataProperties[ "Table" ].Value; // Find the property-to-column mappings var propertyMappings = mapping .Descendants() .Where(e => e.Name.LocalName == "ScalarProperty" ); foreach ( var propertyMapping in propertyMappings) { // Find the property and column being mapped var propertyName = propertyMapping.Attribute( "Name" ).Value; var columnName = propertyMapping.Attribute( "ColumnName" ).Value; tableMapping.PropertyMappings.Add( new PropertyMapping { Property = typeMapping.EntityType.GetProperty(propertyName), ColumnName = columnName }); } } } } private static XDocument GetEdmx(DbContext db) { XDocument doc; using ( var memoryStream = new MemoryStream()) { using ( var xmlWriter = XmlWriter.Create( memoryStream, new XmlWriterSettings { Indent = true })) { EdmxWriter.WriteEdmx(db, xmlWriter); } memoryStream.Position = 0; doc = XDocument.Load(memoryStream); } return doc; } } } |
测试出来
现在让我们通过它运行Code First模型来测试我们的代码。您会注意到我已经包含Entity Splitting来演示为什么我们需要一个实体映射到的表的List。
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
using System; using System.Collections.Generic; using System.Data.Entity; namespace MappingDemo { class Program { static void Main( string [] args) { Database.SetInitializer( new DropCreateDatabaseAlways<BloggingContext>()); using ( var db = new BloggingContext()) { var mappingInfo = new EfMapping(db); foreach ( var item in mappingInfo.TypeMappings) { Console.WriteLine(item.EntityType.FullName); foreach ( var table in item.TableMappings) { Console.WriteLine( " => {0}" , table.TableName); foreach ( var column in table.PropertyMappings) { Console.WriteLine( " {0} => {1}" , column.Property.Name, column.ColumnName); } } } } } } public class BloggingContext : DbContext { public DbSet<Blog> Blogs { get ; set ; } public DbSet<Post> Posts { get ; set ; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { // Rename a column so that not all property/column names match modelBuilder.Entity<Post>() .Property(p => p.PostId) .HasColumnName( "post_id" ); // Perform 'Entity Splitting' on the Blog entity to test // mapping a single entity to multiple tables modelBuilder.Entity<Blog>() .Map(m => { m.Properties(b => new { b.Name, b.Url }); m.ToTable( "Blog_Details" ); }) .Map(m => { m.Properties(b => new { b.Image }); m.ToTable( "Blog_Photo" ); }); } } public class Blog { public int BlogId { get ; set ; } public string Name { get ; set ; } public string Url { get ; set ; } public byte [] Image { get ; set ; } public virtual List<Post> Posts { get ; set ; } } public class Post { public int PostId { get ; set ; } public string Title { get ; set ; } public string Content { get ; set ; } public int BlogId { get ; set ; } public virtual Blog Blog { get ; set ; } } } |
运行我们的应用程序会产生以下输出。
有关
2. 案例2(已经在 EF6.0.0 中测试)
增加 DbContextExtensions.cs 类。
代码如下:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Core.Mapping;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using System.Linq; namespace Sino.SnapshotComparsion.Data
{
/// <summary>
/// Entity Framework 6 中的 DbContext 扩展
/// </summary>
public static class DbContextExtensions
{
private readonly static Dictionary<string, EntitySetBase> _mappingCache = new Dictionary<string, EntitySetBase>(); private static EntitySetBase GetEntitySet(DbContext dbContext, Type type)
{
if (dbContext == null)
{
throw new ArgumentNullException("dbContext");
}
if (type == null)
{
throw new ArgumentNullException("type");
}
if (_mappingCache.ContainsKey(type.FullName))
{
return _mappingCache[type.FullName];
}
string baseTypeName = type.BaseType.Name;
string typeName = type.Name; ObjectContext octx = ((IObjectContextAdapter)dbContext).ObjectContext;
EntitySetBase es = octx.MetadataWorkspace
.GetItemCollection(DataSpace.SSpace)
.GetItems<EntityContainer>()
.SelectMany(c => c.BaseEntitySets
.Where(e => e.Name == typeName
|| e.Name == baseTypeName))
.FirstOrDefault();
if (es == null)
{
throw new ArgumentException("Entity type not found in GetEntitySet", typeName);
}
_mappingCache.Add(type.FullName, es);
return es;
} public static string GetTableName(DbContext context, Type type)
{
var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace; // Get the part of the model that contains info about the actual CLR types
var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace)); // Get the entity type from the model that maps to the CLR type
var entityType = metadata
.GetItems<EntityType>(DataSpace.OSpace)
.Single(e => objectItemCollection.GetClrType(e) == type); // Get the entity set that uses this entity type
var entitySet = metadata
.GetItems<EntityContainer>(DataSpace.CSpace)
.Single()
.EntitySets
.Single(s => s.ElementType.Name == entityType.Name); // Find the mapping between conceptual and storage model for this entity set
var mapping = metadata.GetItems<EntityContainerMapping>(DataSpace.CSSpace)
.Single()
.EntitySetMappings
.Single(s => s.EntitySet == entitySet); // Find the storage entity set (table) that the entity is mapped
var table = mapping
.EntityTypeMappings.Single()
.Fragments.Single()
.StoreEntitySet; // Return the table name from the storage entity set
return (string)table.MetadataProperties["Table"].Value ?? table.Name;
} /// <summary>
/// 获取映射的表的名称
/// </summary>
/// <typeparam name="TEntity">EF 实体的类型</typeparam>
/// <param name="dbContext">EF的 DbContext 上下文</param>
/// <returns></returns>
public static string[] GetPrimaryKeyName<TEntity>(this DbContext dbContext)
{
return GetPrimaryKeyName(dbContext, typeof(TEntity));
} /// <summary>
/// 获取映射的表的主键名称集合
/// </summary>
/// <param name="dbContext">EF的 DbContext 上下文</param>
/// <param name="type">EF 实体的类型</param>
/// <returns></returns>
public static string[] GetPrimaryKeyName(this DbContext dbContext, Type type)
{
EntitySetBase es = GetEntitySet(dbContext, type);
return es.ElementType.KeyProperties.Select(c => c.Name).ToArray();
} /// <summary>
/// 获取映射的表的主键名称集合的字符串形式
/// </summary>
/// <param name="dbContext">EF的 DbContext 上下文</param>
/// <param name="type">EF 实体的类型</param>
/// <returns></returns>
public static string GetPrimaryKeyNameString(this DbContext dbContext, Type type)
{
return string.Join(",", GetPrimaryKeyName(dbContext, type));
} /// <summary>
/// 获取映射的表的主键名称集合的字符串形式
/// </summary>
/// <typeparam name="TEntity">EF 实体的类型</typeparam>
/// <param name="dbContext">EF的 DbContext 上下文</param>
/// <returns></returns>
public static string GetPrimaryKeyNameString<TEntity>(this DbContext dbContext)
{
return GetPrimaryKeyNameString(dbContext, typeof(TEntity));
}
}
}
3. 案例3 - EF6.1类型和表之间的映射
前段时间我在博客上写了一篇关于如何找到给定实体映射到的表的文章。该帖子中的解决方案解决了访问此信息的API是内部的问题。在EF6.1中,我们将映射API公之于众,因此它现在变得更加容易。
此代码的另一个优点是它适用于Code First和EF Designer模型。
代码
不用多说,这里是找到给定CLR类型的表名的代码。我已经包含了一个完整的控制台应用程序列表,该应用程序演示了代码的运行情况,但是如果您需要的话,您可以获取GetTableName方法。
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
using System; using System.Collections.Generic; using System.Data.Entity; using System.Data.Entity.Core.Mapping; using System.Data.Entity.Core.Metadata.Edm; using System.Data.Entity.Infrastructure; using System.Linq; namespace MappingDemo { class Program { static void Main( string [] args) { using ( var db = new BloggingContext()) { Console.WriteLine( "Blog maps to: {0}" , GetTableName( typeof (Blog), db)); Console.WriteLine( "Post maps to: {0}" , GetTableName( typeof (Post), db)); } } public static string GetTableName(Type type, DbContext context) { var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace; // Get the part of the model that contains info about the actual CLR types var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace)); // Get the entity type from the model that maps to the CLR type var entityType = metadata .GetItems<EntityType>(DataSpace.OSpace) .Single(e => objectItemCollection.GetClrType(e) == type); // Get the entity set that uses this entity type var entitySet = metadata .GetItems<EntityContainer>(DataSpace.CSpace) .Single() .EntitySets .Single(s => s.ElementType.Name == entityType.Name); // Find the mapping between conceptual and storage model for this entity set var mapping = metadata.GetItems<EntityContainerMapping>(DataSpace.CSSpace) .Single() .EntitySetMappings .Single(s => s.EntitySet == entitySet); // Find the storage entity set (table) that the entity is mapped var table = mapping .EntityTypeMappings.Single() .Fragments.Single() .StoreEntitySet; // Return the table name from the storage entity set return ( string )table.MetadataProperties[ "Table" ].Value ?? table.Name; } } public class BloggingContext : DbContext { public DbSet<Blog> Blogs { get ; set ; } public DbSet<Post> Posts { get ; set ; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Blog>().ToTable( "t_blog" ); modelBuilder.Entity<Post>().ToTable( "t_post" ); } } public class Blog { public int BlogId { get ; set ; } public string Url { get ; set ; } public List<Post> Posts { get ; set ; } } public class Post { public int PostId { get ; set ; } public string Title { get ; set ; } public string Body { get ; set ; } public int BlogId { get ; set ; } public Blog Blog { get ; set ; } } } |
高级映射的调整
EF支持称为“实体拆分”的高级映射模式。在此模式中,您可以在多个表之间拆分实体的属性。以下是实体拆分Post类的Fluent API调用示例。
1
2
3
4
五
6
7
8
9
10
11
|
modelBuilder.Entity<Post>() .Map(m => { m.Properties(p => new { p.PostId, p.Title, p.BlogId }); m.ToTable( "t_post" ); }) .Map(m => { m.Properties(p => new { p.PostId, p.Body }); m.ToTable( "t_post_body" ); }); |
为了处理这个问题,我们可以更新GetTableName方法以返回类型映射到的表的可枚举数。对前一个实现的唯一更改是从映射片段中查找表名的最后两个代码块。
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
|
public static IEnumerable< string > GetTableName(Type type, DbContext context) { var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace; // Get the part of the model that contains info about the actual CLR types var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace)); // Get the entity type from the model that maps to the CLR type var entityType = metadata .GetItems<EntityType>(DataSpace.OSpace) .Single(e => objectItemCollection.GetClrType(e) == type); // Get the entity set that uses this entity type var entitySet = metadata .GetItems<EntityContainer>(DataSpace.CSpace) .Single() .EntitySets .Single(s => s.ElementType.Name == entityType.Name); // Find the mapping between conceptual and storage model for this entity set var mapping = metadata.GetItems<EntityContainerMapping>(DataSpace.CSSpace) .Single() .EntitySetMappings .Single(s => s.EntitySet == entitySet); // Find the storage entity sets (tables) that the entity is mapped var tables = mapping .EntityTypeMappings.Single() .Fragments; // Return the table name from the storage entity set return tables.Select(f => ( string )f.StoreEntitySet.MetadataProperties[ "Table" ].Value ?? f.StoreEntitySet.Name); } |
其他变化
有些人已从这篇文章中获取代码并将其扩展以适应其他映射场景:
- 属性和列之间的映射 - 本杰明马蒂
- 在模型中使用继承进行映射 --Brian Lowry
有关
谢谢浏览!
Entity Framework 6 中如何获取 EntityTypeConfiguration 的 Edm 信息?(一)的更多相关文章
- Entity Framework 6 中如何获取 EntityTypeConfiguration 的 Edm 信息?(五)
直接贴代码了: NewsInfo 实体类: public class NewsInfo { public int NewsInfoId { get; set; } public string News ...
- Entity Framework 6 中如何获取 EntityTypeConfiguration 的 Edm 信息?(四)
经过上一篇,里面有测试代码,循环60万次,耗时14秒.本次我们增加缓存来优化它. DbContextExtensions.cs using System; using System.Collectio ...
- Entity Framework 6 中如何获取 EntityTypeConfiguration 的 Edm 信息?(三)
接着上一篇,我们继续来优化. 直接贴代码了: LambdaHelper.cs using System; using System.Collections.Generic; using System. ...
- Entity Framework 6 中如何获取 EntityTypeConfiguration 的 Edm 信息?(二)
接着上一篇 直接贴代码了: using System; using System.Collections.Generic; using System.Data.Entity; using System ...
- 浅析Entity Framework Core中的并发处理
前言 Entity Framework Core 2.0更新也已经有一段时间了,园子里也有不少的文章.. 本文主要是浅析一下Entity Framework Core的并发处理方式. 1.常见的并发处 ...
- 在Entity Framework 7中进行数据迁移
(此文章同时发表在本人微信公众号“dotNET每日精华文章”,欢迎右边二维码来关注.) 题记:虽然EF7重新设计了Entity Framework,不过也还是能够支持数据迁移的. Entity Fra ...
- [Programming Entity Framework] 第3章 查询实体数据模型(EDM)(一)
http://www.cnblogs.com/sansi/archive/2012/10/18/2729337.html Programming Entity Framework 第二版翻译索引 你可 ...
- 如何处理Entity Framework / Entity Framework Core中的DbUpdateConcurrencyException异常(转载)
1. Concurrency的作用 场景有个修改用户的页面功能,我们有一条数据User, ID是1的这个User的年龄是20, 性别是female(数据库中的原始数据)正确的该User的年龄是25, ...
- Entity Framework添加记录时获取自增ID值
与Entity Framework相伴的日子痛并快乐着.今天和大家分享一下一个快乐,两个痛苦. 先说快乐的吧.Entity Framework在将数据插入数据库时,如果主键字段是自增标识列,会将该自增 ...
随机推荐
- 数据库之MySQL与Python交互
准备数据 创建数据表 -- 创建 "京东" 数据库 create database jing_dong charset=utf8; -- 使用 "京东" 数据库 ...
- 用python执行Linux命令
例1:在python中包装ls命令 #!/usr/bin/env python #python wapper for the ls command import subprocess subproce ...
- Python GUI开发,效率提升10倍的方法!
1 框架简介 这个框架的名字叫 PySimpleGUI,它完全基于Python语言,能非常方便地开发GUI界面,代码量相比现有框架减少50%到90%.并且,它提供了极为友好的Python风格的接口,大 ...
- css媒体查询aspect-ratio宽高比在less中的使用
css媒体查询有一个 宽高比很方便,aspect-ratio ,可以直接使用宽/高 来进行页面适配 使用样例如下: // 宽高比在((320/50)+(728/90))/2 两个尺寸中间值以内 适 ...
- 简单两行,实现无线WiFi共享上网,手机抓包再也不用愁了
你是否为WiFi共享而发愁,各个无线共享软件,某某共享精灵,某某免费WiFi,某某共享大师,某某随身WiFi,一个比一个难用,一个比一个私货多,一个比一个广告多,如果装上了它们,你的电脑就基本沦陷了, ...
- 靠谱的CSS样式
0. 引言 记录一些用到的CSS样式,只要可以使用CSS3的地方都可以使用. 1. CSS样式 flex布局:引用 阮一峰的网络日志http://www.ruanyifeng.com/blog/201 ...
- PL/SQL编写的SQL语句插入SqlPlus时,报错 PLS-00302
最近刚开始用PL/SQL,然后发现写SQL语句时,运行的时候,会对表中的字段报错. 好像是对字段的使用有问题 原来写的错误代码大概像这样 DECLARE xuehao XSB.id% TYPE; BE ...
- sqlserver查询是否阻塞
查询当前正在执行的语句 SELECT der.[session_id],der.[blocking_session_id], sp.lastwaittype,sp.hostname,sp.progra ...
- Ubuntu12.04配置永久静态ip地址
1 切换到 root 用户 先临时切换到root用户(因为我不想每打一个命令都要在前面加上sudo) $ sudo -i 然后,输入密码临时切换到root用户.如下图: 2 配置IP 打开 /etc/ ...
- 微软发布Visual Studio Online公共预览版和ML.NET 1.4
在今天的Ignite 2019上,Microsoft启动了 Visual Studio Online 公共预览版.Visual Studio Online将Visual Studio,云托管的开发人员 ...