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