* src/**/*: Flush; syncs to DbLinq r1053. Adds
[mono.git] / mcs / class / System.Data.Linq / src / DbLinq / Data / Linq / DataContext.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;\r
29 using System.Data;\r
30 using System.Data.Common;\r
31 using System.Data.Linq.Mapping;\r
32 using System.Collections.Generic;\r
33 using System.IO;\r
34 using System.Linq;\r
35 using System.Reflection;\r
36 \r
37 #if MONO_STRICT\r
38 using System.Data.Linq.Implementation;\r
39 using System.Data.Linq.Sugar;\r
40 using System.Data.Linq.Identity;\r
41 using DbLinq.Util;\r
42 using AttributeMappingSource = System.Data.Linq.Mapping.AttributeMappingSource;\r
43 using MappingContext = System.Data.Linq.Mapping.MappingContext;\r
44 using DbLinq;\r
45 #else\r
46 using DbLinq.Data.Linq.Implementation;\r
47 using DbLinq.Data.Linq.Sugar;\r
48 using DbLinq.Data.Linq.Identity;\r
49 using DbLinq.Util;\r
50 using AttributeMappingSource = DbLinq.Data.Linq.Mapping.AttributeMappingSource;\r
51 using MappingContext = DbLinq.Data.Linq.Mapping.MappingContext;\r
52 using System.Data.Linq;\r
53 #endif\r
54 \r
55 using DbLinq.Factory;\r
56 using DbLinq.Vendor;\r
57 using DbLinq.Data.Linq.Database;\r
58 using DbLinq.Data.Linq.Database.Implementation;\r
59 using System.Linq.Expressions;\r
60 using System.Reflection.Emit;\r
61 \r
62 #if MONO_STRICT\r
63 namespace System.Data.Linq\r
64 #else\r
65 namespace DbLinq.Data.Linq\r
66 #endif\r
67 {\r
68     public partial class DataContext : IDisposable\r
69     {\r
70         //private readonly Dictionary<string, ITable> _tableMap = new Dictionary<string, ITable>();\r
71                 private readonly Dictionary<Type, ITable> _tableMap = new Dictionary<Type, ITable>();\r
72 \r
73         public MetaModel Mapping { get; private set; }\r
74         // PC question: at ctor, we get a IDbConnection and the Connection property exposes a DbConnection\r
75         //              WTF?\r
76         public DbConnection Connection { get { return DatabaseContext.Connection as DbConnection; } }\r
77 \r
78         // all properties below are set public to optionally be injected\r
79         internal IVendor Vendor { get; set; }\r
80         internal IQueryBuilder QueryBuilder { get; set; }\r
81         internal IQueryRunner QueryRunner { get; set; }\r
82         internal IMemberModificationHandler MemberModificationHandler { get; set; }\r
83         internal IDatabaseContext DatabaseContext { get; private set; }\r
84         // /all properties...\r
85 \r
86         private bool objectTrackingEnabled = true;\r
87         private bool deferredLoadingEnabled = true;\r
88 \r
89         private IEntityTracker entityTracker;\r
90         private IEntityTracker EntityTracker\r
91         {\r
92             get\r
93             {\r
94                 if (this.entityTracker == null)\r
95                 {\r
96                     if (this.ObjectTrackingEnabled)\r
97                         this.entityTracker = new EntityTracker();\r
98                     else\r
99                         this.entityTracker = new DisabledEntityTracker();\r
100                 }\r
101                 return this.entityTracker;\r
102             }\r
103         }\r
104 \r
105 \r
106 \r
107         private IIdentityReaderFactory identityReaderFactory;\r
108         private readonly IDictionary<Type, IIdentityReader> identityReaders = new Dictionary<Type, IIdentityReader>();\r
109 \r
110         /// <summary>\r
111         /// The default behavior creates one MappingContext.\r
112         /// </summary>\r
113         [DBLinqExtended]\r
114         internal virtual MappingContext _MappingContext { get; set; }\r
115 \r
116         [DBLinqExtended]\r
117         internal IVendorProvider _VendorProvider { get; set; }\r
118 \r
119         public DataContext(IDbConnection connection, MappingSource mapping)\r
120         {\r
121             Init(new DatabaseContext(connection), mapping, null);\r
122         }\r
123 \r
124         public DataContext(IDbConnection connection)\r
125         {\r
126             if (connection == null)\r
127                 throw new ArgumentNullException("connection");\r
128 \r
129             Init(new DatabaseContext(connection), null, null);\r
130         }\r
131 \r
132         [DbLinqToDo]\r
133         public DataContext(string fileOrServerOrConnection, MappingSource mapping)\r
134         {\r
135             if (fileOrServerOrConnection == null)\r
136                 throw new ArgumentNullException("fileOrServerOrConnection");\r
137             if (mapping == null)\r
138                 throw new ArgumentNullException("mapping");\r
139 \r
140             if (File.Exists(fileOrServerOrConnection))\r
141                 throw new NotImplementedException("File names not supported.");\r
142 \r
143             // Is this a decent server name check?\r
144             // It assumes that the connection string will have at least 2\r
145             // parameters (separated by ';')\r
146             if (!fileOrServerOrConnection.Contains(";"))\r
147                 throw new NotImplementedException("Server name not supported.");\r
148 \r
149             // Assume it's a connection string...\r
150             IVendor ivendor = GetVendor(fileOrServerOrConnection);\r
151 \r
152             IDbConnection dbConnection = ivendor.CreateDbConnection(fileOrServerOrConnection);\r
153             Init(new DatabaseContext(dbConnection), mapping, ivendor);\r
154         }\r
155 \r
156         /// <summary>\r
157         /// Construct DataContext, given a connectionString.\r
158         /// To determine which DB type to go against, we look for 'DbLinqProvider=xxx' substring.\r
159         /// If not found, we assume that we are dealing with MS Sql Server.\r
160         /// \r
161         /// Valid values are names of provider DLLs (or any other DLL containing an IVendor implementation)\r
162         /// DbLinqProvider=Mysql\r
163         /// DbLinqProvider=Oracle etc.\r
164         /// </summary>\r
165         /// <param name="connectionString">specifies file or server connection</param>\r
166         [DbLinqToDo]\r
167         public DataContext(string connectionString)\r
168         {\r
169             IVendor ivendor = GetVendor(connectionString);\r
170 \r
171             IDbConnection dbConnection = ivendor.CreateDbConnection(connectionString);\r
172             Init(new DatabaseContext(dbConnection), null, ivendor);\r
173 \r
174         }\r
175 \r
176         private IVendor GetVendor(string connectionString)\r
177         {\r
178             if (connectionString == null)\r
179                 throw new ArgumentNullException("connectionString");\r
180 \r
181             Assembly assy;\r
182             string vendorClassToLoad;\r
183             GetVendorInfo(connectionString, out assy, out vendorClassToLoad);\r
184 \r
185             var types =\r
186                 from type in assy.GetTypes()\r
187                 where type.Name.ToLowerInvariant() == vendorClassToLoad.ToLowerInvariant() &&\r
188                     type.GetInterfaces().Contains(typeof(IVendor)) &&\r
189                     type.GetConstructor(Type.EmptyTypes) != null\r
190                 select type;\r
191             if (!types.Any())\r
192             {\r
193                 throw new ArgumentException(string.Format("Found no IVendor class in assembly `{0}' named `{1}' having a default constructor.",\r
194                     assy.GetName().Name, vendorClassToLoad));\r
195             }\r
196             else if (types.Count() > 1)\r
197             {\r
198                 throw new ArgumentException(string.Format("Found too many IVendor classes in assembly `{0}' named `{1}' having a default constructor.",\r
199                     assy.GetName().Name, vendorClassToLoad));\r
200             }\r
201             return (IVendor) Activator.CreateInstance(types.First());\r
202         }\r
203 \r
204         private void GetVendorInfo(string connectionString, out Assembly assembly, out string typeName)\r
205         {\r
206             System.Text.RegularExpressions.Regex reProvider\r
207                 = new System.Text.RegularExpressions.Regex(@"DbLinqProvider=([\w\.]+)");\r
208 \r
209             string assemblyFile = null;\r
210             string vendor;\r
211             if (!reProvider.IsMatch(connectionString))\r
212             {\r
213                 vendor       = "SqlServer";\r
214                 assemblyFile = "DbLinq.SqlServer.dll";\r
215             }\r
216             else\r
217             {\r
218                 var match    = reProvider.Match(connectionString);\r
219                 vendor       = match.Groups[1].Value;\r
220                 assemblyFile = "DbLinq." + vendor + ".dll";\r
221 \r
222                 //plain DbLinq - non MONO: \r
223                 //IVendor classes are in DLLs such as "DbLinq.MySql.dll"\r
224                 if (vendor.Contains("."))\r
225                 {\r
226                     //already fully qualified DLL name?\r
227                     throw new ArgumentException("Please provide a short name, such as 'MySql', not '" + vendor + "'");\r
228                 }\r
229 \r
230                 //shorten: "DbLinqProvider=X;Server=Y" -> ";Server=Y"\r
231                 connectionString = reProvider.Replace(connectionString, "");\r
232             }\r
233 \r
234             typeName = vendor + "Vendor";\r
235 \r
236             try\r
237             {\r
238 #if MONO_STRICT\r
239                 assembly = typeof (DataContext).Assembly; // System.Data.Linq.dll\r
240 #else\r
241                 //TODO: check if DLL is already loaded?\r
242                 assembly = Assembly.LoadFrom(assemblyFile);\r
243 #endif\r
244             }\r
245             catch (Exception e)\r
246             {\r
247                 throw new ArgumentException(\r
248                         string.Format(\r
249                             "Unable to load the `{0}' DbLinq vendor within assembly `{1}'.",\r
250                             assemblyFile, vendor),\r
251                         "connectionString", e);\r
252             }\r
253         }\r
254 \r
255         private void Init(IDatabaseContext databaseContext, MappingSource mappingSource, IVendor vendor)\r
256         {\r
257             if (databaseContext == null)\r
258                 throw new ArgumentNullException("databaseContext");\r
259 \r
260             // Yes, .NET throws an NRE for this.  Why it's not ArgumentNullException, I couldn't tell you.\r
261             if (databaseContext.Connection.ConnectionString == null)\r
262                 throw new NullReferenceException();\r
263 \r
264             _VendorProvider = ObjectFactory.Get<IVendorProvider>();\r
265             Vendor = vendor ?? \r
266                 (databaseContext.Connection.ConnectionString != null\r
267                     ? GetVendor(databaseContext.Connection.ConnectionString)\r
268                     : null) ??\r
269                 _VendorProvider.FindVendorByProviderType(typeof(SqlClient.Sql2005Provider));\r
270 \r
271             DatabaseContext = databaseContext;\r
272 \r
273             MemberModificationHandler = ObjectFactory.Create<IMemberModificationHandler>(); // not a singleton: object is stateful\r
274             QueryBuilder = ObjectFactory.Get<IQueryBuilder>();\r
275             QueryRunner = ObjectFactory.Get<IQueryRunner>();\r
276 \r
277             //EntityMap = ObjectFactory.Create<IEntityMap>();\r
278             identityReaderFactory = ObjectFactory.Get<IIdentityReaderFactory>();\r
279 \r
280             _MappingContext = new MappingContext();\r
281 \r
282             // initialize the mapping information\r
283             if (mappingSource == null)\r
284                 mappingSource = new AttributeMappingSource();\r
285             Mapping = mappingSource.GetModel(GetType());\r
286         }\r
287 \r
288                 /// <summary>\r
289                 /// Checks if the table is allready mapped or maps it if not.\r
290                 /// </summary>\r
291                 /// <param name="tableType">Type of the table.</param>\r
292                 /// <exception cref="InvalidOperationException">Thrown if the table is not mappable.</exception>\r
293                 private void CheckTableMapping(Type tableType)\r
294                 {\r
295                         //This will throw an exception if the table is not found\r
296                         if(Mapping.GetTable(tableType) == null)\r
297                         {\r
298                                 throw new InvalidOperationException("The type '" + tableType.Name + "' is not mapped as a Table.");\r
299                         }\r
300                 }\r
301 \r
302                 /// <summary>\r
303                 /// Returns a Table for the type TEntity.\r
304                 /// </summary>\r
305                 /// <exception cref="InvalidOperationException">If the type TEntity is not mappable as a Table.</exception>\r
306                 /// <typeparam name="TEntity">The table type.</typeparam>\r
307         public Table<TEntity> GetTable<TEntity>() where TEntity : class\r
308         {\r
309             return (Table<TEntity>)GetTable(typeof(TEntity));\r
310         }\r
311 \r
312                 /// <summary>\r
313                 /// Returns a Table for the given type.\r
314                 /// </summary>\r
315                 /// <param name="type">The table type.</param>\r
316                 /// <exception cref="InvalidOperationException">If the type is not mappable as a Table.</exception>\r
317         public ITable GetTable(Type type)\r
318         {\r
319             lock (_tableMap)\r
320             {\r
321                 ITable tableExisting;\r
322                                 if (_tableMap.TryGetValue(type, out tableExisting))\r
323                     return tableExisting;\r
324 \r
325                                 //Check for table mapping\r
326                                 CheckTableMapping(type);\r
327 \r
328                 var tableNew = Activator.CreateInstance(\r
329                                   typeof(Table<>).MakeGenericType(type)\r
330                                   , BindingFlags.NonPublic | BindingFlags.Instance\r
331                                   , null\r
332                                   , new object[] { this }\r
333                                   , System.Globalization.CultureInfo.CurrentCulture) as ITable;\r
334 \r
335                 _tableMap[type] = tableNew;\r
336                 return tableNew;\r
337             }\r
338         }\r
339 \r
340         public void SubmitChanges()\r
341         {\r
342             SubmitChanges(ConflictMode.FailOnFirstConflict);\r
343         }\r
344 \r
345         /// <summary>\r
346         /// Pings database\r
347         /// </summary>\r
348         /// <returns></returns>\r
349         public bool DatabaseExists()\r
350         {\r
351             try\r
352             {\r
353                 return Vendor.Ping(this);\r
354             }\r
355             catch (Exception)\r
356             {\r
357                 return false;\r
358             }\r
359         }\r
360 \r
361         /// <summary>\r
362         /// Commits all pending changes to database \r
363         /// </summary>\r
364         /// <param name="failureMode"></param>\r
365         public virtual void SubmitChanges(ConflictMode failureMode)\r
366         {\r
367             if (this.objectTrackingEnabled == false)\r
368                 throw new InvalidOperationException("Object tracking is not enabled for the current data context instance.");\r
369             using (DatabaseContext.OpenConnection()) //ConnMgr will close connection for us\r
370             using (IDatabaseTransaction transaction = DatabaseContext.Transaction())\r
371             {\r
372                 var queryContext = new QueryContext(this);\r
373                 var entityTracks = EntityTracker.EnumerateAll().ToList();\r
374                 foreach (var entityTrack in entityTracks)\r
375                 {\r
376                     switch (entityTrack.EntityState)\r
377                     {\r
378                         case EntityState.ToInsert:\r
379                             var insertQuery = QueryBuilder.GetInsertQuery(entityTrack.Entity, queryContext);\r
380                             QueryRunner.Insert(entityTrack.Entity, insertQuery);\r
381                             Register(entityTrack.Entity);\r
382                             break;\r
383                         case EntityState.ToWatch:\r
384                             if (MemberModificationHandler.IsModified(entityTrack.Entity, Mapping))\r
385                             {\r
386                                 var modifiedMembers = MemberModificationHandler.GetModifiedProperties(entityTrack.Entity, Mapping);\r
387                                 var updateQuery = QueryBuilder.GetUpdateQuery(entityTrack.Entity, modifiedMembers, queryContext);\r
388                                 QueryRunner.Update(entityTrack.Entity, updateQuery, modifiedMembers);\r
389 \r
390                                 RegisterUpdateAgain(entityTrack.Entity);\r
391                             }\r
392                             break;\r
393                         case EntityState.ToDelete:\r
394                             var deleteQuery = QueryBuilder.GetDeleteQuery(entityTrack.Entity, queryContext);\r
395                             QueryRunner.Delete(entityTrack.Entity, deleteQuery);\r
396 \r
397                             UnregisterDelete(entityTrack.Entity);\r
398                             break;\r
399                         default:\r
400                             throw new ArgumentOutOfRangeException();\r
401                     }\r
402                 }\r
403                 // TODO: handle conflicts (which can only occur when concurrency mode is implemented)\r
404                 transaction.Commit();\r
405             }\r
406         }\r
407 \r
408         /// <summary>\r
409         /// TODO - allow generated methods to call into stored procedures\r
410         /// </summary>\r
411         [DBLinqExtended]\r
412         internal IExecuteResult _ExecuteMethodCall(DataContext context, System.Reflection.MethodInfo method, params object[] sqlParams)\r
413         {\r
414             using (DatabaseContext.OpenConnection())\r
415             {\r
416                 System.Data.Linq.IExecuteResult result = Vendor.ExecuteMethodCall(context, method, sqlParams);\r
417                 return result;\r
418             }\r
419         }\r
420 \r
421         [DbLinqToDo]\r
422         protected IExecuteResult ExecuteMethodCall(object instance, System.Reflection.MethodInfo methodInfo, params object[] parameters)\r
423         {\r
424             throw new NotImplementedException();\r
425         }\r
426 \r
427         #region Identity management\r
428 \r
429         [DBLinqExtended]\r
430         internal IIdentityReader _GetIdentityReader(Type t)\r
431         {\r
432             IIdentityReader identityReader;\r
433             lock (identityReaders)\r
434             {\r
435                 if (!identityReaders.TryGetValue(t, out identityReader))\r
436                 {\r
437                     identityReader = identityReaderFactory.GetReader(t, this);\r
438                     identityReaders[t] = identityReader;\r
439                 }\r
440             }\r
441             return identityReader;\r
442         }\r
443 \r
444         [DBLinqExtended]\r
445         internal object _GetRegisteredEntity(object entity)\r
446         {\r
447             // TODO: check what is faster: by identity or by ref\r
448             var identityReader = _GetIdentityReader(entity.GetType());\r
449             var identityKey = identityReader.GetIdentityKey(entity);\r
450             if (identityKey == null) // if we don't have an entitykey here, it means that the entity has no PK\r
451                 return entity;\r
452             // even \r
453             var registeredEntityTrack = EntityTracker.FindByIdentity(identityKey);\r
454             if (registeredEntityTrack != null)\r
455                 return registeredEntityTrack.Entity;\r
456             return null;\r
457         }\r
458 \r
459         //internal object GetRegisteredEntityByKey(IdentityKey identityKey)\r
460         //{\r
461         //    return EntityMap[identityKey];\r
462         //}\r
463 \r
464         /// <summary>\r
465         /// Registers an entity in a watch state\r
466         /// </summary>\r
467         /// <param name="entity"></param>\r
468         /// <returns></returns>\r
469         [DBLinqExtended]\r
470         internal object _GetOrRegisterEntity(object entity)\r
471         {\r
472             var identityReader = _GetIdentityReader(entity.GetType());\r
473             var identityKey = identityReader.GetIdentityKey(entity);\r
474             SetEntitySetsQueries(entity);\r
475             SetEntityRefQueries(entity);\r
476 \r
477             // if we have no identity, we can't track it\r
478             if (identityKey == null)\r
479                 return entity;\r
480 \r
481             // try to find an already registered entity and return it\r
482             var registeredEntityTrack = EntityTracker.FindByIdentity(identityKey);\r
483             if (registeredEntityTrack != null)\r
484                 return registeredEntityTrack.Entity;\r
485 \r
486             // otherwise, register and return\r
487             EntityTracker.RegisterToWatch(entity, identityKey);\r
488             return entity;\r
489         }\r
490 \r
491         readonly IDataMapper DataMapper = ObjectFactory.Get<IDataMapper>();\r
492         private void SetEntityRefQueries(object entity)\r
493         {\r
494             if (!this.deferredLoadingEnabled)\r
495                 return;\r
496 \r
497             // BUG: This is ignoring External Mappings from XmlMappingSource.\r
498 \r
499             Type thisType = entity.GetType();\r
500             IList<MemberInfo> properties = DataMapper.GetEntityRefAssociations(thisType);\r
501 \r
502 \r
503             foreach (PropertyInfo prop in properties)\r
504             {\r
505                 //example of entityRef:Order.Employee\r
506                 AssociationAttribute associationInfo = prop.GetAttribute<AssociationAttribute>();\r
507                 Type otherTableType = prop.PropertyType;\r
508                 IList<MemberInfo> otherPKs = DataMapper.GetPrimaryKeys(Mapping.GetTable(otherTableType));\r
509 \r
510                 if (otherPKs.Count > 1)\r
511                     throw new NotSupportedException("Multiple keys object not supported yet.");\r
512 \r
513                 var otherTable = GetTable(otherTableType);\r
514 \r
515                 //ie:EmployeeTerritories.EmployeeID\r
516 \r
517                 var thisForeignKeyProperty = thisType.GetProperty(associationInfo.ThisKey);\r
518                 object thisForeignKeyValue = thisForeignKeyProperty.GetValue(entity, null);\r
519 \r
520                 IEnumerable query = null;\r
521                 if (thisForeignKeyValue != null)\r
522                 {\r
523                     ParameterExpression p = Expression.Parameter(otherTableType, "other");\r
524                     Expression predicate;\r
525                     if (!(thisForeignKeyProperty.PropertyType.IsNullable()))\r
526                     {\r
527                         predicate = Expression.Equal(Expression.MakeMemberAccess(p, otherPKs.First()),\r
528                                                                     Expression.Constant(thisForeignKeyValue));\r
529                     }\r
530                     else\r
531                     {\r
532                         var ValueProperty = thisForeignKeyProperty.PropertyType.GetProperty("Value");\r
533                         predicate = Expression.Equal(Expression.MakeMemberAccess(p, otherPKs.First()),\r
534                                                                  Expression.Constant(ValueProperty.GetValue(thisForeignKeyValue, null)));\r
535                     }\r
536 \r
537                     query = GetOtherTableQuery(predicate, p, otherTableType, otherTable) as IEnumerable;\r
538                     //it would be interesting surround the above query with a .Take(1) expression for performance.\r
539                 }\r
540 \r
541 \r
542                 FieldInfo entityRefField = entity.GetType().GetField(associationInfo.Storage, BindingFlags.NonPublic | BindingFlags.Instance);\r
543                 object entityRefValue = null;\r
544                 if (query != null)\r
545                     entityRefValue = Activator.CreateInstance(entityRefField.FieldType, query);\r
546                 else\r
547                     entityRefValue = Activator.CreateInstance(entityRefField.FieldType);\r
548                 entityRefField.SetValue(entity, entityRefValue);\r
549             }\r
550         }\r
551 \r
552         /// <summary>\r
553         /// This method is executed when the entity is being registered. Each EntitySet property has a internal query that can be set using the EntitySet.SetSource method.\r
554         /// Here we set the query source of each EntitySetProperty\r
555         /// </summary>\r
556         /// <param name="entity"></param>\r
557         private void SetEntitySetsQueries(object entity)\r
558         {\r
559             if (!this.deferredLoadingEnabled)\r
560                 return;\r
561 \r
562             // BUG: This is ignoring External Mappings from XmlMappingSource.\r
563 \r
564             IList<MemberInfo> properties = DataMapper.GetEntitySetAssociations(entity.GetType());\r
565             \r
566             if (properties.Any()) {\r
567                 IList<MemberInfo> thisPKs = DataMapper.GetPrimaryKeys(Mapping.GetTable(entity.GetType()));\r
568 \r
569                 if (thisPKs.Count > 1)\r
570                     throw new NotSupportedException("Multiple keys object not supported yet.");\r
571 \r
572                 object primaryKeyValue = (thisPKs.First() as PropertyInfo).GetValue(entity, null);\r
573 \r
574 \r
575                 foreach (PropertyInfo prop in properties)\r
576                 {\r
577                     //example of entitySet: Employee.EmployeeTerritories\r
578                     var associationInfo = prop.GetAttribute<AssociationAttribute>();\r
579                     Type otherTableType = prop.PropertyType.GetGenericArguments().First();\r
580 \r
581                     //other table:EmployeeTerritories\r
582                     var otherTable = GetTable(otherTableType);\r
583                     //other table member:EmployeeTerritories.EmployeeID\r
584                     var otherTableMember = otherTableType.GetProperty(associationInfo.OtherKey);\r
585 \r
586 \r
587                     ParameterExpression p = Expression.Parameter(otherTableType, "other");\r
588                     Expression predicate;\r
589                     if (!(otherTableMember.PropertyType.IsNullable()))\r
590                     {\r
591                         predicate = Expression.Equal(Expression.MakeMemberAccess(p, otherTableMember),\r
592                                                                     Expression.Constant(primaryKeyValue));\r
593                     }\r
594                     else\r
595                     {\r
596                         var ValueProperty = otherTableMember.PropertyType.GetProperty("Value");\r
597                         predicate = Expression.Equal(Expression.MakeMemberAccess(\r
598                                                                     Expression.MakeMemberAccess(p, otherTableMember),\r
599                                                                     ValueProperty),\r
600                                                                  Expression.Constant(primaryKeyValue));\r
601                     }\r
602 \r
603                     var query = GetOtherTableQuery(predicate, p, otherTableType, otherTable);\r
604 \r
605                     var entitySetValue = prop.GetValue(entity, null);\r
606 \r
607                     if (entitySetValue == null)\r
608                     {\r
609                         entitySetValue = Activator.CreateInstance(prop.PropertyType);\r
610                         prop.SetValue(entity, entitySetValue, null);\r
611                     }\r
612 \r
613                     var setSourceMethod = entitySetValue.GetType().GetMethod("SetSource");\r
614                     setSourceMethod.Invoke(entitySetValue, new[] { query });\r
615                     //employee.EmployeeTerritories.SetSource(Table[EmployeesTerritories].Where(other=>other.employeeID="WARTH"))\r
616                 }\r
617             }\r
618         }\r
619 \r
620         private object GetOtherTableQuery(Expression predicate, ParameterExpression parameter, Type otherTableType, IQueryable otherTable)\r
621         {\r
622             //predicate: other.EmployeeID== "WARTH"\r
623             Expression lambdaPredicate = Expression.Lambda(predicate, parameter);\r
624             //lambdaPredicate: other=>other.EmployeeID== "WARTH"\r
625 \r
626             var whereMethod = typeof(Queryable)\r
627                               .GetMethods().First(m => m.Name == "Where")\r
628                               .MakeGenericMethod(otherTableType);\r
629 \r
630 \r
631             Expression call = Expression.Call(whereMethod, otherTable.Expression, lambdaPredicate);\r
632             //Table[EmployeesTerritories].Where(other=>other.employeeID="WARTH")\r
633 \r
634             return otherTable.Provider.CreateQuery(call);\r
635         }\r
636 \r
637         #endregion\r
638 \r
639         #region Insert/Update/Delete management\r
640 \r
641         /// <summary>\r
642         /// Registers an entity for insert\r
643         /// </summary>\r
644         /// <param name="entity"></param>\r
645         internal void RegisterInsert(object entity)\r
646         {\r
647             EntityTracker.RegisterToInsert(entity);\r
648         }\r
649 \r
650         /// <summary>\r
651         /// Registers an entity for update\r
652         /// The entity will be updated only if some of its members have changed after the registration\r
653         /// </summary>\r
654         /// <param name="entity"></param>\r
655         internal void RegisterUpdate(object entity)\r
656         {\r
657             if (entity == null)\r
658                 throw new ArgumentNullException("entity");\r
659 \r
660             if (!this.objectTrackingEnabled)\r
661                 return;\r
662 \r
663             var identityReader = _GetIdentityReader(entity.GetType());\r
664             var identityKey = identityReader.GetIdentityKey(entity);\r
665             // if we have no key, we can not watch\r
666             if (identityKey == null)\r
667                 return;\r
668             // register entity\r
669             EntityTracker.RegisterToWatch(entity, identityKey);\r
670         }\r
671 \r
672         /// <summary>\r
673         /// Registers or re-registers an entity and clears its state\r
674         /// </summary>\r
675         /// <param name="entity"></param>\r
676         /// <returns></returns>\r
677         internal object Register(object entity)\r
678         {\r
679             if (! this.objectTrackingEnabled)\r
680                 return entity;\r
681             var registeredEntity = _GetOrRegisterEntity(entity);\r
682             // the fact of registering again clears the modified state, so we're... clear with that\r
683             MemberModificationHandler.Register(registeredEntity, Mapping);\r
684             return registeredEntity;\r
685         }\r
686 \r
687         /// <summary>\r
688         /// Registers an entity for update\r
689         /// The entity will be updated only if some of its members have changed after the registration\r
690         /// </summary>\r
691         /// <param name="entity"></param>\r
692         /// <param name="entityOriginalState"></param>\r
693         internal void RegisterUpdate(object entity, object entityOriginalState)\r
694         {\r
695             if (!this.objectTrackingEnabled)\r
696                 return;\r
697             RegisterUpdate(entity);\r
698             MemberModificationHandler.Register(entity, entityOriginalState, Mapping);\r
699         }\r
700 \r
701         /// <summary>\r
702         /// Clears the current state, and marks the object as clean\r
703         /// </summary>\r
704         /// <param name="entity"></param>\r
705         internal void RegisterUpdateAgain(object entity)\r
706         {\r
707             if (!this.objectTrackingEnabled)\r
708                 return;\r
709             MemberModificationHandler.ClearModified(entity, Mapping);\r
710         }\r
711 \r
712         /// <summary>\r
713         /// Registers an entity for delete\r
714         /// </summary>\r
715         /// <param name="entity"></param>\r
716         internal void RegisterDelete(object entity)\r
717         {\r
718             if (!this.objectTrackingEnabled)\r
719                 return;\r
720             EntityTracker.RegisterToDelete(entity);\r
721         }\r
722 \r
723         /// <summary>\r
724         /// Unregisters entity after deletion\r
725         /// </summary>\r
726         /// <param name="entity"></param>\r
727         internal void UnregisterDelete(object entity)\r
728         {\r
729             if (!this.objectTrackingEnabled)\r
730                 return;\r
731             EntityTracker.RegisterDeleted(entity);\r
732         }\r
733 \r
734         #endregion\r
735 \r
736         /// <summary>\r
737         /// Changed object determine \r
738         /// </summary>\r
739         /// <returns>Lists of inserted, updated, deleted objects</returns>\r
740         public ChangeSet GetChangeSet()\r
741         {\r
742             var inserts = new List<object>();\r
743             var updates = new List<object>();\r
744             var deletes = new List<object>();\r
745             foreach (var entityTrack in EntityTracker.EnumerateAll())\r
746             {\r
747                 switch (entityTrack.EntityState)\r
748                 {\r
749                     case EntityState.ToInsert:\r
750                         inserts.Add(entityTrack.Entity);\r
751                         break;\r
752                     case EntityState.ToWatch:\r
753                         if (MemberModificationHandler.IsModified(entityTrack.Entity, Mapping))\r
754                             updates.Add(entityTrack.Entity);\r
755                         break;\r
756                     case EntityState.ToDelete:\r
757                         deletes.Add(entityTrack.Entity);\r
758                         break;\r
759                     default:\r
760                         throw new ArgumentOutOfRangeException();\r
761                 }\r
762             }\r
763             return new ChangeSet(inserts, updates, deletes);\r
764         }\r
765 \r
766         /// <summary>\r
767         /// use ExecuteCommand to call raw SQL\r
768         /// </summary>\r
769         public int ExecuteCommand(string command, params object[] parameters)\r
770         {\r
771             var directQuery = QueryBuilder.GetDirectQuery(command, new QueryContext(this));\r
772             return QueryRunner.Execute(directQuery, parameters);\r
773         }\r
774 \r
775         /// <summary>\r
776         /// Execute raw SQL query and return object\r
777         /// </summary>\r
778         public IEnumerable<TResult> ExecuteQuery<TResult>(string query, params object[] parameters) where TResult : class, new()\r
779         {\r
780             if (query == null)\r
781                 throw new ArgumentNullException("query");\r
782 \r
783             return CreateExecuteQueryEnumerable<TResult>(query, parameters);\r
784         }\r
785 \r
786         private IEnumerable<TResult> CreateExecuteQueryEnumerable<TResult>(string query, object[] parameters)\r
787             where TResult : class, new()\r
788         {\r
789             foreach (TResult result in ExecuteQuery(typeof(TResult), query, parameters))\r
790                 yield return result;\r
791         }\r
792 \r
793         public IEnumerable ExecuteQuery(Type elementType, string query, params object[] parameters)\r
794         {\r
795             Console.WriteLine("# ExecuteQuery: query={0}", query != null ? query : "<null>");\r
796             if (elementType == null)\r
797                 throw new ArgumentNullException("elementType");\r
798             if (query == null)\r
799                 throw new ArgumentNullException("query");\r
800 \r
801             var queryContext = new QueryContext(this);\r
802             var directQuery = QueryBuilder.GetDirectQuery(query, queryContext);\r
803             return QueryRunner.ExecuteSelect(elementType, directQuery, parameters);\r
804         }\r
805 \r
806         /// <summary>\r
807         /// Gets or sets the load options\r
808         /// </summary>\r
809         [DbLinqToDo]\r
810         public DataLoadOptions LoadOptions \r
811         {\r
812             get { throw new NotImplementedException(); }\r
813             set { throw new NotImplementedException(); }\r
814         }\r
815 \r
816         public DbTransaction Transaction { get; set; }\r
817 \r
818         /// <summary>\r
819         /// Runs the given reader and returns columns.\r
820         /// </summary>\r
821         /// <typeparam name="TResult">The type of the result.</typeparam>\r
822         /// <param name="reader">The reader.</param>\r
823         /// <returns></returns>\r
824         public IEnumerable<TResult> Translate<TResult>(DbDataReader reader)\r
825         {\r
826             if (reader == null)\r
827                 throw new ArgumentNullException("reader");\r
828             return CreateTranslateIterator<TResult>(reader);\r
829         }\r
830 \r
831         IEnumerable<TResult> CreateTranslateIterator<TResult>(DbDataReader reader)\r
832         {\r
833             foreach (TResult result in Translate(typeof(TResult), reader))\r
834                 yield return result;\r
835         }\r
836 \r
837         public IMultipleResults Translate(DbDataReader reader)\r
838         {\r
839             throw new NotImplementedException();\r
840         }\r
841 \r
842         public IEnumerable Translate(Type elementType, DbDataReader reader)\r
843         {\r
844             if (elementType == null)\r
845                 throw new ArgumentNullException("elementType");\r
846             if (reader == null)\r
847                 throw new ArgumentNullException("reader");\r
848 \r
849             return QueryRunner.EnumerateResult(elementType, reader, this);\r
850         }\r
851 \r
852         public void Dispose()\r
853         {\r
854             //connection closing should not be done here.\r
855             //read: http://msdn2.microsoft.com/en-us/library/bb292288.aspx\r
856         }\r
857 \r
858         [DbLinqToDo]\r
859         protected virtual void Dispose(bool disposing)\r
860         {\r
861             throw new NotImplementedException();\r
862         }\r
863 \r
864         /// <summary>\r
865         /// Creates a IDbDataAdapter. Used internally by Vendors\r
866         /// </summary>\r
867         /// <returns></returns>\r
868         internal IDbDataAdapter CreateDataAdapter()\r
869         {\r
870             return DatabaseContext.CreateDataAdapter();\r
871         }\r
872 \r
873         /// <summary>\r
874         /// Sets a TextWriter where generated SQL commands are written\r
875         /// </summary>\r
876         public TextWriter Log { get; set; }\r
877 \r
878         /// <summary>\r
879         /// Writes text on Log (if not null)\r
880         /// Internal helper\r
881         /// </summary>\r
882         /// <param name="text"></param>\r
883         internal void WriteLog(string text)\r
884         {\r
885             if (Log != null)\r
886                 Log.WriteLine(text);\r
887         }\r
888 \r
889         /// <summary>\r
890         /// Write an IDbCommand to Log (if non null)\r
891         /// </summary>\r
892         /// <param name="command"></param>\r
893         internal void WriteLog(IDbCommand command)\r
894         {\r
895             if (Log != null)\r
896             {\r
897                 Log.WriteLine(command.CommandText);\r
898                 foreach (IDbDataParameter parameter in command.Parameters)\r
899                     WriteLog(parameter);\r
900                 Log.Write("--");\r
901                 Log.Write(" Context: {0}", Vendor.VendorName);\r
902                 Log.Write(" Model: {0}", Mapping.GetType().Name);\r
903                 Log.Write(" Build: {0}", Assembly.GetExecutingAssembly().GetName().Version);\r
904                 Log.WriteLine();\r
905             }\r
906         }\r
907 \r
908         /// <summary>\r
909         /// Writes and IDbDataParameter to Log (if non null)\r
910         /// </summary>\r
911         /// <param name="parameter"></param>\r
912         internal void WriteLog(IDbDataParameter parameter)\r
913         {\r
914             if (Log != null)\r
915             {\r
916                 // -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [2]\r
917                 // -- <name>: <direction> <type> (...) [<value>]\r
918                 Log.WriteLine("-- {0}: {1} {2} (Size = {3}; Prec = {4}; Scale = {5}) [{6}]",\r
919                     parameter.ParameterName, parameter.Direction, parameter.DbType,\r
920                     parameter.Size, parameter.Precision, parameter.Scale, parameter.Value);\r
921             }\r
922         }\r
923 \r
924         public bool ObjectTrackingEnabled\r
925         {\r
926             get { return this.objectTrackingEnabled; }\r
927             set \r
928             {\r
929                 if (this.entityTracker != null && value != this.objectTrackingEnabled)\r
930                     throw new InvalidOperationException("Data context options cannot be modified after results have been returned from a query.");\r
931                 this.objectTrackingEnabled = value;\r
932             }\r
933         }\r
934 \r
935         [DbLinqToDo]\r
936         public int CommandTimeout\r
937         {\r
938             get { throw new NotImplementedException(); }\r
939             set { throw new NotImplementedException(); }\r
940         }\r
941 \r
942         public bool DeferredLoadingEnabled\r
943         {\r
944             get { return this.deferredLoadingEnabled; }\r
945             set\r
946             {\r
947                 if (this.entityTracker != null && value != this.deferredLoadingEnabled)\r
948                     throw new InvalidOperationException("Data context options cannot be modified after results have been returned from a query.");\r
949                 this.deferredLoadingEnabled = value;\r
950             }\r
951         }\r
952 \r
953         [DbLinqToDo]\r
954         public ChangeConflictCollection ChangeConflicts\r
955         {\r
956             get { throw new NotImplementedException(); }\r
957         }\r
958 \r
959         [DbLinqToDo]\r
960         public DbCommand GetCommand(IQueryable query)\r
961         {\r
962             if (query == null)\r
963                 throw new ArgumentNullException("query");\r
964 \r
965             var qp = query.Provider as QueryProvider;\r
966             if (qp == null)\r
967                 throw new InvalidOperationException();\r
968 \r
969             IDbCommand dbCommand = qp.GetQuery(null).GetCommand().Command;\r
970             if (!(dbCommand is DbCommand))\r
971                 throw new InvalidOperationException();\r
972 \r
973             return (DbCommand)dbCommand;\r
974         }\r
975 \r
976         [DbLinqToDo]\r
977         public void Refresh(RefreshMode mode, IEnumerable entities)\r
978         {\r
979             throw new NotImplementedException();\r
980         }\r
981 \r
982         [DbLinqToDo]\r
983         public void Refresh(RefreshMode mode, params object[] entities)\r
984         {\r
985             throw new NotImplementedException();\r
986         }\r
987 \r
988         [DbLinqToDo]\r
989         public void Refresh(RefreshMode mode, object entity)\r
990         {\r
991             throw new NotImplementedException();\r
992         }\r
993 \r
994         [DbLinqToDo]\r
995         public void DeleteDatabase()\r
996         {\r
997             throw new NotImplementedException();\r
998         }\r
999 \r
1000         [DbLinqToDo]\r
1001         public void CreateDatabase()\r
1002         {\r
1003             throw new NotImplementedException();\r
1004         }\r
1005 \r
1006         [DbLinqToDo]\r
1007         protected internal IQueryable<TResult> CreateMethodCallQuery<TResult>(object instance, MethodInfo methodInfo, params object[] parameters)\r
1008         {\r
1009             throw new NotImplementedException();\r
1010         }\r
1011 \r
1012         [DbLinqToDo]\r
1013         protected internal void ExecuteDynamicDelete(object entity)\r
1014         {\r
1015             throw new NotImplementedException();\r
1016         }\r
1017 \r
1018         [DbLinqToDo]\r
1019         protected internal void ExecuteDynamicInsert(object entity)\r
1020         {\r
1021             throw new NotImplementedException();\r
1022         }\r
1023 \r
1024         [DbLinqToDo]\r
1025         protected internal void ExecuteDynamicUpdate(object entity)\r
1026         {\r
1027             throw new NotImplementedException();\r
1028         }\r
1029     }\r
1030 }\r