从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
有关
谢谢浏览!