1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 3 using System; 4 using System.Collections.Generic; 5 using System.Data.Mapping; 6 using System.Data.Metadata.Edm; 7 using System.Data.Objects; 8 using System.Globalization; 9 using System.Linq; 10 using System.Reflection; 11 using System.Web.Http; 12 13 namespace Microsoft.Web.Http.Data.EntityFramework.Metadata 14 { 15 /// <summary> 16 /// EF metadata utilities class. 17 /// </summary> 18 internal static class MetadataWorkspaceUtilities 19 { 20 /// <summary> 21 /// Creates a metadata workspace for the specified context. 22 /// </summary> 23 /// <param name="contextType">The type of the object context.</param> 24 /// <param name="isDbContext">Set to <c>true</c> if context is a database context.</param> 25 /// <returns>The metadata workspace.</returns> CreateMetadataWorkspace(Type contextType, bool isDbContext)26 public static MetadataWorkspace CreateMetadataWorkspace(Type contextType, bool isDbContext) 27 { 28 MetadataWorkspace metadataWorkspace = null; 29 30 if (!isDbContext) 31 { 32 metadataWorkspace = MetadataWorkspaceUtilities.CreateMetadataWorkspaceFromResources(contextType, typeof(ObjectContext)); 33 } 34 else 35 { 36 metadataWorkspace = MetadataWorkspaceUtilities.CreateMetadataWorkspaceFromResources(contextType, typeof(System.Data.Entity.DbContext)); 37 if (metadataWorkspace == null && typeof(System.Data.Entity.DbContext).IsAssignableFrom(contextType)) 38 { 39 if (contextType.GetConstructor(Type.EmptyTypes) == null) 40 { 41 throw Error.InvalidOperation(Resource.DefaultCtorNotFound, contextType.FullName); 42 } 43 44 try 45 { 46 System.Data.Entity.DbContext dbContext = Activator.CreateInstance(contextType) as System.Data.Entity.DbContext; 47 ObjectContext objectContext = (dbContext as System.Data.Entity.Infrastructure.IObjectContextAdapter).ObjectContext; 48 metadataWorkspace = objectContext.MetadataWorkspace; 49 } 50 catch (Exception efException) 51 { 52 throw Error.InvalidOperation(efException, Resource.MetadataWorkspaceNotFound, contextType.FullName); 53 } 54 } 55 } 56 if (metadataWorkspace == null) 57 { 58 throw Error.InvalidOperation(Resource.LinqToEntitiesProvider_UnableToRetrieveMetadata, contextType.Name); 59 } 60 else 61 { 62 return metadataWorkspace; 63 } 64 } 65 66 /// <summary> 67 /// Creates the MetadataWorkspace for the given context type and base context type. 68 /// </summary> 69 /// <param name="contextType">The type of the context.</param> 70 /// <param name="baseContextType">The base context type (DbContext or ObjectContext).</param> 71 /// <returns>The generated <see cref="MetadataWorkspace"/></returns> CreateMetadataWorkspaceFromResources(Type contextType, Type baseContextType)72 public static MetadataWorkspace CreateMetadataWorkspaceFromResources(Type contextType, Type baseContextType) 73 { 74 // get the set of embedded mapping resources for the target assembly and create 75 // a metadata workspace info for each group 76 IEnumerable<string> metadataResourcePaths = FindMetadataResources(contextType.Assembly); 77 IEnumerable<MetadataWorkspaceInfo> workspaceInfos = GetMetadataWorkspaceInfos(metadataResourcePaths); 78 79 // Search for the correct EntityContainer by name and if found, create 80 // a comlete MetadataWorkspace and return it 81 foreach (var workspaceInfo in workspaceInfos) 82 { 83 EdmItemCollection edmItemCollection = new EdmItemCollection(workspaceInfo.Csdl); 84 85 Type currentType = contextType; 86 while (currentType != baseContextType && currentType != typeof(object)) 87 { 88 EntityContainer container; 89 if (edmItemCollection.TryGetEntityContainer(currentType.Name, out container)) 90 { 91 StoreItemCollection store = new StoreItemCollection(workspaceInfo.Ssdl); 92 StorageMappingItemCollection mapping = new StorageMappingItemCollection(edmItemCollection, store, workspaceInfo.Msl); 93 MetadataWorkspace workspace = new MetadataWorkspace(); 94 workspace.RegisterItemCollection(edmItemCollection); 95 workspace.RegisterItemCollection(store); 96 workspace.RegisterItemCollection(mapping); 97 workspace.RegisterItemCollection(new ObjectItemCollection()); 98 return workspace; 99 } 100 101 currentType = currentType.BaseType; 102 } 103 } 104 return null; 105 } 106 107 /// <summary> 108 /// Gets the specified resource paths as metadata workspace info objects. 109 /// </summary> 110 /// <param name="resourcePaths">The metadata resource paths.</param> 111 /// <returns>The metadata workspace info objects.</returns> GetMetadataWorkspaceInfos(IEnumerable<string> resourcePaths)112 private static IEnumerable<MetadataWorkspaceInfo> GetMetadataWorkspaceInfos(IEnumerable<string> resourcePaths) 113 { 114 // for file paths, you would want to group without the path or the extension like Path.GetFileNameWithoutExtension, but resource names can contain 115 // forbidden path chars, so don't use it on resource names 116 foreach (var group in resourcePaths.GroupBy(p => p.Substring(0, p.LastIndexOf('.')), StringComparer.InvariantCultureIgnoreCase)) 117 { 118 yield return MetadataWorkspaceInfo.Create(group); 119 } 120 } 121 122 /// <summary> 123 /// Find all the EF metadata resources. 124 /// </summary> 125 /// <param name="assembly">The assembly to find the metadata resources in.</param> 126 /// <returns>The metadata paths that were found.</returns> FindMetadataResources(Assembly assembly)127 private static IEnumerable<string> FindMetadataResources(Assembly assembly) 128 { 129 List<string> result = new List<string>(); 130 foreach (string name in assembly.GetManifestResourceNames()) 131 { 132 if (MetadataWorkspaceInfo.IsMetadata(name)) 133 { 134 result.Add(String.Format(CultureInfo.InvariantCulture, "res://{0}/{1}", assembly.FullName, name)); 135 } 136 } 137 138 return result; 139 } 140 141 /// <summary> 142 /// Represents the paths for a single metadata workspace. 143 /// </summary> 144 private class MetadataWorkspaceInfo 145 { 146 private const string CsdlExtension = ".csdl"; 147 private const string MslExtension = ".msl"; 148 private const string SsdlExtension = ".ssdl"; 149 MetadataWorkspaceInfo(string csdlPath, string mslPath, string ssdlPath)150 public MetadataWorkspaceInfo(string csdlPath, string mslPath, string ssdlPath) 151 { 152 if (csdlPath == null) 153 { 154 throw Error.ArgumentNull("csdlPath"); 155 } 156 157 if (mslPath == null) 158 { 159 throw Error.ArgumentNull("mslPath"); 160 } 161 162 if (ssdlPath == null) 163 { 164 throw Error.ArgumentNull("ssdlPath"); 165 } 166 167 Csdl = csdlPath; 168 Msl = mslPath; 169 Ssdl = ssdlPath; 170 } 171 172 public string Csdl { get; private set; } 173 174 public string Msl { get; private set; } 175 176 public string Ssdl { get; private set; } 177 Create(IEnumerable<string> paths)178 public static MetadataWorkspaceInfo Create(IEnumerable<string> paths) 179 { 180 string csdlPath = null; 181 string mslPath = null; 182 string ssdlPath = null; 183 foreach (string path in paths) 184 { 185 if (path.EndsWith(CsdlExtension, StringComparison.OrdinalIgnoreCase)) 186 { 187 csdlPath = path; 188 } 189 else if (path.EndsWith(MslExtension, StringComparison.OrdinalIgnoreCase)) 190 { 191 mslPath = path; 192 } 193 else if (path.EndsWith(SsdlExtension, StringComparison.OrdinalIgnoreCase)) 194 { 195 ssdlPath = path; 196 } 197 } 198 199 return new MetadataWorkspaceInfo(csdlPath, mslPath, ssdlPath); 200 } 201 IsMetadata(string path)202 public static bool IsMetadata(string path) 203 { 204 return path.EndsWith(CsdlExtension, StringComparison.OrdinalIgnoreCase) || 205 path.EndsWith(MslExtension, StringComparison.OrdinalIgnoreCase) || 206 path.EndsWith(SsdlExtension, StringComparison.OrdinalIgnoreCase); 207 } 208 } 209 } 210 } 211