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