2009-07-11 Michael Barker <mike@middlesoft.co.uk>
[mono.git] / mcs / class / System.Data.Linq / src / DbLinq / Data / Linq / Mapping / AttributedMetaModel.cs
1 #region MIT license\r
2 // \r
3 // MIT license\r
4 //\r
5 // Copyright (c) 2007-2008 Jiri Moudry, Stefan Klinger\r
6 // \r
7 // Permission is hereby granted, free of charge, to any person obtaining a copy\r
8 // of this software and associated documentation files (the "Software"), to deal\r
9 // in the Software without restriction, including without limitation the rights\r
10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
11 // copies of the Software, and to permit persons to whom the Software is\r
12 // furnished to do so, subject to the following conditions:\r
13 // \r
14 // The above copyright notice and this permission notice shall be included in\r
15 // all copies or substantial portions of the Software.\r
16 // \r
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
23 // THE SOFTWARE.\r
24 // \r
25 #endregion\r
26 \r
27 using System;\r
28 using System.Collections.Generic;\r
29 using System.Data.Linq.Mapping;\r
30 using System.Diagnostics;\r
31 using System.Linq;\r
32 using System.Reflection;\r
33 \r
34 #if MONO_STRICT\r
35 using System.Data.Linq;\r
36 #else\r
37 using DbLinq.Data.Linq;\r
38 #endif\r
39 \r
40 using DbLinq.Data.Linq.Mapping;\r
41 using DbLinq.Util;\r
42 \r
43 //Change notes:\r
44 //removed virtual init call from constructor\r
45 //renamed member variables to be better distinguishable from local variables\r
46 \r
47 namespace DbLinq.Data.Linq.Mapping\r
48 {\r
49     /// <summary>\r
50     /// This class is a stateless attribute meta model (it does not depend on any provider)\r
51     /// So the MappingSource can use singletons\r
52     /// </summary>\r
53     [DebuggerDisplay("MetaModel for {DatabaseName}")]\r
54     internal class AttributedMetaModel : MetaModel\r
55         {\r
56                 private readonly Type _ContextType;\r
57 \r
58                 /// <summary>\r
59                 /// The DataContext (or a derived type) that is used for this model.\r
60                 /// </summary>\r
61                 public override Type ContextType\r
62                 {\r
63                         get { return _ContextType; }\r
64                 }\r
65 \r
66 \r
67                 // just because of this, the whole model can not be cached efficiently, since we can not guarantee\r
68                 // that another mapping source instance will not use the same model\r
69                 private MappingSource _MappingSource;\r
70 \r
71                 /// <summary>\r
72                 /// The mapping source used for that model.\r
73                 /// </summary>\r
74                 public override MappingSource MappingSource\r
75                 {\r
76                         get { return _MappingSource; }\r
77                 }\r
78 \r
79 \r
80                 private string _DatabaseName;\r
81 \r
82                 /// <summary>\r
83                 /// Name of the database.\r
84                 /// </summary>\r
85                 /// <remarks>\r
86                 /// The name of the database is the type name of the DataContext inheriting class.\r
87                 /// If a plain DataContext is used, the database name is "DataContext".\r
88                 /// </remarks>\r
89                 public override string DatabaseName\r
90                 {\r
91                         get {\r
92                 if (_DatabaseName == null)\r
93                     DiscoverDatabaseName();\r
94                 return _DatabaseName;\r
95             }\r
96                 }\r
97 \r
98 \r
99                 //Currently not implemented Properties\r
100                 public override Type ProviderType\r
101                 {\r
102                         get { throw new NotImplementedException(); }\r
103                 }\r
104 \r
105                 //This function will try to add unknown table types\r
106                 private IDictionary<Type, MetaTable> _Tables = new Dictionary<Type, MetaTable>();\r
107 \r
108                 /// <summary>\r
109                 /// Initializes a new instance of the <see cref="AttributedMetaModel"/> class.\r
110                 /// </summary>\r
111                 /// <param name="contextType">DataContext type used.</param>\r
112                 /// <param name="mappingSource">The mapping source.</param>\r
113         public AttributedMetaModel(Type contextType, MappingSource mappingSource)\r
114         {\r
115             _ContextType = contextType;\r
116             _MappingSource = mappingSource;\r
117         }\r
118 \r
119                 /// <summary>\r
120                 /// Gets the <see cref="MetaFunction"/> for the given MethodInfo.\r
121                 /// </summary>\r
122                 /// <param name="method">The method info for which the <see cref="MetaFunction"/> should be returned.</param>\r
123         public override MetaFunction GetFunction(MethodInfo method)\r
124         {\r
125             return GetFunctions().SingleOrDefault(m => m.Method == method);\r
126         }\r
127 \r
128                 /// <summary>\r
129                 /// Returns an enumeration of all mapped functions.\r
130                 /// </summary>\r
131         public override IEnumerable<MetaFunction> GetFunctions()\r
132         {\r
133             const BindingFlags scope = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;\r
134             foreach (var methodInfo in _ContextType.GetMethods(scope))\r
135             {\r
136                 var function = methodInfo.GetAttribute<FunctionAttribute>();\r
137                 if (function != null)\r
138                     yield return new AttributedMetaFunction(methodInfo, function);\r
139             }\r
140         }\r
141 \r
142         public override MetaType GetMetaType(Type type)\r
143         {\r
144             var metaTable = GetTable(type);\r
145             if (metaTable == null)\r
146                 return null;\r
147             return metaTable.RowType;\r
148         }\r
149 \r
150                 /// <summary>\r
151                 /// Returns the <see cref="MetaTable"/> for the given table type.\r
152                 /// </summary>\r
153                 /// <remarks>\r
154                 /// If the given type is not allready mapped it tries to map it.\r
155                 /// </remarks>\r
156                 /// <param name="tableType"><see cref="MetaTable"/> for the table type or null if not mappable.</param>\r
157                 public override MetaTable GetTable(Type tableType)\r
158                 {\r
159                         MetaTable metaTable;\r
160                         _Tables.TryGetValue(tableType, out metaTable);\r
161                         if (metaTable != null)\r
162                         {\r
163                                 return metaTable;\r
164                         }\r
165                         return GetTables().FirstOrDefault(t => t.RowType.Type == tableType)\r
166                                 ?? AddTableType(tableType);\r
167                 }\r
168 \r
169                 /// <summary>\r
170                 /// Returns an enumeration of all mapped tables.\r
171                 /// </summary>\r
172         //Discover all the tables used with this context, used for the GetTable/GetTables function\r
173         //Behaviour of GetTables in the Framework: STRANGE\r
174         //If the DataContext was a strong typed one (derived with fields for the tables),\r
175         //it returns a list of MetaTables for all this tables.\r
176         //But if you call GetTable<T> with an additional table - the table doesn't get added to this list.\r
177         //If you use a vanilla DataContext the list is empty at the beginning (ok no surprise here),\r
178         //if you call GetTable<T> here the table is added to the list.\r
179         //\r
180         //If you add to properties with the same T of Table<T> only the first gets into the list.\r
181         public override IEnumerable<MetaTable> GetTables()\r
182         {\r
183             const BindingFlags scope = BindingFlags.GetField |\r
184                 BindingFlags.GetProperty | BindingFlags.Static |\r
185                 BindingFlags.Instance | BindingFlags.NonPublic |\r
186                 BindingFlags.Public;\r
187             var seen = new HashSet<Type>();\r
188             foreach (var info in _ContextType.GetMembers(scope))\r
189             {\r
190                 // Only look for Fields & Properties.\r
191                 if (info.MemberType != MemberTypes.Field && info.MemberType != MemberTypes.Property)\r
192                     continue;\r
193                 Type memberType = info.GetMemberType();\r
194 \r
195                 if (memberType == null || !memberType.IsGenericType ||\r
196                         memberType.GetGenericTypeDefinition() != typeof(Table<>))\r
197                     continue;\r
198                 var tableType = memberType.GetGenericArguments()[0];\r
199                 if (tableType.IsGenericParameter)\r
200                     continue;\r
201                 if (seen.Contains(tableType))\r
202                     continue;\r
203                 seen.Add(tableType);\r
204 \r
205                 MetaTable metaTable;\r
206                 if (_Tables.TryGetValue(tableType, out metaTable))\r
207                   yield return metaTable;\r
208                 else\r
209                   yield return AddTableType(tableType);\r
210             }\r
211         }\r
212 \r
213                 /// <summary>\r
214                 /// Tries to discover the name of the database.\r
215                 /// Database name == class name of the DataContext's most derived class used for this MetaModel.\r
216                 /// </summary>\r
217                 private void DiscoverDatabaseName()\r
218                 {\r
219                         var databaseAttribute = _ContextType.GetAttribute<DatabaseAttribute>();\r
220                         if (databaseAttribute != null)\r
221                         {\r
222                                 _DatabaseName = databaseAttribute.Name;\r
223                         }\r
224                         else //Found no DatabaseAttribute get the class name\r
225                         {\r
226                                 _DatabaseName = _ContextType.Name;\r
227                         }\r
228                 }\r
229 \r
230                 /// <summary>\r
231                 /// Adds the table of the given type to the mappings.\r
232                 /// </summary>\r
233                 /// <remarks>\r
234                 /// The given type must have a <see cref="TableAttribute" /> to be mappable.\r
235                 /// </remarks>\r
236                 /// <param name="tableType">Type of the table.</param>\r
237                 /// <returns>\r
238                 /// Returns the <see cref="MetaTable"/> for the given table type or null if it is not mappable.\r
239                 /// </returns>\r
240                 private MetaTable AddTableType(Type tableType)\r
241                 {\r
242                         //No need to check base types because framework implementation doesn't do this either\r
243                         var tableAttribute = tableType.GetAttribute<TableAttribute>();\r
244 \r
245                         if (tableAttribute == null)\r
246                         {\r
247                                 return null;\r
248                         }\r
249 \r
250                         //First set up the table without associations\r
251                         var metaType = new AttributedMetaType(tableType);\r
252                         var metaTable = new AttributedMetaTable(tableAttribute, metaType, this);\r
253                         metaType.SetMetaTable(metaTable);\r
254                         _Tables[tableType] = metaTable;\r
255 \r
256                         return metaTable;\r
257                 }\r
258         }\r
259 }\r