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;
\r
32 using System.Data.Linq.Mapping;
\r
33 using System.Linq.Expressions;
\r
34 using System.Collections.Generic;
\r
37 using System.Reflection;
\r
38 using System.Reflection.Emit;
\r
41 using AttributeMappingSource = System.Data.Linq.Mapping.AttributeMappingSource;
\r
43 using AttributeMappingSource = DbLinq.Data.Linq.Mapping.AttributeMappingSource;
\r
47 using DbLinq.Data.Linq;
\r
48 using DbLinq.Data.Linq.Database;
\r
49 using DbLinq.Data.Linq.Database.Implementation;
\r
50 using DbLinq.Data.Linq.Identity;
\r
51 using DbLinq.Data.Linq.Implementation;
\r
52 using DbLinq.Data.Linq.Mapping;
\r
53 using DbLinq.Data.Linq.Sugar;
\r
54 using DbLinq.Factory;
\r
56 using DbLinq.Vendor;
\r
59 namespace System.Data.Linq
\r
61 namespace DbLinq.Data.Linq
\r
64 public partial class DataContext : IDisposable
\r
66 //private readonly Dictionary<string, ITable> _tableMap = new Dictionary<string, ITable>();
\r
67 private readonly Dictionary<Type, ITable> _tableMap = new Dictionary<Type, ITable>();
\r
69 public MetaModel Mapping { get; private set; }
\r
70 // PC question: at ctor, we get a IDbConnection and the Connection property exposes a DbConnection
\r
72 public DbConnection Connection { get { return DatabaseContext.Connection as DbConnection; } }
\r
74 // all properties below are set public to optionally be injected
\r
75 internal IVendor Vendor { get; set; }
\r
76 internal IQueryBuilder QueryBuilder { get; set; }
\r
77 internal IQueryRunner QueryRunner { get; set; }
\r
78 internal IMemberModificationHandler MemberModificationHandler { get; set; }
\r
79 internal IDatabaseContext DatabaseContext { get; private set; }
\r
80 // /all properties...
\r
82 private bool objectTrackingEnabled = true;
\r
83 private bool deferredLoadingEnabled = true;
\r
85 private bool queryCacheEnabled = false;
\r
88 /// Disable the QueryCache: this is surely good for rarely used Select, since preparing
\r
89 /// the SelectQuery to be cached could require more time than build the sql from scratch.
\r
92 public bool QueryCacheEnabled
\r
94 get { return queryCacheEnabled; }
\r
95 set { queryCacheEnabled = value; }
\r
98 private IEntityTracker currentTransactionEntities;
\r
99 private IEntityTracker CurrentTransactionEntities
\r
103 if (this.currentTransactionEntities == null)
\r
105 if (this.ObjectTrackingEnabled)
\r
106 this.currentTransactionEntities = new EntityTracker();
\r
108 this.currentTransactionEntities = new DisabledEntityTracker();
\r
110 return this.currentTransactionEntities;
\r
114 private IEntityTracker allTrackedEntities;
\r
115 private IEntityTracker AllTrackedEntities
\r
119 if (this.allTrackedEntities == null)
\r
121 allTrackedEntities = ObjectTrackingEnabled
\r
122 ? (IEntityTracker) new EntityTracker()
\r
123 : (IEntityTracker) new DisabledEntityTracker();
\r
125 return this.allTrackedEntities;
\r
129 private IIdentityReaderFactory identityReaderFactory;
\r
130 private readonly IDictionary<Type, IIdentityReader> identityReaders = new Dictionary<Type, IIdentityReader>();
\r
133 /// The default behavior creates one MappingContext.
\r
136 internal virtual MappingContext _MappingContext { get; set; }
\r
139 internal IVendorProvider _VendorProvider { get; set; }
\r
141 public DataContext(IDbConnection connection, MappingSource mapping)
\r
143 Profiler.At("START DataContext(IDbConnection, MappingSource)");
\r
144 Init(new DatabaseContext(connection), mapping, null);
\r
145 Profiler.At("END DataContext(IDbConnection, MappingSource)");
\r
148 public DataContext(IDbConnection connection)
\r
150 Profiler.At("START DataContext(IDbConnection)");
\r
151 if (connection == null)
\r
152 throw new ArgumentNullException("connection");
\r
154 Init(new DatabaseContext(connection), null, null);
\r
155 Profiler.At("END DataContext(IDbConnection)");
\r
159 public DataContext(string fileOrServerOrConnection, MappingSource mapping)
\r
161 Profiler.At("START DataContext(string, MappingSource)");
\r
162 if (fileOrServerOrConnection == null)
\r
163 throw new ArgumentNullException("fileOrServerOrConnection");
\r
164 if (mapping == null)
\r
165 throw new ArgumentNullException("mapping");
\r
167 if (File.Exists(fileOrServerOrConnection))
\r
168 throw new NotImplementedException("File names not supported.");
\r
170 // Is this a decent server name check?
\r
171 // It assumes that the connection string will have at least 2
\r
172 // parameters (separated by ';')
\r
173 if (!fileOrServerOrConnection.Contains(";"))
\r
174 throw new NotImplementedException("Server name not supported.");
\r
176 // Assume it's a connection string...
\r
177 IVendor ivendor = GetVendor(ref fileOrServerOrConnection);
\r
179 IDbConnection dbConnection = ivendor.CreateDbConnection(fileOrServerOrConnection);
\r
180 Init(new DatabaseContext(dbConnection), mapping, ivendor);
\r
181 Profiler.At("END DataContext(string, MappingSource)");
\r
185 /// Construct DataContext, given a connectionString.
\r
186 /// To determine which DB type to go against, we look for 'DbLinqProvider=xxx' substring.
\r
187 /// If not found, we assume that we are dealing with MS Sql Server.
\r
189 /// Valid values are names of provider DLLs (or any other DLL containing an IVendor implementation)
\r
190 /// DbLinqProvider=Mysql
\r
191 /// DbLinqProvider=Oracle etc.
\r
193 /// <param name="connectionString">specifies file or server connection</param>
\r
195 public DataContext(string connectionString)
\r
197 Profiler.At("START DataContext(string)");
\r
198 IVendor ivendor = GetVendor(ref connectionString);
\r
200 IDbConnection dbConnection = ivendor.CreateDbConnection(connectionString);
\r
201 Init(new DatabaseContext(dbConnection), null, ivendor);
\r
203 Profiler.At("END DataContext(string)");
\r
206 private IVendor GetVendor(ref string connectionString)
\r
208 if (connectionString == null)
\r
209 throw new ArgumentNullException("connectionString");
\r
212 string vendorClassToLoad;
\r
213 GetVendorInfo(ref connectionString, out assy, out vendorClassToLoad);
\r
216 from type in assy.GetTypes()
\r
217 where type.Name.ToLowerInvariant() == vendorClassToLoad.ToLowerInvariant() &&
\r
218 type.GetInterfaces().Contains(typeof(IVendor)) &&
\r
219 type.GetConstructor(Type.EmptyTypes) != null
\r
223 throw new ArgumentException(string.Format("Found no IVendor class in assembly `{0}' named `{1}' having a default constructor.",
\r
224 assy.GetName().Name, vendorClassToLoad));
\r
226 else if (types.Count() > 1)
\r
228 throw new ArgumentException(string.Format("Found too many IVendor classes in assembly `{0}' named `{1}' having a default constructor.",
\r
229 assy.GetName().Name, vendorClassToLoad));
\r
231 return (IVendor) Activator.CreateInstance(types.First());
\r
234 private void GetVendorInfo(ref string connectionString, out Assembly assembly, out string typeName)
\r
236 System.Text.RegularExpressions.Regex reProvider
\r
237 = new System.Text.RegularExpressions.Regex(@"DbLinqProvider=([\w\.]+);?");
\r
239 string assemblyName = null;
\r
241 if (!reProvider.IsMatch(connectionString))
\r
243 vendor = "SqlServer";
\r
244 assemblyName = "DbLinq.SqlServer";
\r
248 var match = reProvider.Match(connectionString);
\r
249 vendor = match.Groups[1].Value;
\r
250 assemblyName = "DbLinq." + vendor;
\r
252 //plain DbLinq - non MONO:
\r
253 //IVendor classes are in DLLs such as "DbLinq.MySql.dll"
\r
254 if (vendor.Contains("."))
\r
256 //already fully qualified DLL name?
\r
257 throw new ArgumentException("Please provide a short name, such as 'MySql', not '" + vendor + "'");
\r
260 //shorten: "DbLinqProvider=X;Server=Y" -> ";Server=Y"
\r
261 connectionString = reProvider.Replace(connectionString, "");
\r
264 typeName = vendor + "Vendor";
\r
269 assembly = typeof (DataContext).Assembly; // System.Data.Linq.dll
\r
271 assembly = Assembly.Load(assemblyName);
\r
274 catch (Exception e)
\r
276 throw new ArgumentException(
\r
278 "Unable to load the `{0}' DbLinq vendor within assembly '{1}.dll'.",
\r
279 assemblyName, vendor),
\r
280 "connectionString", e);
\r
284 private void Init(IDatabaseContext databaseContext, MappingSource mappingSource, IVendor vendor)
\r
286 if (databaseContext == null)
\r
287 throw new ArgumentNullException("databaseContext");
\r
289 // Yes, .NET throws an NRE for this. Why it's not ArgumentNullException, I couldn't tell you.
\r
290 if (databaseContext.Connection.ConnectionString == null)
\r
291 throw new NullReferenceException();
\r
293 string connectionString = databaseContext.Connection.ConnectionString;
\r
294 _VendorProvider = ObjectFactory.Get<IVendorProvider>();
\r
295 Vendor = vendor ??
\r
296 (connectionString != null ? GetVendor(ref connectionString) : null) ??
\r
298 _VendorProvider.FindVendorByProviderType(typeof(DbLinq.Sqlite.SqliteSqlProvider));
\r
300 _VendorProvider.FindVendorByProviderType(typeof(SqlClient.Sql2005Provider));
\r
303 DatabaseContext = databaseContext;
\r
305 MemberModificationHandler = ObjectFactory.Create<IMemberModificationHandler>(); // not a singleton: object is stateful
\r
306 QueryBuilder = ObjectFactory.Get<IQueryBuilder>();
\r
307 QueryRunner = ObjectFactory.Get<IQueryRunner>();
\r
309 //EntityMap = ObjectFactory.Create<IEntityMap>();
\r
310 identityReaderFactory = ObjectFactory.Get<IIdentityReaderFactory>();
\r
312 _MappingContext = new MappingContext();
\r
314 // initialize the mapping information
\r
315 if (mappingSource == null)
\r
316 mappingSource = new AttributeMappingSource();
\r
317 Mapping = mappingSource.GetModel(GetType());
\r
321 /// Checks if the table is allready mapped or maps it if not.
\r
323 /// <param name="tableType">Type of the table.</param>
\r
324 /// <exception cref="InvalidOperationException">Thrown if the table is not mappable.</exception>
\r
325 private void CheckTableMapping(Type tableType)
\r
327 //This will throw an exception if the table is not found
\r
328 if(Mapping.GetTable(tableType) == null)
\r
330 throw new InvalidOperationException("The type '" + tableType.Name + "' is not mapped as a Table.");
\r
335 /// Returns a Table for the type TEntity.
\r
337 /// <exception cref="InvalidOperationException">If the type TEntity is not mappable as a Table.</exception>
\r
338 /// <typeparam name="TEntity">The table type.</typeparam>
\r
339 public Table<TEntity> GetTable<TEntity>() where TEntity : class
\r
341 return (Table<TEntity>)GetTable(typeof(TEntity));
\r
345 /// Returns a Table for the given type.
\r
347 /// <param name="type">The table type.</param>
\r
348 /// <exception cref="InvalidOperationException">If the type is not mappable as a Table.</exception>
\r
349 public ITable GetTable(Type type)
\r
351 Profiler.At("DataContext.GetTable(typeof({0}))", type != null ? type.Name : null);
\r
352 ITable tableExisting;
\r
353 if (_tableMap.TryGetValue(type, out tableExisting))
\r
354 return tableExisting;
\r
356 //Check for table mapping
\r
357 CheckTableMapping(type);
\r
359 var tableNew = Activator.CreateInstance(
\r
360 typeof(Table<>).MakeGenericType(type)
\r
361 , BindingFlags.NonPublic | BindingFlags.Instance
\r
363 , new object[] { this }
\r
364 , System.Globalization.CultureInfo.CurrentCulture) as ITable;
\r
366 _tableMap[type] = tableNew;
\r
370 public void SubmitChanges()
\r
372 SubmitChanges(ConflictMode.FailOnFirstConflict);
\r
378 /// <returns></returns>
\r
379 public bool DatabaseExists()
\r
383 return Vendor.Ping(this);
\r
392 /// Commits all pending changes to database
\r
394 /// <param name="failureMode"></param>
\r
395 public virtual void SubmitChanges(ConflictMode failureMode)
\r
397 if (this.objectTrackingEnabled == false)
\r
398 throw new InvalidOperationException("Object tracking is not enabled for the current data context instance.");
\r
399 using (DatabaseContext.OpenConnection()) //ConnMgr will close connection for us
\r
401 if (Transaction != null)
\r
402 SubmitChangesImpl(failureMode);
\r
405 using (IDbTransaction transaction = DatabaseContext.CreateTransaction())
\r
409 Transaction = (DbTransaction) transaction;
\r
410 SubmitChangesImpl(failureMode);
\r
411 // TODO: handle conflicts (which can only occur when concurrency mode is implemented)
\r
412 transaction.Commit();
\r
416 Transaction = null;
\r
423 void SubmitChangesImpl(ConflictMode failureMode)
\r
425 var queryContext = new QueryContext(this);
\r
427 // There's no sense in updating an entity when it's going to
\r
428 // be deleted in the current transaction, so do deletes first.
\r
429 foreach (var entityTrack in CurrentTransactionEntities.EnumerateAll().ToList())
\r
431 switch (entityTrack.EntityState)
\r
433 case EntityState.ToDelete:
\r
434 var deleteQuery = QueryBuilder.GetDeleteQuery(entityTrack.Entity, queryContext);
\r
435 QueryRunner.Delete(entityTrack.Entity, deleteQuery);
\r
437 UnregisterDelete(entityTrack.Entity);
\r
438 AllTrackedEntities.RegisterToDelete(entityTrack.Entity);
\r
439 AllTrackedEntities.RegisterDeleted(entityTrack.Entity);
\r
446 foreach (var entityTrack in CurrentTransactionEntities.EnumerateAll()
\r
447 .Concat(AllTrackedEntities.EnumerateAll())
\r
450 switch (entityTrack.EntityState)
\r
452 case EntityState.ToInsert:
\r
453 foreach (var toInsert in GetReferencedObjects(entityTrack.Entity))
\r
455 InsertEntity(toInsert, queryContext);
\r
458 case EntityState.ToWatch:
\r
459 foreach (var toUpdate in GetReferencedObjects(entityTrack.Entity))
\r
461 UpdateEntity(toUpdate, queryContext);
\r
465 throw new ArgumentOutOfRangeException();
\r
470 private IEnumerable<object> GetReferencedObjects(object value)
\r
472 var values = new EntitySet<object>();
\r
473 FillReferencedObjects(value, values);
\r
477 // Breadth-first traversal of an object graph
\r
478 private void FillReferencedObjects(object parent, EntitySet<object> values)
\r
480 if (parent == null)
\r
482 var children = new Queue<object>();
\r
483 children.Enqueue(parent);
\r
484 while (children.Count > 0)
\r
486 object value = children.Dequeue();
\r
488 IEnumerable<MetaAssociation> associationList = Mapping.GetMetaType(value.GetType()).Associations.Where(a => !a.IsForeignKey);
\r
489 if (associationList.Any())
\r
491 foreach (MetaAssociation association in associationList)
\r
493 var memberData = association.ThisMember;
\r
494 var entitySetValue = memberData.Member.GetMemberValue(value);
\r
496 if (entitySetValue != null)
\r
498 var hasLoadedOrAssignedValues = entitySetValue.GetType().GetProperty("HasLoadedOrAssignedValues");
\r
499 if (!((bool)hasLoadedOrAssignedValues.GetValue(entitySetValue, null)))
\r
500 continue; // execution deferred; ignore.
\r
501 foreach (var o in ((IEnumerable)entitySetValue))
\r
502 children.Enqueue(o);
\r
509 private void InsertEntity(object entity, QueryContext queryContext)
\r
511 var insertQuery = QueryBuilder.GetInsertQuery(entity, queryContext);
\r
512 QueryRunner.Insert(entity, insertQuery);
\r
514 UpdateReferencedObjects(entity);
\r
515 MoveToAllTrackedEntities(entity, true);
\r
518 private void UpdateEntity(object entity, QueryContext queryContext)
\r
520 if (!AllTrackedEntities.ContainsReference(entity))
\r
521 InsertEntity(entity, queryContext);
\r
522 else if (MemberModificationHandler.IsModified(entity, Mapping))
\r
524 var modifiedMembers = MemberModificationHandler.GetModifiedProperties(entity, Mapping);
\r
525 var updateQuery = QueryBuilder.GetUpdateQuery(entity, modifiedMembers, queryContext);
\r
526 QueryRunner.Update(entity, updateQuery, modifiedMembers);
\r
528 RegisterUpdateAgain(entity);
\r
529 UpdateReferencedObjects(entity);
\r
530 MoveToAllTrackedEntities(entity, false);
\r
534 private void UpdateReferencedObjects(object root)
\r
536 var metaType = Mapping.GetMetaType(root.GetType());
\r
537 foreach (var assoc in metaType.Associations)
\r
539 var memberData = assoc.ThisMember;
\r
540 //This is not correct - AutoSyncing applies to auto-updating columns, such as a TimeStamp, not to foreign key associations, which is always automatically synched
\r
541 //Confirmed against default .NET l2sql - association columns are always set, even if AutoSync==AutoSync.Never
\r
542 //if (memberData.Association.ThisKey.Any(m => (m.AutoSync != AutoSync.Always) && (m.AutoSync != sync)))
\r
544 var oks = memberData.Association.OtherKey.Select(m => m.StorageMember).ToList();
\r
545 if (oks.Count == 0)
\r
547 var pks = memberData.Association.ThisKey
\r
548 .Select(m => m.StorageMember.GetMemberValue(root))
\r
550 if (pks.Count != oks.Count)
\r
551 throw new InvalidOperationException(
\r
552 string.Format("Count of primary keys ({0}) doesn't match count of other keys ({1}).",
\r
553 pks.Count, oks.Count));
\r
554 var members = memberData.Member.GetMemberValue(root) as IEnumerable;
\r
555 if (members == null)
\r
557 foreach (var member in members)
\r
559 for (int i = 0; i < pks.Count; ++i)
\r
561 oks[i].SetMemberValue(member, pks[i]);
\r
567 private void MoveToAllTrackedEntities(object entity, bool insert)
\r
569 if (!ObjectTrackingEnabled)
\r
571 if (CurrentTransactionEntities.ContainsReference(entity))
\r
573 CurrentTransactionEntities.RegisterToDelete(entity);
\r
575 CurrentTransactionEntities.RegisterDeleted(entity);
\r
577 if (!AllTrackedEntities.ContainsReference(entity))
\r
579 var identityReader = _GetIdentityReader(entity.GetType());
\r
580 AllTrackedEntities.RegisterToWatch(entity, identityReader.GetIdentityKey(entity));
\r
585 /// TODO - allow generated methods to call into stored procedures
\r
588 internal IExecuteResult _ExecuteMethodCall(DataContext context, System.Reflection.MethodInfo method, params object[] sqlParams)
\r
590 using (DatabaseContext.OpenConnection())
\r
592 System.Data.Linq.IExecuteResult result = Vendor.ExecuteMethodCall(context, method, sqlParams);
\r
598 protected IExecuteResult ExecuteMethodCall(object instance, System.Reflection.MethodInfo methodInfo, params object[] parameters)
\r
600 throw new NotImplementedException();
\r
603 #region Identity management
\r
606 internal IIdentityReader _GetIdentityReader(Type t)
\r
608 IIdentityReader identityReader;
\r
609 if (!identityReaders.TryGetValue(t, out identityReader))
\r
611 identityReader = identityReaderFactory.GetReader(t, this);
\r
612 identityReaders[t] = identityReader;
\r
614 return identityReader;
\r
618 internal object _GetRegisteredEntity(object entity)
\r
620 // TODO: check what is faster: by identity or by ref
\r
621 var identityReader = _GetIdentityReader(entity.GetType());
\r
622 var identityKey = identityReader.GetIdentityKey(entity);
\r
623 if (identityKey == null) // if we don't have an entitykey here, it means that the entity has no PK
\r
626 var registeredEntityTrack =
\r
627 CurrentTransactionEntities.FindByIdentity(identityKey) ??
\r
628 AllTrackedEntities.FindByIdentity(identityKey);
\r
629 if (registeredEntityTrack != null)
\r
630 return registeredEntityTrack.Entity;
\r
634 //internal object GetRegisteredEntityByKey(IdentityKey identityKey)
\r
636 // return EntityMap[identityKey];
\r
640 /// Registers an entity in a watch state
\r
642 /// <param name="entity"></param>
\r
643 /// <returns></returns>
\r
645 internal object _GetOrRegisterEntity(object entity)
\r
647 var identityReader = _GetIdentityReader(entity.GetType());
\r
648 var identityKey = identityReader.GetIdentityKey(entity);
\r
649 SetEntitySetsQueries(entity);
\r
650 SetEntityRefQueries(entity);
\r
652 // if we have no identity, we can't track it
\r
653 if (identityKey == null)
\r
656 // try to find an already registered entity and return it
\r
657 var registeredEntityTrack =
\r
658 CurrentTransactionEntities.FindByIdentity(identityKey) ??
\r
659 AllTrackedEntities.FindByIdentity(identityKey);
\r
660 if (registeredEntityTrack != null)
\r
661 return registeredEntityTrack.Entity;
\r
663 // otherwise, register and return
\r
664 AllTrackedEntities.RegisterToWatch(entity, identityKey);
\r
668 readonly IDataMapper DataMapper = ObjectFactory.Get<IDataMapper>();
\r
669 private void SetEntityRefQueries(object entity)
\r
671 if (!this.deferredLoadingEnabled)
\r
674 // BUG: This is ignoring External Mappings from XmlMappingSource.
\r
676 Type thisType = entity.GetType();
\r
677 IEnumerable<MetaAssociation> associationList = Mapping.GetMetaType(entity.GetType()).Associations.Where(a => a.IsForeignKey);
\r
678 foreach (MetaAssociation association in associationList)
\r
680 //example of entityRef:Order.Employee
\r
681 var memberData = association.ThisMember;
\r
682 Type otherTableType = association.OtherType.Type;
\r
683 ParameterExpression p = Expression.Parameter(otherTableType, "other");
\r
685 var otherTable = GetTable(otherTableType);
\r
687 //ie:EmployeeTerritories.EmployeeID
\r
688 var foreignKeys = memberData.Association.ThisKey;
\r
689 BinaryExpression predicate = null;
\r
690 var otherPKs = memberData.Association.OtherKey;
\r
691 IEnumerator<MetaDataMember> otherPKEnumerator = otherPKs.GetEnumerator();
\r
693 if (otherPKs.Count != foreignKeys.Count)
\r
694 throw new InvalidOperationException("Foreign keys don't match ThisKey");
\r
695 foreach (MetaDataMember key in foreignKeys)
\r
697 otherPKEnumerator.MoveNext();
\r
699 var thisForeignKeyProperty = (PropertyInfo)key.Member;
\r
700 object thisForeignKeyValue = thisForeignKeyProperty.GetValue(entity, null);
\r
702 if (thisForeignKeyValue != null)
\r
704 BinaryExpression keyPredicate;
\r
705 if (!(thisForeignKeyProperty.PropertyType.IsNullable()))
\r
707 keyPredicate = Expression.Equal(Expression.MakeMemberAccess(p, otherPKEnumerator.Current.Member),
\r
708 Expression.Constant(thisForeignKeyValue));
\r
712 var ValueProperty = thisForeignKeyProperty.PropertyType.GetProperty("Value");
\r
713 keyPredicate = Expression.Equal(Expression.MakeMemberAccess(p, otherPKEnumerator.Current.Member),
\r
714 Expression.Constant(ValueProperty.GetValue(thisForeignKeyValue, null)));
\r
717 if (predicate == null)
\r
718 predicate = keyPredicate;
\r
720 predicate = Expression.And(predicate, keyPredicate);
\r
723 IEnumerable query = null;
\r
724 if (predicate != null)
\r
726 query = GetOtherTableQuery(predicate, p, otherTableType, otherTable) as IEnumerable;
\r
727 //it would be interesting surround the above query with a .Take(1) expression for performance.
\r
730 // If no separate Storage is specified, use the member directly
\r
731 MemberInfo storage = memberData.StorageMember;
\r
732 if (storage == null)
\r
733 storage = memberData.Member;
\r
735 // Check that the storage is a field or a writable property
\r
736 if (!(storage is FieldInfo) && !(storage is PropertyInfo && ((PropertyInfo)storage).CanWrite)) {
\r
737 throw new InvalidOperationException(String.Format(
\r
738 "Member {0}.{1} is not a field nor a writable property",
\r
739 storage.DeclaringType, storage.Name));
\r
742 Type storageType = storage.GetMemberType();
\r
744 object entityRefValue = null;
\r
746 entityRefValue = Activator.CreateInstance(storageType, query);
\r
748 entityRefValue = Activator.CreateInstance(storageType);
\r
750 storage.SetMemberValue(entity, entityRefValue);
\r
755 /// 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
756 /// Here we set the query source of each EntitySetProperty
\r
758 /// <param name="entity"></param>
\r
759 private void SetEntitySetsQueries(object entity)
\r
761 if (!this.deferredLoadingEnabled)
\r
764 // BUG: This is ignoring External Mappings from XmlMappingSource.
\r
766 IEnumerable<MetaAssociation> associationList = Mapping.GetMetaType(entity.GetType()).Associations.Where(a => !a.IsForeignKey);
\r
768 if (associationList.Any())
\r
770 foreach (MetaAssociation association in associationList)
\r
772 //example of entitySet: Employee.EmployeeTerritories
\r
773 var memberData = association.ThisMember;
\r
774 Type otherTableType = association.OtherType.Type;
\r
775 ParameterExpression p = Expression.Parameter(otherTableType, "other");
\r
777 //other table:EmployeeTerritories
\r
778 var otherTable = GetTable(otherTableType);
\r
780 var otherKeys = memberData.Association.OtherKey;
\r
781 var thisKeys = memberData.Association.ThisKey;
\r
782 if (otherKeys.Count != thisKeys.Count)
\r
783 throw new InvalidOperationException("This keys don't match OtherKey");
\r
784 BinaryExpression predicate = null;
\r
785 IEnumerator<MetaDataMember> thisKeyEnumerator = thisKeys.GetEnumerator();
\r
786 foreach (MetaDataMember otherKey in otherKeys)
\r
788 thisKeyEnumerator.MoveNext();
\r
789 //other table member:EmployeeTerritories.EmployeeID
\r
790 var otherTableMember = (PropertyInfo)otherKey.Member;
\r
792 BinaryExpression keyPredicate;
\r
793 if (!(otherTableMember.PropertyType.IsNullable()))
\r
795 keyPredicate = Expression.Equal(Expression.MakeMemberAccess(p, otherTableMember),
\r
796 Expression.Constant(thisKeyEnumerator.Current.Member.GetMemberValue(entity)));
\r
800 var ValueProperty = otherTableMember.PropertyType.GetProperty("Value");
\r
801 keyPredicate = Expression.Equal(Expression.MakeMemberAccess(
\r
802 Expression.MakeMemberAccess(p, otherTableMember),
\r
804 Expression.Constant(thisKeyEnumerator.Current.Member.GetMemberValue(entity)));
\r
806 if (predicate == null)
\r
807 predicate = keyPredicate;
\r
809 predicate = Expression.And(predicate, keyPredicate);
\r
812 var query = GetOtherTableQuery(predicate, p, otherTableType, otherTable);
\r
814 var entitySetValue = memberData.Member.GetMemberValue(entity);
\r
816 if (entitySetValue == null)
\r
818 entitySetValue = Activator.CreateInstance(memberData.Member.GetMemberType());
\r
819 memberData.Member.SetMemberValue(entity, entitySetValue);
\r
822 var hasLoadedOrAssignedValues = entitySetValue.GetType().GetProperty("HasLoadedOrAssignedValues");
\r
823 if ((bool)hasLoadedOrAssignedValues.GetValue(entitySetValue, null))
\r
826 var setSourceMethod = entitySetValue.GetType().GetMethod("SetSource");
\r
827 setSourceMethod.Invoke(entitySetValue, new[] { query });
\r
828 //employee.EmployeeTerritories.SetSource(Table[EmployeesTerritories].Where(other=>other.employeeID="WARTH"))
\r
833 private static MethodInfo _WhereMethod;
\r
834 internal object GetOtherTableQuery(Expression predicate, ParameterExpression parameter, Type otherTableType, IQueryable otherTable)
\r
836 if (_WhereMethod == null)
\r
837 System.Threading.Interlocked.CompareExchange (ref _WhereMethod, typeof(Queryable).GetMethods().First(m => m.Name == "Where"), null);
\r
839 //predicate: other.EmployeeID== "WARTH"
\r
840 Expression lambdaPredicate = Expression.Lambda(predicate, parameter);
\r
841 //lambdaPredicate: other=>other.EmployeeID== "WARTH"
\r
843 Expression call = Expression.Call(_WhereMethod.MakeGenericMethod(otherTableType), otherTable.Expression, lambdaPredicate);
\r
844 //Table[EmployeesTerritories].Where(other=>other.employeeID="WARTH")
\r
846 return otherTable.Provider.CreateQuery(call);
\r
851 #region Insert/Update/Delete management
\r
854 /// Registers an entity for insert
\r
856 /// <param name="entity"></param>
\r
857 internal void RegisterInsert(object entity)
\r
859 CurrentTransactionEntities.RegisterToInsert(entity);
\r
862 private void DoRegisterUpdate(object entity)
\r
864 if (entity == null)
\r
865 throw new ArgumentNullException("entity");
\r
867 if (!this.objectTrackingEnabled)
\r
870 var identityReader = _GetIdentityReader(entity.GetType());
\r
871 var identityKey = identityReader.GetIdentityKey(entity);
\r
872 // if we have no key, we can not watch
\r
873 if (identityKey == null || identityKey.Keys.Count == 0)
\r
876 AllTrackedEntities.RegisterToWatch(entity, identityKey);
\r
880 /// Registers an entity for update
\r
881 /// The entity will be updated only if some of its members have changed after the registration
\r
883 /// <param name="entity"></param>
\r
884 internal void RegisterUpdate(object entity)
\r
886 DoRegisterUpdate(entity);
\r
887 MemberModificationHandler.Register(entity, Mapping);
\r
891 /// Registers or re-registers an entity and clears its state
\r
893 /// <param name="entity"></param>
\r
894 /// <returns></returns>
\r
895 internal object Register(object entity)
\r
897 if (! this.objectTrackingEnabled)
\r
899 var registeredEntity = _GetOrRegisterEntity(entity);
\r
900 // the fact of registering again clears the modified state, so we're... clear with that
\r
901 MemberModificationHandler.Register(registeredEntity, Mapping);
\r
902 return registeredEntity;
\r
906 /// Registers an entity for update
\r
907 /// The entity will be updated only if some of its members have changed after the registration
\r
909 /// <param name="entity"></param>
\r
910 /// <param name="entityOriginalState"></param>
\r
911 internal void RegisterUpdate(object entity, object entityOriginalState)
\r
913 if (!this.objectTrackingEnabled)
\r
915 DoRegisterUpdate(entity);
\r
916 MemberModificationHandler.Register(entity, entityOriginalState, Mapping);
\r
920 /// Clears the current state, and marks the object as clean
\r
922 /// <param name="entity"></param>
\r
923 internal void RegisterUpdateAgain(object entity)
\r
925 if (!this.objectTrackingEnabled)
\r
927 MemberModificationHandler.ClearModified(entity, Mapping);
\r
931 /// Registers an entity for delete
\r
933 /// <param name="entity"></param>
\r
934 internal void RegisterDelete(object entity)
\r
936 if (!this.objectTrackingEnabled)
\r
938 CurrentTransactionEntities.RegisterToDelete(entity);
\r
942 /// Unregisters entity after deletion
\r
944 /// <param name="entity"></param>
\r
945 internal void UnregisterDelete(object entity)
\r
947 if (!this.objectTrackingEnabled)
\r
949 CurrentTransactionEntities.RegisterDeleted(entity);
\r
955 /// Changed object determine
\r
957 /// <returns>Lists of inserted, updated, deleted objects</returns>
\r
958 public ChangeSet GetChangeSet()
\r
960 var inserts = new List<object>();
\r
961 var updates = new List<object>();
\r
962 var deletes = new List<object>();
\r
963 foreach (var entityTrack in CurrentTransactionEntities.EnumerateAll()
\r
964 .Concat(AllTrackedEntities.EnumerateAll()))
\r
966 switch (entityTrack.EntityState)
\r
968 case EntityState.ToInsert:
\r
969 inserts.Add(entityTrack.Entity);
\r
971 case EntityState.ToWatch:
\r
972 if (MemberModificationHandler.IsModified(entityTrack.Entity, Mapping))
\r
973 updates.Add(entityTrack.Entity);
\r
975 case EntityState.ToDelete:
\r
976 deletes.Add(entityTrack.Entity);
\r
979 throw new ArgumentOutOfRangeException();
\r
982 return new ChangeSet(inserts, updates, deletes);
\r
986 /// use ExecuteCommand to call raw SQL
\r
988 public int ExecuteCommand(string command, params object[] parameters)
\r
990 var directQuery = QueryBuilder.GetDirectQuery(command, new QueryContext(this));
\r
991 return QueryRunner.Execute(directQuery, parameters);
\r
995 /// Execute raw SQL query and return object
\r
997 public IEnumerable<TResult> ExecuteQuery<TResult>(string query, params object[] parameters) where TResult : new()
\r
1000 throw new ArgumentNullException("query");
\r
1002 return CreateExecuteQueryEnumerable<TResult>(query, parameters);
\r
1005 private IEnumerable<TResult> CreateExecuteQueryEnumerable<TResult>(string query, object[] parameters)
\r
1006 where TResult : new()
\r
1008 foreach (TResult result in ExecuteQuery(typeof(TResult), query, parameters))
\r
1009 yield return result;
\r
1012 public IEnumerable ExecuteQuery(Type elementType, string query, params object[] parameters)
\r
1014 if (elementType == null)
\r
1015 throw new ArgumentNullException("elementType");
\r
1016 if (query == null)
\r
1017 throw new ArgumentNullException("query");
\r
1019 var queryContext = new QueryContext(this);
\r
1020 var directQuery = QueryBuilder.GetDirectQuery(query, queryContext);
\r
1021 return QueryRunner.ExecuteSelect(elementType, directQuery, parameters);
\r
1025 /// Gets or sets the load options
\r
1028 public DataLoadOptions LoadOptions
\r
1030 get { throw new NotImplementedException(); }
\r
1031 set { throw new NotImplementedException(); }
\r
1034 public DbTransaction Transaction {
\r
1035 get { return (DbTransaction) DatabaseContext.CurrentTransaction; }
\r
1036 set { DatabaseContext.CurrentTransaction = value; }
\r
1040 /// Runs the given reader and returns columns.
\r
1042 /// <typeparam name="TResult">The type of the result.</typeparam>
\r
1043 /// <param name="reader">The reader.</param>
\r
1044 /// <returns></returns>
\r
1045 public IEnumerable<TResult> Translate<TResult>(DbDataReader reader)
\r
1047 if (reader == null)
\r
1048 throw new ArgumentNullException("reader");
\r
1049 return CreateTranslateIterator<TResult>(reader);
\r
1052 IEnumerable<TResult> CreateTranslateIterator<TResult>(DbDataReader reader)
\r
1054 foreach (TResult result in Translate(typeof(TResult), reader))
\r
1055 yield return result;
\r
1058 public IMultipleResults Translate(DbDataReader reader)
\r
1060 throw new NotImplementedException();
\r
1063 public IEnumerable Translate(Type elementType, DbDataReader reader)
\r
1065 if (elementType == null)
\r
1066 throw new ArgumentNullException("elementType");
\r
1067 if (reader == null)
\r
1068 throw new ArgumentNullException("reader");
\r
1070 return QueryRunner.EnumerateResult(elementType, reader, this);
\r
1073 public void Dispose()
\r
1075 //connection closing should not be done here.
\r
1076 //read: http://msdn2.microsoft.com/en-us/library/bb292288.aspx
\r
1078 //We own the instance of MemberModificationHandler - we must unregister listeners of entities we attached to
\r
1079 MemberModificationHandler.UnregisterAll();
\r
1083 protected virtual void Dispose(bool disposing)
\r
1085 throw new NotImplementedException();
\r
1089 /// Creates a IDbDataAdapter. Used internally by Vendors
\r
1091 /// <returns></returns>
\r
1092 internal IDbDataAdapter CreateDataAdapter()
\r
1094 return DatabaseContext.CreateDataAdapter();
\r
1098 /// Sets a TextWriter where generated SQL commands are written
\r
1100 public TextWriter Log { get; set; }
\r
1103 /// Writes text on Log (if not null)
\r
1104 /// Internal helper
\r
1106 /// <param name="text"></param>
\r
1107 internal void WriteLog(string text)
\r
1110 Log.WriteLine(text);
\r
1114 /// Write an IDbCommand to Log (if non null)
\r
1116 /// <param name="command"></param>
\r
1117 internal void WriteLog(IDbCommand command)
\r
1121 Log.WriteLine(command.CommandText);
\r
1122 foreach (IDbDataParameter parameter in command.Parameters)
\r
1123 WriteLog(parameter);
\r
1125 Log.Write(" Context: {0}", Vendor.VendorName);
\r
1126 Log.Write(" Model: {0}", Mapping.GetType().Name);
\r
1127 Log.Write(" Build: {0}", Assembly.GetExecutingAssembly().GetName().Version);
\r
1133 /// Writes and IDbDataParameter to Log (if non null)
\r
1135 /// <param name="parameter"></param>
\r
1136 internal void WriteLog(IDbDataParameter parameter)
\r
1140 // -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [2]
\r
1141 // -- <name>: <direction> <type> (...) [<value>]
\r
1142 Log.WriteLine("-- {0}: {1} {2} (Size = {3}; Prec = {4}; Scale = {5}) [{6}]",
\r
1143 parameter.ParameterName, parameter.Direction, parameter.DbType,
\r
1144 parameter.Size, parameter.Precision, parameter.Scale, parameter.Value);
\r
1148 public bool ObjectTrackingEnabled
\r
1150 get { return this.objectTrackingEnabled; }
\r
1153 if (this.currentTransactionEntities != null && value != this.objectTrackingEnabled)
\r
1154 throw new InvalidOperationException("Data context options cannot be modified after results have been returned from a query.");
\r
1155 this.objectTrackingEnabled = value;
\r
1160 public int CommandTimeout
\r
1162 get { throw new NotImplementedException(); }
\r
1163 set { throw new NotImplementedException(); }
\r
1166 public bool DeferredLoadingEnabled
\r
1168 get { return this.deferredLoadingEnabled; }
\r
1171 if (this.currentTransactionEntities != null && value != this.deferredLoadingEnabled)
\r
1172 throw new InvalidOperationException("Data context options cannot be modified after results have been returned from a query.");
\r
1173 this.deferredLoadingEnabled = value;
\r
1178 public ChangeConflictCollection ChangeConflicts
\r
1180 get { throw new NotImplementedException(); }
\r
1184 public DbCommand GetCommand(IQueryable query)
\r
1186 DbCommand dbCommand = GetIDbCommand(query) as DbCommand;
\r
1187 if (dbCommand == null)
\r
1188 throw new InvalidOperationException();
\r
1194 public IDbCommand GetIDbCommand(IQueryable query)
\r
1196 if (query == null)
\r
1197 throw new ArgumentNullException("query");
\r
1199 var qp = query.Provider as QueryProvider;
\r
1201 throw new InvalidOperationException();
\r
1203 if (qp.ExpressionChain.Expressions.Count == 0)
\r
1204 qp.ExpressionChain.Expressions.Add(CreateDefaultQuery(query));
\r
1206 return qp.GetQuery(null).GetCommand().Command;
\r
1209 private Expression CreateDefaultQuery(IQueryable query)
\r
1211 // Manually create the expression tree for: IQueryable<TableType>.Select(e => e)
\r
1212 var identityParameter = Expression.Parameter(query.ElementType, "e");
\r
1213 var identityBody = Expression.Lambda(
\r
1214 typeof(Func<,>).MakeGenericType(query.ElementType, query.ElementType),
\r
1215 identityParameter,
\r
1216 new[] { identityParameter }
\r
1219 return Expression.Call(
\r
1220 typeof(Queryable),
\r
1222 new[] { query.ElementType, query.ElementType },
\r
1224 Expression.Quote(identityBody)
\r
1229 public void Refresh(RefreshMode mode, IEnumerable entities)
\r
1231 throw new NotImplementedException();
\r
1235 public void Refresh(RefreshMode mode, params object[] entities)
\r
1237 throw new NotImplementedException();
\r
1241 public void Refresh(RefreshMode mode, object entity)
\r
1243 throw new NotImplementedException();
\r
1247 public void DeleteDatabase()
\r
1249 throw new NotImplementedException();
\r
1253 public void CreateDatabase()
\r
1255 throw new NotImplementedException();
\r
1259 protected internal IQueryable<TResult> CreateMethodCallQuery<TResult>(object instance, MethodInfo methodInfo, params object[] parameters)
\r
1261 throw new NotImplementedException();
\r
1265 protected internal void ExecuteDynamicDelete(object entity)
\r
1267 throw new NotImplementedException();
\r
1271 protected internal void ExecuteDynamicInsert(object entity)
\r
1273 throw new NotImplementedException();
\r
1277 protected internal void ExecuteDynamicUpdate(object entity)
\r
1279 throw new NotImplementedException();
\r