5 // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne
\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
14 // The above copyright notice and this permission notice shall be included in
\r
15 // all copies or substantial portions of the Software.
\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
28 using System.Collections;
\r
30 using System.Data.Common;
\r
31 using System.Data.Linq.Mapping;
\r
32 using System.Collections.Generic;
\r
35 using System.Reflection;
\r
38 using System.Data.Linq.Implementation;
\r
39 using System.Data.Linq.Sugar;
\r
40 using System.Data.Linq.Identity;
\r
42 using AttributeMappingSource = System.Data.Linq.Mapping.AttributeMappingSource;
\r
43 using MappingContext = System.Data.Linq.Mapping.MappingContext;
\r
46 using DbLinq.Data.Linq.Implementation;
\r
47 using DbLinq.Data.Linq.Sugar;
\r
48 using DbLinq.Data.Linq.Identity;
\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
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
63 namespace System.Data.Linq
\r
65 namespace DbLinq.Data.Linq
\r
68 public partial class DataContext : IDisposable
\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
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
76 public DbConnection Connection { get { return DatabaseContext.Connection as DbConnection; } }
\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
86 private bool objectTrackingEnabled = true;
\r
87 private bool deferredLoadingEnabled = true;
\r
89 private IEntityTracker entityTracker;
\r
90 private IEntityTracker EntityTracker
\r
94 if (this.entityTracker == null)
\r
96 if (this.ObjectTrackingEnabled)
\r
97 this.entityTracker = new EntityTracker();
\r
99 this.entityTracker = new DisabledEntityTracker();
\r
101 return this.entityTracker;
\r
107 private IIdentityReaderFactory identityReaderFactory;
\r
108 private readonly IDictionary<Type, IIdentityReader> identityReaders = new Dictionary<Type, IIdentityReader>();
\r
111 /// The default behavior creates one MappingContext.
\r
114 internal virtual MappingContext _MappingContext { get; set; }
\r
117 internal IVendorProvider _VendorProvider { get; set; }
\r
119 public DataContext(IDbConnection connection, MappingSource mapping)
\r
121 Init(new DatabaseContext(connection), mapping, null);
\r
124 public DataContext(IDbConnection connection)
\r
126 if (connection == null)
\r
127 throw new ArgumentNullException("connection");
\r
129 Init(new DatabaseContext(connection), null, null);
\r
133 public DataContext(string fileOrServerOrConnection, MappingSource mapping)
\r
135 if (fileOrServerOrConnection == null)
\r
136 throw new ArgumentNullException("fileOrServerOrConnection");
\r
137 if (mapping == null)
\r
138 throw new ArgumentNullException("mapping");
\r
140 if (File.Exists(fileOrServerOrConnection))
\r
141 throw new NotImplementedException("File names not supported.");
\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
149 // Assume it's a connection string...
\r
150 IVendor ivendor = GetVendor(fileOrServerOrConnection);
\r
152 IDbConnection dbConnection = ivendor.CreateDbConnection(fileOrServerOrConnection);
\r
153 Init(new DatabaseContext(dbConnection), mapping, ivendor);
\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
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
165 /// <param name="connectionString">specifies file or server connection</param>
\r
167 public DataContext(string connectionString)
\r
169 IVendor ivendor = GetVendor(connectionString);
\r
171 IDbConnection dbConnection = ivendor.CreateDbConnection(connectionString);
\r
172 Init(new DatabaseContext(dbConnection), null, ivendor);
\r
176 private IVendor GetVendor(string connectionString)
\r
178 if (connectionString == null)
\r
179 throw new ArgumentNullException("connectionString");
\r
182 string vendorClassToLoad;
\r
183 GetVendorInfo(connectionString, out assy, out vendorClassToLoad);
\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
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
196 else if (types.Count() > 1)
\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
201 return (IVendor) Activator.CreateInstance(types.First());
\r
204 private void GetVendorInfo(string connectionString, out Assembly assembly, out string typeName)
\r
206 System.Text.RegularExpressions.Regex reProvider
\r
207 = new System.Text.RegularExpressions.Regex(@"DbLinqProvider=([\w\.]+)");
\r
209 string assemblyFile = null;
\r
211 if (!reProvider.IsMatch(connectionString))
\r
213 vendor = "SqlServer";
\r
214 assemblyFile = "DbLinq.SqlServer.dll";
\r
218 var match = reProvider.Match(connectionString);
\r
219 vendor = match.Groups[1].Value;
\r
220 assemblyFile = "DbLinq." + vendor + ".dll";
\r
222 //plain DbLinq - non MONO:
\r
223 //IVendor classes are in DLLs such as "DbLinq.MySql.dll"
\r
224 if (vendor.Contains("."))
\r
226 //already fully qualified DLL name?
\r
227 throw new ArgumentException("Please provide a short name, such as 'MySql', not '" + vendor + "'");
\r
230 //shorten: "DbLinqProvider=X;Server=Y" -> ";Server=Y"
\r
231 connectionString = reProvider.Replace(connectionString, "");
\r
234 typeName = vendor + "Vendor";
\r
239 assembly = typeof (DataContext).Assembly; // System.Data.Linq.dll
\r
241 //TODO: check if DLL is already loaded?
\r
242 assembly = Assembly.LoadFrom(assemblyFile);
\r
245 catch (Exception e)
\r
247 throw new ArgumentException(
\r
249 "Unable to load the `{0}' DbLinq vendor within assembly `{1}'.",
\r
250 assemblyFile, vendor),
\r
251 "connectionString", e);
\r
255 private void Init(IDatabaseContext databaseContext, MappingSource mappingSource, IVendor vendor)
\r
257 if (databaseContext == null)
\r
258 throw new ArgumentNullException("databaseContext");
\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
264 _VendorProvider = ObjectFactory.Get<IVendorProvider>();
\r
265 Vendor = vendor ??
\r
266 (databaseContext.Connection.ConnectionString != null
\r
267 ? GetVendor(databaseContext.Connection.ConnectionString)
\r
269 _VendorProvider.FindVendorByProviderType(typeof(SqlClient.Sql2005Provider));
\r
271 DatabaseContext = databaseContext;
\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
277 //EntityMap = ObjectFactory.Create<IEntityMap>();
\r
278 identityReaderFactory = ObjectFactory.Get<IIdentityReaderFactory>();
\r
280 _MappingContext = new MappingContext();
\r
282 // initialize the mapping information
\r
283 if (mappingSource == null)
\r
284 mappingSource = new AttributeMappingSource();
\r
285 Mapping = mappingSource.GetModel(GetType());
\r
289 /// Checks if the table is allready mapped or maps it if not.
\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
295 //This will throw an exception if the table is not found
\r
296 if(Mapping.GetTable(tableType) == null)
\r
298 throw new InvalidOperationException("The type '" + tableType.Name + "' is not mapped as a Table.");
\r
303 /// Returns a Table for the type TEntity.
\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
309 return (Table<TEntity>)GetTable(typeof(TEntity));
\r
313 /// Returns a Table for the given type.
\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
321 ITable tableExisting;
\r
322 if (_tableMap.TryGetValue(type, out tableExisting))
\r
323 return tableExisting;
\r
325 //Check for table mapping
\r
326 CheckTableMapping(type);
\r
328 var tableNew = Activator.CreateInstance(
\r
329 typeof(Table<>).MakeGenericType(type)
\r
330 , BindingFlags.NonPublic | BindingFlags.Instance
\r
332 , new object[] { this }
\r
333 , System.Globalization.CultureInfo.CurrentCulture) as ITable;
\r
335 _tableMap[type] = tableNew;
\r
340 public void SubmitChanges()
\r
342 SubmitChanges(ConflictMode.FailOnFirstConflict);
\r
348 /// <returns></returns>
\r
349 public bool DatabaseExists()
\r
353 return Vendor.Ping(this);
\r
362 /// Commits all pending changes to database
\r
364 /// <param name="failureMode"></param>
\r
365 public virtual void SubmitChanges(ConflictMode failureMode)
\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
372 var queryContext = new QueryContext(this);
\r
373 var entityTracks = EntityTracker.EnumerateAll().ToList();
\r
374 foreach (var entityTrack in entityTracks)
\r
376 switch (entityTrack.EntityState)
\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
383 case EntityState.ToWatch:
\r
384 if (MemberModificationHandler.IsModified(entityTrack.Entity, Mapping))
\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
390 RegisterUpdateAgain(entityTrack.Entity);
\r
393 case EntityState.ToDelete:
\r
394 var deleteQuery = QueryBuilder.GetDeleteQuery(entityTrack.Entity, queryContext);
\r
395 QueryRunner.Delete(entityTrack.Entity, deleteQuery);
\r
397 UnregisterDelete(entityTrack.Entity);
\r
400 throw new ArgumentOutOfRangeException();
\r
403 // TODO: handle conflicts (which can only occur when concurrency mode is implemented)
\r
404 transaction.Commit();
\r
409 /// TODO - allow generated methods to call into stored procedures
\r
412 internal IExecuteResult _ExecuteMethodCall(DataContext context, System.Reflection.MethodInfo method, params object[] sqlParams)
\r
414 using (DatabaseContext.OpenConnection())
\r
416 System.Data.Linq.IExecuteResult result = Vendor.ExecuteMethodCall(context, method, sqlParams);
\r
422 protected IExecuteResult ExecuteMethodCall(object instance, System.Reflection.MethodInfo methodInfo, params object[] parameters)
\r
424 throw new NotImplementedException();
\r
427 #region Identity management
\r
430 internal IIdentityReader _GetIdentityReader(Type t)
\r
432 IIdentityReader identityReader;
\r
433 lock (identityReaders)
\r
435 if (!identityReaders.TryGetValue(t, out identityReader))
\r
437 identityReader = identityReaderFactory.GetReader(t, this);
\r
438 identityReaders[t] = identityReader;
\r
441 return identityReader;
\r
445 internal object _GetRegisteredEntity(object entity)
\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
453 var registeredEntityTrack = EntityTracker.FindByIdentity(identityKey);
\r
454 if (registeredEntityTrack != null)
\r
455 return registeredEntityTrack.Entity;
\r
459 //internal object GetRegisteredEntityByKey(IdentityKey identityKey)
\r
461 // return EntityMap[identityKey];
\r
465 /// Registers an entity in a watch state
\r
467 /// <param name="entity"></param>
\r
468 /// <returns></returns>
\r
470 internal object _GetOrRegisterEntity(object entity)
\r
472 var identityReader = _GetIdentityReader(entity.GetType());
\r
473 var identityKey = identityReader.GetIdentityKey(entity);
\r
474 SetEntitySetsQueries(entity);
\r
475 SetEntityRefQueries(entity);
\r
477 // if we have no identity, we can't track it
\r
478 if (identityKey == null)
\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
486 // otherwise, register and return
\r
487 EntityTracker.RegisterToWatch(entity, identityKey);
\r
491 readonly IDataMapper DataMapper = ObjectFactory.Get<IDataMapper>();
\r
492 private void SetEntityRefQueries(object entity)
\r
494 if (!this.deferredLoadingEnabled)
\r
497 // BUG: This is ignoring External Mappings from XmlMappingSource.
\r
499 Type thisType = entity.GetType();
\r
500 IList<MemberInfo> properties = DataMapper.GetEntityRefAssociations(thisType);
\r
503 foreach (PropertyInfo prop in properties)
\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
510 if (otherPKs.Count > 1)
\r
511 throw new NotSupportedException("Multiple keys object not supported yet.");
\r
513 var otherTable = GetTable(otherTableType);
\r
515 //ie:EmployeeTerritories.EmployeeID
\r
517 var thisForeignKeyProperty = thisType.GetProperty(associationInfo.ThisKey);
\r
518 object thisForeignKeyValue = thisForeignKeyProperty.GetValue(entity, null);
\r
520 IEnumerable query = null;
\r
521 if (thisForeignKeyValue != null)
\r
523 ParameterExpression p = Expression.Parameter(otherTableType, "other");
\r
524 Expression predicate;
\r
525 if (!(thisForeignKeyProperty.PropertyType.IsNullable()))
\r
527 predicate = Expression.Equal(Expression.MakeMemberAccess(p, otherPKs.First()),
\r
528 Expression.Constant(thisForeignKeyValue));
\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
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
542 FieldInfo entityRefField = entity.GetType().GetField(associationInfo.Storage, BindingFlags.NonPublic | BindingFlags.Instance);
\r
543 object entityRefValue = null;
\r
545 entityRefValue = Activator.CreateInstance(entityRefField.FieldType, query);
\r
547 entityRefValue = Activator.CreateInstance(entityRefField.FieldType);
\r
548 entityRefField.SetValue(entity, entityRefValue);
\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
556 /// <param name="entity"></param>
\r
557 private void SetEntitySetsQueries(object entity)
\r
559 if (!this.deferredLoadingEnabled)
\r
562 // BUG: This is ignoring External Mappings from XmlMappingSource.
\r
564 IList<MemberInfo> properties = DataMapper.GetEntitySetAssociations(entity.GetType());
\r
566 if (properties.Any()) {
\r
567 IList<MemberInfo> thisPKs = DataMapper.GetPrimaryKeys(Mapping.GetTable(entity.GetType()));
\r
569 if (thisPKs.Count > 1)
\r
570 throw new NotSupportedException("Multiple keys object not supported yet.");
\r
572 object primaryKeyValue = (thisPKs.First() as PropertyInfo).GetValue(entity, null);
\r
575 foreach (PropertyInfo prop in properties)
\r
577 //example of entitySet: Employee.EmployeeTerritories
\r
578 var associationInfo = prop.GetAttribute<AssociationAttribute>();
\r
579 Type otherTableType = prop.PropertyType.GetGenericArguments().First();
\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
587 ParameterExpression p = Expression.Parameter(otherTableType, "other");
\r
588 Expression predicate;
\r
589 if (!(otherTableMember.PropertyType.IsNullable()))
\r
591 predicate = Expression.Equal(Expression.MakeMemberAccess(p, otherTableMember),
\r
592 Expression.Constant(primaryKeyValue));
\r
596 var ValueProperty = otherTableMember.PropertyType.GetProperty("Value");
\r
597 predicate = Expression.Equal(Expression.MakeMemberAccess(
\r
598 Expression.MakeMemberAccess(p, otherTableMember),
\r
600 Expression.Constant(primaryKeyValue));
\r
603 var query = GetOtherTableQuery(predicate, p, otherTableType, otherTable);
\r
605 var entitySetValue = prop.GetValue(entity, null);
\r
607 if (entitySetValue == null)
\r
609 entitySetValue = Activator.CreateInstance(prop.PropertyType);
\r
610 prop.SetValue(entity, entitySetValue, null);
\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
620 private object GetOtherTableQuery(Expression predicate, ParameterExpression parameter, Type otherTableType, IQueryable otherTable)
\r
622 //predicate: other.EmployeeID== "WARTH"
\r
623 Expression lambdaPredicate = Expression.Lambda(predicate, parameter);
\r
624 //lambdaPredicate: other=>other.EmployeeID== "WARTH"
\r
626 var whereMethod = typeof(Queryable)
\r
627 .GetMethods().First(m => m.Name == "Where")
\r
628 .MakeGenericMethod(otherTableType);
\r
631 Expression call = Expression.Call(whereMethod, otherTable.Expression, lambdaPredicate);
\r
632 //Table[EmployeesTerritories].Where(other=>other.employeeID="WARTH")
\r
634 return otherTable.Provider.CreateQuery(call);
\r
639 #region Insert/Update/Delete management
\r
642 /// Registers an entity for insert
\r
644 /// <param name="entity"></param>
\r
645 internal void RegisterInsert(object entity)
\r
647 EntityTracker.RegisterToInsert(entity);
\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
654 /// <param name="entity"></param>
\r
655 internal void RegisterUpdate(object entity)
\r
657 if (entity == null)
\r
658 throw new ArgumentNullException("entity");
\r
660 if (!this.objectTrackingEnabled)
\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
669 EntityTracker.RegisterToWatch(entity, identityKey);
\r
673 /// Registers or re-registers an entity and clears its state
\r
675 /// <param name="entity"></param>
\r
676 /// <returns></returns>
\r
677 internal object Register(object entity)
\r
679 if (! this.objectTrackingEnabled)
\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
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
691 /// <param name="entity"></param>
\r
692 /// <param name="entityOriginalState"></param>
\r
693 internal void RegisterUpdate(object entity, object entityOriginalState)
\r
695 if (!this.objectTrackingEnabled)
\r
697 RegisterUpdate(entity);
\r
698 MemberModificationHandler.Register(entity, entityOriginalState, Mapping);
\r
702 /// Clears the current state, and marks the object as clean
\r
704 /// <param name="entity"></param>
\r
705 internal void RegisterUpdateAgain(object entity)
\r
707 if (!this.objectTrackingEnabled)
\r
709 MemberModificationHandler.ClearModified(entity, Mapping);
\r
713 /// Registers an entity for delete
\r
715 /// <param name="entity"></param>
\r
716 internal void RegisterDelete(object entity)
\r
718 if (!this.objectTrackingEnabled)
\r
720 EntityTracker.RegisterToDelete(entity);
\r
724 /// Unregisters entity after deletion
\r
726 /// <param name="entity"></param>
\r
727 internal void UnregisterDelete(object entity)
\r
729 if (!this.objectTrackingEnabled)
\r
731 EntityTracker.RegisterDeleted(entity);
\r
737 /// Changed object determine
\r
739 /// <returns>Lists of inserted, updated, deleted objects</returns>
\r
740 public ChangeSet GetChangeSet()
\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
747 switch (entityTrack.EntityState)
\r
749 case EntityState.ToInsert:
\r
750 inserts.Add(entityTrack.Entity);
\r
752 case EntityState.ToWatch:
\r
753 if (MemberModificationHandler.IsModified(entityTrack.Entity, Mapping))
\r
754 updates.Add(entityTrack.Entity);
\r
756 case EntityState.ToDelete:
\r
757 deletes.Add(entityTrack.Entity);
\r
760 throw new ArgumentOutOfRangeException();
\r
763 return new ChangeSet(inserts, updates, deletes);
\r
767 /// use ExecuteCommand to call raw SQL
\r
769 public int ExecuteCommand(string command, params object[] parameters)
\r
771 var directQuery = QueryBuilder.GetDirectQuery(command, new QueryContext(this));
\r
772 return QueryRunner.Execute(directQuery, parameters);
\r
776 /// Execute raw SQL query and return object
\r
778 public IEnumerable<TResult> ExecuteQuery<TResult>(string query, params object[] parameters) where TResult : class, new()
\r
781 throw new ArgumentNullException("query");
\r
783 return CreateExecuteQueryEnumerable<TResult>(query, parameters);
\r
786 private IEnumerable<TResult> CreateExecuteQueryEnumerable<TResult>(string query, object[] parameters)
\r
787 where TResult : class, new()
\r
789 foreach (TResult result in ExecuteQuery(typeof(TResult), query, parameters))
\r
790 yield return result;
\r
793 public IEnumerable ExecuteQuery(Type elementType, string query, params object[] parameters)
\r
795 Console.WriteLine("# ExecuteQuery: query={0}", query != null ? query : "<null>");
\r
796 if (elementType == null)
\r
797 throw new ArgumentNullException("elementType");
\r
799 throw new ArgumentNullException("query");
\r
801 var queryContext = new QueryContext(this);
\r
802 var directQuery = QueryBuilder.GetDirectQuery(query, queryContext);
\r
803 return QueryRunner.ExecuteSelect(elementType, directQuery, parameters);
\r
807 /// Gets or sets the load options
\r
810 public DataLoadOptions LoadOptions
\r
812 get { throw new NotImplementedException(); }
\r
813 set { throw new NotImplementedException(); }
\r
816 public DbTransaction Transaction { get; set; }
\r
819 /// Runs the given reader and returns columns.
\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
826 if (reader == null)
\r
827 throw new ArgumentNullException("reader");
\r
828 return CreateTranslateIterator<TResult>(reader);
\r
831 IEnumerable<TResult> CreateTranslateIterator<TResult>(DbDataReader reader)
\r
833 foreach (TResult result in Translate(typeof(TResult), reader))
\r
834 yield return result;
\r
837 public IMultipleResults Translate(DbDataReader reader)
\r
839 throw new NotImplementedException();
\r
842 public IEnumerable Translate(Type elementType, DbDataReader reader)
\r
844 if (elementType == null)
\r
845 throw new ArgumentNullException("elementType");
\r
846 if (reader == null)
\r
847 throw new ArgumentNullException("reader");
\r
849 return QueryRunner.EnumerateResult(elementType, reader, this);
\r
852 public void Dispose()
\r
854 //connection closing should not be done here.
\r
855 //read: http://msdn2.microsoft.com/en-us/library/bb292288.aspx
\r
859 protected virtual void Dispose(bool disposing)
\r
861 throw new NotImplementedException();
\r
865 /// Creates a IDbDataAdapter. Used internally by Vendors
\r
867 /// <returns></returns>
\r
868 internal IDbDataAdapter CreateDataAdapter()
\r
870 return DatabaseContext.CreateDataAdapter();
\r
874 /// Sets a TextWriter where generated SQL commands are written
\r
876 public TextWriter Log { get; set; }
\r
879 /// Writes text on Log (if not null)
\r
880 /// Internal helper
\r
882 /// <param name="text"></param>
\r
883 internal void WriteLog(string text)
\r
886 Log.WriteLine(text);
\r
890 /// Write an IDbCommand to Log (if non null)
\r
892 /// <param name="command"></param>
\r
893 internal void WriteLog(IDbCommand command)
\r
897 Log.WriteLine(command.CommandText);
\r
898 foreach (IDbDataParameter parameter in command.Parameters)
\r
899 WriteLog(parameter);
\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
909 /// Writes and IDbDataParameter to Log (if non null)
\r
911 /// <param name="parameter"></param>
\r
912 internal void WriteLog(IDbDataParameter parameter)
\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
924 public bool ObjectTrackingEnabled
\r
926 get { return this.objectTrackingEnabled; }
\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
936 public int CommandTimeout
\r
938 get { throw new NotImplementedException(); }
\r
939 set { throw new NotImplementedException(); }
\r
942 public bool DeferredLoadingEnabled
\r
944 get { return this.deferredLoadingEnabled; }
\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
954 public ChangeConflictCollection ChangeConflicts
\r
956 get { throw new NotImplementedException(); }
\r
960 public DbCommand GetCommand(IQueryable query)
\r
963 throw new ArgumentNullException("query");
\r
965 var qp = query.Provider as QueryProvider;
\r
967 throw new InvalidOperationException();
\r
969 IDbCommand dbCommand = qp.GetQuery(null).GetCommand().Command;
\r
970 if (!(dbCommand is DbCommand))
\r
971 throw new InvalidOperationException();
\r
973 return (DbCommand)dbCommand;
\r
977 public void Refresh(RefreshMode mode, IEnumerable entities)
\r
979 throw new NotImplementedException();
\r
983 public void Refresh(RefreshMode mode, params object[] entities)
\r
985 throw new NotImplementedException();
\r
989 public void Refresh(RefreshMode mode, object entity)
\r
991 throw new NotImplementedException();
\r
995 public void DeleteDatabase()
\r
997 throw new NotImplementedException();
\r
1001 public void CreateDatabase()
\r
1003 throw new NotImplementedException();
\r
1007 protected internal IQueryable<TResult> CreateMethodCallQuery<TResult>(object instance, MethodInfo methodInfo, params object[] parameters)
\r
1009 throw new NotImplementedException();
\r
1013 protected internal void ExecuteDynamicDelete(object entity)
\r
1015 throw new NotImplementedException();
\r
1019 protected internal void ExecuteDynamicInsert(object entity)
\r
1021 throw new NotImplementedException();
\r
1025 protected internal void ExecuteDynamicUpdate(object entity)
\r
1027 throw new NotImplementedException();
\r