从entity framework元数据获取数据库表名
我试图找出一种方法来获得给定的实体types的基础SQL表名。 我已经尝试了MetadataWorkspace查询,虽然我可以从对象或存储空间获取大量信息,但似乎无法弄清楚如何在两者之间进行映射。
所以说我有一个名为Lookup的对象模型中的types – 如何在数据库中find表名(wws_lookups)?
我可以查询所有EntityType对象的CSpace和SSpace,我可以看到正确列出,但我不知道如何从CSpace获取SSpace。
有没有办法做到这一点?
我使用Nigel的方法(从.ToTraceString()
提取表名),但进行了一些修改,因为如果表不在默认的SQL Server架构( dbo.{table-name}
)中,他的代码将不起作用。
我为DbContext
和ObjectContext
对象创build了扩展方法:
public static class ContextExtensions { public static string GetTableName<T>(this DbContext context) where T : class { ObjectContext objectContext = ((IObjectContextAdapter) context).ObjectContext; return objectContext.GetTableName<T>(); } public static string GetTableName<T>(this ObjectContext context) where T : class { string sql = context.CreateObjectSet<T>().ToTraceString(); Regex regex = new Regex(@"FROM\s+(?<table>.+)\s+AS"); Match match = regex.Match(sql); string table = match.Groups["table"].Value; return table; } }
更多细节在这里:
entity framework:从实体获取映射的表名
编辑这个答案现在过时了,因为EF 6.1的新function: 表types之间的映射 。 去那里先!
我有其他答案的问题,因为我有一个派生types。 我得到了这个方法(在我的上下文类中)的工作 – 我目前在我的模型中只有一个inheritance层
private readonly static Dictionary<Type, EntitySetBase> _mappingCache = new Dictionary<Type, EntitySetBase>(); private ObjectContext _ObjectContext { get { return (this as IObjectContextAdapter).ObjectContext; } } private EntitySetBase GetEntitySet(Type type) { if (_mappingCache.ContainsKey(type)) return _mappingCache[type]; type = GetObjectType(type); string baseTypeName = type.BaseType.Name; string typeName = type.Name; ObjectContext octx = _ObjectContext; var 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); // Put es in cache. _mappingCache.Add(type, es); return es; } internal String GetTableName(Type type) { EntitySetBase es = GetEntitySet(type); //if you are using EF6 return String.Format("[{0}].[{1}]", es.Schema, es.Table); //if you have a version prior to EF6 //return string.Format( "[{0}].[{1}]", // es.MetadataProperties["Schema"].Value, // es.MetadataProperties["Table"].Value ); } internal Type GetObjectType(Type type) { return System.Data.Entity.Core.Objects.ObjectContext.GetObjectType(type); }
注意有计划改进元数据API ,如果这没有得到我们想要的,那么我们可以看看EF代码首先映射types和表
不,不幸的是,使用元数据API无法获取给定实体的表名是不可能的。
这是因为Mapping元数据不是公开的,因此使用EF的API无法从C-Space转到S-Space。
如果你真的需要这样做,你总是可以通过parsingMSL来构build地图。 这不是心灰意冷 ,但它应该是可能的,除非你使用的QueryViews (这是非常罕见的),在这一点上是所有的意图和目的不可能(你将不得不parsingESQL …唉! )
Alex James
微软。
有一种方法可以使用EF删除数据,而不必首先加载它,我将其描述为: http : //nigelfindlater.blogspot.com/2010/04/how-to-delete-objects-in-ef4 -without.html
诀窍是将IQueriable强制转换为ObjectQuery并使用ToTraceString方法。 然后编辑生成的SQLstring。 它的工作原理,但你需要小心,因为你正在绕过EF的维护依赖和限制的机制。 但出于性能的原因,我认为这样做是可以的….
玩的开心…
奈杰尔…
private string GetClause<TEntity>(IQueryable<TEntity> clause) where TEntity : class { string snippet = "FROM [dbo].["; string sql = ((ObjectQuery<TEntity>)clause).ToTraceString(); string sqlFirstPart = sql.Substring(sql.IndexOf(snippet)); sqlFirstPart = sqlFirstPart.Replace("AS [Extent1]", ""); sqlFirstPart = sqlFirstPart.Replace("[Extent1].", ""); return sqlFirstPart; } public void DeleteAll<TEntity>(IQueryable<TEntity> clause) where TEntity : class { string sqlClause = GetClause<TEntity>(clause); this.context.ExecuteStoreCommand(string.Format(CultureInfo.InvariantCulture, "DELETE {0}", sqlClause)); }
如果您使用T4模板作为POCO类,则可以通过更改T4模板来获取它。 请参阅片段:
<# //////////////////////////////////////////////////////////////////////////////// region.Begin("Custom Properties"); string xPath = "//*[@TypeName='" + entity.FullName + "']"; XmlDocument doc = new XmlDocument(); doc.Load(inputFile); XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable); nsmgr.AddNamespace("edmx", "http://schemas.microsoft.com/ado/2008/10/edmx"); XmlNode item; XmlElement root = doc.DocumentElement; item = root.SelectSingleNode(xPath); #> //<#= xPath #> //<#= entity.FullName #> //<#= (item == null).ToString() #> <# if (item != null) #> // Table Name from database public string TableName { get { return "<#= item.ChildNodes[0].Attributes["StoreEntitySet"].Value #>"; } } <# region.End(); ////////////////////////////////////////////////////////////////////////////////
一个可能的解决方法(不是很好,但也没有select…):
var sql = Context.EntitySetName.ToTraceString();
然后parsingSQL,这应该很简单。
这是我能够使用LINQ to XML。 代码也获取列名的映射。
var d = XDocument.Load("MyModel.edmx"); XNamespace n = "http://schemas.microsoft.com/ado/2008/09/mapping/cs"; var l = (from etm in d.Descendants() where etm.Name == n + "EntityTypeMapping" let s = etm.Attribute("TypeName").Value select new { Name = s.Remove(0, s.IndexOf(".") + 1).Replace(")", ""), Table = etm.Element(n + "MappingFragment").Attribute("StoreEntitySet").Value, Properties = (from sp in etm.Descendants(n + "ScalarProperty") select new { Name = sp.Attribute("Name").Value, Column = sp.Attribute("ColumnName").Value }).ToArray() }).ToArray();
更好的方法是使用元数据中的StoreItemCollection。 这家伙已经提供了一个使用它的例子: 获取表和关系
EF 6.1,代码优先:
public static string GetTableName<T>(this DbContext context) where T : class { ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext; return objectContext.GetTableName(typeof(T)); } public static string GetTableName(this DbContext context, Type t) { ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext; return objectContext.GetTableName(t); } private static readonly Dictionary<Type,string> TableNames = new Dictionary<Type, string>(); public static string GetTableName(this ObjectContext context, Type t) { string result; if (!TableNames.TryGetValue(t, out result)) { lock (TableNames) { if (!TableNames.TryGetValue(t, out result)) { string entityName = t.Name; ReadOnlyCollection<EntityContainerMapping> storageMetadata = context.MetadataWorkspace.GetItems<EntityContainerMapping>(DataSpace.CSSpace); foreach (EntityContainerMapping ecm in storageMetadata) { EntitySet entitySet; if (ecm.StoreEntityContainer.TryGetEntitySetByName(entityName, true, out entitySet)) { if (String.IsNullOrEmpty(entitySet.Schema)) { result = entitySet.Table; break; } //we must recognize if we are under SQL Server Compact version, which does not support multiple schemas //SQL server compact does not support schemas, entity framework sets entitySet.Schema set to "dbo", anyway //the System.Data.Entity.Infrastructure.TableExistenceChecker.GetTableName() returns only table name //schema is (not) added by the overrides of the method AnyModelTableExistsInDatabase //the SqlCeTableExistenceChecker has the knowledge that there is no metadata schema needed //the SqlTableExistenceChecker has the knowledge that there is metadata with schema, which should be added to the table names var entityConnection = (System.Data.Entity.Core.EntityClient.EntityConnection) context.Connection; DbConnection storeConnection = entityConnection.StoreConnection; if (storeConnection != null && "SqlCeConnection".Equals(storeConnection.GetType().Name, StringComparison.OrdinalIgnoreCase)) { result = entitySet.Table; break; } result = entitySet.Schema + "." + entitySet.Table; break; } } TableNames.Add(t,result); } } } return result; }
这是查找表名的另一种方法。 这有点奇怪,但工作。 VB:
For Each Table In northwind.MetadataWorkspace.GetItemCollection(New System.Data.Metadata.Edm.DataSpace) 'adds table name to a list of strings all table names in EF have the project namespace in front of it.' If Table.ToString.Contains("namespace of project") then 'using substring to remove project namespace from the table name.' TableNames.Add(Table.ToString.Substring("length of namespace name")) End If Next
您可以尝试MappingAPI扩展: https ://efmappingapi.codeplex.com/
这真的很容易使用
context.Db<YourEntityType>().TableName
如果您在EF6中使用codefirst,则可以在dbcontext类中添加如下所示的内容。
public string GetTableName(Type entityType) { var sql = Set(entityType).ToString(); var regex = new Regex(@"FROM \[dbo\]\.\[(?<table>.*)\] AS"); var match = regex.Match(sql); return match.Groups["table"].Value; }
假设你有上下文,并且在内存中有一个选定的实体,你需要find真正的表名。
公共静态类ObjectContextExtentions { 公共静态stringTableNameFor(此ObjectContext上下文,ObjectStateEntry条目) { var generic = 第一个(p => p.Name == entry.EntityKey.EntitySetName); var objectset = generic.GetValue(context,null); var method = objectset.GetType()。GetMethod(“ToTraceString”); var sql =(String)method.Invoke(objectset,null); var match = Regex.Match(sql,@“FROM \ s + \ [dbo \] \。\ [(?<TableName> [^ \]] +)\]”,RegexOptions.Multiline); 如果(match.Success) { 返回match.Groups [“TableName”]。Value; } 抛出新的ArgumentException(“无法find表名”。 } }
其实我已经经历了同样的问题,我已经产生了一个抽象的代码片段,它给你两个Dictionary<string,List<string>>
($ table_name,$ columns_name_list)。 第一个有数据库表+列名列表,第二个有本地EF实体+属性
当然,你可以添加更多的检查数据types,顺便说一句,这将迫使你写疯狂复杂的代码。
损益
PS抱歉的压缩风格,我是一个lambda狂热
using (EFModelContext efmc = new EFModelContext("appConfigConnectionName")) { string schemaName = "dbo"; string sql = @"select o.name + '.' + c.name from sys.all_objects o inner join sys.schemas s on s.schema_id = o.schema_id inner join sys.all_columns c on c.object_id = o.object_id where Rtrim(Ltrim(o.type)) in ('U') and s.name = @p0"; Dictionary<string, List<string>> dbTableColumns = new Dictionary<string, List<string>>(); efmc.Database.SqlQuery<string>(sql, schemaName).Select(tc => { string[] splitted = System.Text.RegularExpressions.Regex.Split(tc, "[.]"); return new { TableName = splitted[0], ColumnName = splitted[1] }; }).GroupBy(k => k.TableName, k => k.ColumnName).ToList().ForEach(ig => dbTableColumns.Add(ig.Key, ig.ToList())); Dictionary<string, List<string>> efTableColumns = new Dictionary<string, List<string>>(); efTableColumns = ((IObjectContextAdapter)uc).ObjectContext.MetadataWorkspace .GetItems(DataSpace.SSpace).OfType<EntityType>() .ToDictionary( eft => eft.MetadataProperties .First(mp => mp.Name == "TableName").Value.ToString(), eft => eft.Properties.Select(p => p.Name).ToList()); }
亚历克斯是正确的 – 这是一个在元数据API的悲伤限制。 我只需要将MSL加载为XML文档,并在处理C空间模型时查看S空间实体。
使用EF5和一点点reflection,像下面这样的应该做的伎俩:
using System; using System.Collections; using System.Data.Entity.Infrastructure; using System.Data.Metadata.Edm; using System.Linq; using System.Reflection; namespace EFHelpers { public class EFMetadataMappingHelper { public static string GetTableName(MetadataWorkspace metadata, DbEntityEntry entry) { var entityType = entry.Entity.GetType(); var objectType = getObjectType(metadata, entityType); var conceptualSet = getConceptualSet(metadata, objectType); var storeSet = getStoreSet(metadata, conceptualSet); var tableName = findTableName(storeSet); return tableName; } private static EntitySet getStoreSet(MetadataWorkspace metadata, EntitySetBase entitySet) { var csSpace = metadata.GetItems(DataSpace.CSSpace).Single(); var flags = BindingFlags.NonPublic | BindingFlags.Instance; var entitySetMaps = (ICollection)csSpace.GetType().GetProperty("EntitySetMaps", flags).GetValue(csSpace, null); object mapping = null; foreach (var map in entitySetMaps) { var set = map.GetType().GetProperty("Set", flags).GetValue(map, null); if (entitySet == set) { mapping = map; break; } } var m_typeMappings = ((ICollection)mapping.GetType().BaseType.GetField("m_typeMappings", flags).GetValue(mapping)).OfType<object>().Single(); var m_fragments = ((ICollection)m_typeMappings.GetType().BaseType.GetField("m_fragments", flags).GetValue(m_typeMappings)).OfType<object>().Single(); var storeSet = (EntitySet) m_fragments.GetType().GetProperty("TableSet", flags).GetValue(m_fragments, null); return storeSet; } private static string findTableName(EntitySet storeSet) { string tableName = null; MetadataProperty tableProperty; storeSet.MetadataProperties.TryGetValue("Table", true, out tableProperty); if (tableProperty == null || tableProperty.Value == null) storeSet.MetadataProperties.TryGetValue("http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator:Table", true, out tableProperty); if (tableProperty != null) tableName = tableProperty.Value as string; if (tableName == null) tableName = storeSet.Name; return tableName; } private static EntityType getObjectType(MetadataWorkspace metadata, Type entityType) { var objectItemCollection = (ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace); var edmEntityType = metadata .GetItems<EntityType>(DataSpace.OSpace) .First(e => objectItemCollection.GetClrType(e) == entityType); return edmEntityType; } private static EntitySetBase getConceptualSet(MetadataWorkspace metadata, EntityType entityType) { var entitySetBase = metadata .GetItems<EntityContainer>(DataSpace.CSpace) .SelectMany(a => a.BaseEntitySets) .Where(s => s.ElementType.Name == entityType.Name) .FirstOrDefault(); return entitySetBase; } } }
调用它是这样的:
public string GetTableName(DbContext db, DbEntityEntry entry) { var metadata = ((IObjectContextAdapter)db).ObjectContext.MetadataWorkspace; return EFMetadataMappingHelper.GetTableName(metadata, entry); }
在这里复制我的答案到另一个问题。
如果还有人在看,我就是这样做的。 这是DBContext的扩展方法,它接受一个types并返回物理列名及其属性。
这利用对象上下文获取物理列列表,然后使用“PreferredName”元数据属性将每个列映射到其属性。
由于它使用了对象上下文,所以它启动了一个数据库连接,所以根据上下文的复杂性,第一次运行将会很慢。
public static IDictionary<String, PropertyInfo> GetTableColumns(this DbContext ctx, Type entityType) { ObjectContext octx = (ctx as IObjectContextAdapter).ObjectContext; EntityType storageEntityType = octx.MetadataWorkspace.GetItems(DataSpace.SSpace) .Where(x => x.BuiltInTypeKind == BuiltInTypeKind.EntityType).OfType<EntityType>() .Single(x => x.Name == entityType.Name); var columnNames = storageEntityType.Properties.ToDictionary(x => x.Name, y => y.MetadataProperties.FirstOrDefault(x => x.Name == "PreferredName")?.Value as string ?? y.Name); return storageEntityType.Properties.Select((elm, index) => new {elm.Name, Property = entityType.GetProperty(columnNames[elm.Name])}) .ToDictionary(x => x.Name, x => x.Property); }
要使用它,只需创build一个辅助静态类,并添加上面的函数; 那么就像调用一样简单
var tabCols = context.GetTableColumns(typeof(EntityType));
对于EF6,混合/压缩来自其他答案的代码(VB,对不起):
Public Function getDBTableName(data As myDataModel, ByVal entity As Object) As String Dim context = CType(data, IObjectContextAdapter).ObjectContext Dim sName As String = entity.GetType.BaseType.Name 'use BaseType to avoid proxy names' Dim map = context.MetadataWorkspace.GetItems(Of EntityContainerMapping)(DataSpace.CSSpace).FirstOrDefault Return (From esmap In map.EntitySetMappings Select esmap.EntityTypeMappings.First( Function(etm) etm.EntityType.Name = sName ).Fragments.First.StoreEntitySet.Name).FirstOrDefault 'TODO: use less .first everywhere but filter the correct ones' End Function
它适用于db-first。
在一个.edmx文件之后相对容易理解。