2009-07-11 Michael Barker <mike@middlesoft.co.uk>
[mono.git] / mcs / class / System.Data.Linq / src / DbLinq / Data / Linq / Sugar / Implementation / DataMapper.cs
1 #region MIT license\r
2 // \r
3 // MIT license\r
4 //\r
5 // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne\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.Collections.ObjectModel;\r
30 using System.Data.Linq.Mapping;\r
31 using System.Linq;\r
32 using System.Reflection;\r
33 \r
34 using DbLinq.Data.Linq.Sugar.Expressions;\r
35 \r
36 #if MONO_STRICT\r
37 using System.Data.Linq;\r
38 #else\r
39 using DbLinq.Data.Linq;\r
40 #endif\r
41 \r
42 namespace DbLinq.Data.Linq.Sugar.Implementation\r
43 {\r
44     internal class DataMapper : IDataMapper\r
45     {\r
46         /// <summary>\r
47         /// Returns a table given a type, or null if the type is not mapped\r
48         /// </summary>\r
49         /// <param name="tableType"></param>\r
50         /// <param name="dataContext"></param>\r
51         /// <returns></returns>\r
52         public virtual string GetTableName(Type tableType, DataContext dataContext)\r
53         {\r
54             var tableDescription = dataContext.Mapping.GetTable(tableType);\r
55             if (tableDescription != null)\r
56                 return tableDescription.TableName;\r
57             return null;\r
58         }\r
59 \r
60         public virtual string GetColumnName(TableExpression tableExpression, MemberInfo memberInfo, DataContext dataContext)\r
61         {\r
62             return GetColumnName(tableExpression.Type, memberInfo, dataContext);\r
63         }\r
64 \r
65         public virtual string GetColumnName(Type tableType, MemberInfo memberInfo, DataContext dataContext)\r
66         {\r
67             var tableDescription = dataContext.Mapping.GetTable(tableType);\r
68             var columnDescription = tableDescription.RowType.GetDataMember(memberInfo);\r
69             if (columnDescription != null)\r
70                 return columnDescription.MappedName;\r
71             return null;\r
72         }\r
73 \r
74         public virtual IList<MemberInfo> GetPrimaryKeys(TableExpression tableExpression, DataContext dataContext)\r
75         {\r
76             var tableDescription = dataContext.Mapping.GetTable(tableExpression.Type);\r
77             if (tableDescription != null)\r
78                 return GetPrimaryKeys(tableDescription);\r
79             return null;\r
80         }\r
81 \r
82         public virtual IList<MemberInfo> GetPrimaryKeys(MetaTable tableDescription)\r
83         {\r
84             return (from column in tableDescription.RowType.IdentityMembers select column.Member).ToList();\r
85         }\r
86 \r
87         /// <summary>\r
88         /// Lists table mapped columns\r
89         /// </summary>\r
90         /// <param name="tableDescription"></param>\r
91         /// <returns></returns>\r
92         public IList<MemberInfo> GetColumns(MetaTable tableDescription)\r
93         {\r
94             return (from column in tableDescription.RowType.PersistentDataMembers select column.Member).ToList();\r
95         }\r
96 \r
97         /// <summary>\r
98         /// Returns association definition, if any\r
99         /// </summary>\r
100         /// <param name="thisTableExpression">The table referenced by the assocation (the type holding the member)</param>\r
101         /// <param name="memberInfo">The memberInfo related to association</param>\r
102         /// <param name="otherType"></param>\r
103         /// <param name="otherKey">The keys in the associated table</param>\r
104         /// <param name="joinType"></param>\r
105         /// <param name="joinID"></param>\r
106         /// <param name="dataContext"></param>\r
107         /// <returns>ThisKey</returns>\r
108         public virtual IList<MemberInfo> GetAssociation(TableExpression thisTableExpression, MemberInfo memberInfo, Type otherType, out IList<MemberInfo> otherKey, out TableJoinType joinType, out string joinID, DataContext dataContext)\r
109         {\r
110             var thisTableDescription = dataContext.Mapping.GetTable(thisTableExpression.Type);\r
111             var thisAssociation =\r
112                 (from association in thisTableDescription.RowType.Associations\r
113                  where association.ThisMember.Member == memberInfo\r
114                  select association).SingleOrDefault();\r
115             if (thisAssociation != null)\r
116             {\r
117                 // by default, join is inner\r
118                 joinType = TableJoinType.Inner;\r
119                 joinID = thisAssociation.ThisMember.MappedName;\r
120                 if (string.IsNullOrEmpty(joinID))\r
121                     throw Error.BadArgument("S0108: Association name is required to ensure join uniqueness");\r
122 \r
123                 var otherTableDescription = dataContext.Mapping.GetTable(otherType);\r
124                 bool thisKeyHasNullables, otherKeyHasNullables;\r
125                 var thisKey = GetAssociationKeys(thisTableDescription, thisAssociation.ThisKey, dataContext,\r
126                                                  out thisKeyHasNullables);\r
127                 otherKey = GetAssociationKeys(otherTableDescription, thisAssociation.OtherKey, dataContext,\r
128                                               out otherKeyHasNullables);\r
129 \r
130                 // we just test here the left join (since associations are symmetric,\r
131                 //        we can only find left joins here, and the otherKeyHasNullables is\r
132                 //        always equal to thisKeyHasNullables)\r
133                 if (thisKeyHasNullables)\r
134                     joinType |= TableJoinType.LeftOuter;\r
135 \r
136                 return thisKey;\r
137             }\r
138             otherKey = null;\r
139             joinType = TableJoinType.Default;\r
140             joinID = null;\r
141             return null;\r
142         }\r
143 \r
144         /// <summary>\r
145         /// Enumerates Keys for a given table.\r
146         /// Keys can be provided as input. If none provided, PKs are taken from table\r
147         /// </summary>\r
148         /// <param name="description"></param>\r
149         /// <param name="keys">Keys to be used, leave empty to use PKs instead</param>\r
150         /// <param name="dataContext"></param>\r
151         /// <param name="hasNullableKeys">returned as true if some keys can be null (we then have an outer join)</param>\r
152         /// <returns></returns>\r
153         protected virtual IList<MemberInfo> GetAssociationKeys(MetaTable description, ReadOnlyCollection<MetaDataMember> keys,\r
154                                                                DataContext dataContext, out bool hasNullableKeys)\r
155         {\r
156             var sourceKeys = keys;\r
157             if (sourceKeys.Count == 0)\r
158                 sourceKeys = description.RowType.IdentityMembers;\r
159 \r
160             hasNullableKeys = false;\r
161             var members = new List<MemberInfo>();\r
162             foreach (var sourceKey in sourceKeys)\r
163             {\r
164                 members.Add(sourceKey.Member);\r
165                 if (sourceKey.CanBeNull)\r
166                     hasNullableKeys = true;\r
167             }\r
168             return members;\r
169         }\r
170 \r
171         public IList<MemberInfo> GetEntitySetAssociations(Type type)\r
172         {\r
173             // BUG: This is ignoring External Mappings from XmlMappingSource.\r
174 \r
175             // TODO: Should be cached in a static thread safe cache.\r
176 \r
177             return type.GetProperties()\r
178                 .Where(p => p.PropertyType.IsGenericType \r
179                     && (p.PropertyType.GetGenericTypeDefinition() == typeof(System.Data.Linq.EntitySet<>) \r
180 #if !MONO_STRICT\r
181                     || p.PropertyType.GetGenericTypeDefinition() == typeof(DbLinq.Data.Linq.EntitySet<>)\r
182 #endif\r
183                     )\r
184                     && p.IsDefined(typeof(AssociationAttribute), true))\r
185                 .Cast<MemberInfo>().ToList();\r
186         }\r
187 \r
188         public IList<MemberInfo> GetEntityRefAssociations(Type type)\r
189         {\r
190             // BUG: This is ignoring External Mappings from XmlMappingSource.\r
191 \r
192             // TODO: Should be cached in a static thread safe cache.\r
193 \r
194             List<MemberInfo> associations = new List<MemberInfo>();\r
195             foreach (var p in type.GetProperties())\r
196             {\r
197                 AssociationAttribute associationAttribute = p.GetCustomAttributes(typeof(AssociationAttribute), true).FirstOrDefault() as AssociationAttribute;\r
198                 if (associationAttribute != null)\r
199                 {\r
200                     FieldInfo field = type.GetField(associationAttribute.Storage, BindingFlags.NonPublic | BindingFlags.Instance);\r
201                     if (field != null && field.FieldType.IsGenericType &&\r
202 #if MONO_STRICT\r
203                         field.FieldType.GetGenericTypeDefinition() == typeof(System.Data.Linq.EntityRef<>)\r
204 #else\r
205                         field.FieldType.GetGenericTypeDefinition() == typeof(DbLinq.Data.Linq.EntityRef<>)\r
206 #endif\r
207                         )\r
208                         associations.Add(p);\r
209                 }\r
210             }\r
211             return associations;\r
212         }\r
213     }\r
214 }\r