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 assemblyFile = null;
\r
241 if (!reProvider.IsMatch(connectionString))
\r
243 vendor = "SqlServer";
\r
244 assemblyFile = "DbLinq.SqlServer.dll";
\r
248 var match = reProvider.Match(connectionString);
\r
249 vendor = match.Groups[1].Value;
\r
250 assemblyFile = "DbLinq." + vendor + ".dll";
\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 //TODO: check if DLL is already loaded?
\r
272 assembly = Assembly.LoadFrom(assemblyFile);
\r
275 catch (Exception e)
\r
277 throw new ArgumentException(
\r
279 "Unable to load the `{0}' DbLinq vendor within assembly `{1}'.",
\r
280 assemblyFile, vendor),
\r
281 "connectionString", e);
\r
285 private void Init(IDatabaseContext databaseContext, MappingSource mappingSource, IVendor vendor)
\r
287 if (databaseContext == null)
\r
288 throw new ArgumentNullException("databaseContext");
\r
290 // Yes, .NET throws an NRE for this. Why it's not ArgumentNullException, I couldn't tell you.
\r
291 if (databaseContext.Connection.ConnectionString == null)
\r
292 throw new NullReferenceException();
\r
294 string connectionString = databaseContext.Connection.ConnectionString;
\r
295 _VendorProvider = ObjectFactory.Get<IVendorProvider>();
\r
296 Vendor = vendor ??
\r
297 (connectionString != null ? GetVendor(ref connectionString) : null) ??
\r
298 _VendorProvider.FindVendorByProviderType(typeof(SqlClient.Sql2005Provider));
\r
300 DatabaseContext = databaseContext;
\r
302 MemberModificationHandler = ObjectFactory.Create<IMemberModificationHandler>(); // not a singleton: object is stateful
\r
303 QueryBuilder = ObjectFactory.Get<IQueryBuilder>();
\r
304 QueryRunner = ObjectFactory.Get<IQueryRunner>();
\r
306 //EntityMap = ObjectFactory.Create<IEntityMap>();
\r
307 identityReaderFactory = ObjectFactory.Get<IIdentityReaderFactory>();
\r
309 _MappingContext = new MappingContext();
\r
311 // initialize the mapping information
\r
312 if (mappingSource == null)
\r
313 mappingSource = new AttributeMappingSource();
\r
314 Mapping = mappingSource.GetModel(GetType());
\r
318 /// Checks if the table is allready mapped or maps it if not.
\r
320 /// <param name="tableType">Type of the table.</param>
\r
321 /// <exception cref="InvalidOperationException">Thrown if the table is not mappable.</exception>
\r
322 private void CheckTableMapping(Type tableType)
\r
324 //This will throw an exception if the table is not found
\r
325 if(Mapping.GetTable(tableType) == null)
\r
327 throw new InvalidOperationException("The type '" + tableType.Name + "' is not mapped as a Table.");
\r
332 /// Returns a Table for the type TEntity.
\r
334 /// <exception cref="InvalidOperationException">If the type TEntity is not mappable as a Table.</exception>
\r
335 /// <typeparam name="TEntity">The table type.</typeparam>
\r
336 public Table<TEntity> GetTable<TEntity>() where TEntity : class
\r
338 return (Table<TEntity>)GetTable(typeof(TEntity));
\r
342 /// Returns a Table for the given type.
\r
344 /// <param name="type">The table type.</param>
\r
345 /// <exception cref="InvalidOperationException">If the type is not mappable as a Table.</exception>
\r
346 public ITable GetTable(Type type)
\r
348 Profiler.At("DataContext.GetTable(typeof({0}))", type != null ? type.Name : null);
\r
349 ITable tableExisting;
\r
350 if (_tableMap.TryGetValue(type, out tableExisting))
\r
351 return tableExisting;
\r
353 //Check for table mapping
\r
354 CheckTableMapping(type);
\r
356 var tableNew = Activator.CreateInstance(
\r
357 typeof(Table<>).MakeGenericType(type)
\r
358 , BindingFlags.NonPublic | BindingFlags.Instance
\r
360 , new object[] { this }
\r
361 , System.Globalization.CultureInfo.CurrentCulture) as ITable;
\r
363 _tableMap[type] = tableNew;
\r
367 public void SubmitChanges()
\r
369 SubmitChanges(ConflictMode.FailOnFirstConflict);
\r
375 /// <returns></returns>
\r
376 public bool DatabaseExists()
\r
380 return Vendor.Ping(this);
\r
389 /// Commits all pending changes to database
\r
391 /// <param name="failureMode"></param>
\r
392 public virtual void SubmitChanges(ConflictMode failureMode)
\r
394 if (this.objectTrackingEnabled == false)
\r
395 throw new InvalidOperationException("Object tracking is not enabled for the current data context instance.");
\r
396 using (DatabaseContext.OpenConnection()) //ConnMgr will close connection for us
\r
397 using (IDatabaseTransaction transaction = DatabaseContext.Transaction())
\r
399 var queryContext = new QueryContext(this);
\r
401 // There's no sense in updating an entity when it's going to
\r
402 // be deleted in the current transaction, so do deletes first.
\r
403 foreach (var entityTrack in CurrentTransactionEntities.EnumerateAll().ToList())
\r
405 switch (entityTrack.EntityState)
\r
407 case EntityState.ToDelete:
\r
408 var deleteQuery = QueryBuilder.GetDeleteQuery(entityTrack.Entity, queryContext);
\r
409 QueryRunner.Delete(entityTrack.Entity, deleteQuery);
\r
411 UnregisterDelete(entityTrack.Entity);
\r
412 AllTrackedEntities.RegisterToDelete(entityTrack.Entity);
\r
413 AllTrackedEntities.RegisterDeleted(entityTrack.Entity);
\r
420 foreach (var entityTrack in CurrentTransactionEntities.EnumerateAll()
\r
421 .Concat(AllTrackedEntities.EnumerateAll())
\r
424 switch (entityTrack.EntityState)
\r
426 case EntityState.ToInsert:
\r
427 foreach (var toInsert in GetReferencedObjects(entityTrack.Entity))
\r
429 InsertEntity(toInsert, queryContext);
\r
432 case EntityState.ToWatch:
\r
433 foreach (var toUpdate in GetReferencedObjects(entityTrack.Entity))
\r
435 UpdateEntity(toUpdate, queryContext);
\r
439 throw new ArgumentOutOfRangeException();
\r
442 // TODO: handle conflicts (which can only occur when concurrency mode is implemented)
\r
443 transaction.Commit();
\r
447 private static IEnumerable<object> GetReferencedObjects(object value)
\r
449 var values = new EntitySet<object>();
\r
450 FillReferencedObjects(value, values);
\r
454 // Breadth-first traversal of an object graph
\r
455 private static void FillReferencedObjects(object value, EntitySet<object> values)
\r
460 var children = new List<object>();
\r
461 foreach (var p in value.GetType().GetProperties())
\r
463 var type = p.PropertyType.IsGenericType
\r
464 ? p.PropertyType.GetGenericTypeDefinition()
\r
466 if (type != null && p.CanRead && type == typeof(EntitySet<>) &&
\r
467 p.GetGetMethod().GetParameters().Length == 0)
\r
469 var set = p.GetValue(value, null);
\r
472 var hasLoadedOrAssignedValues = p.PropertyType.GetProperty("HasLoadedOrAssignedValues");
\r
473 if (!((bool)hasLoadedOrAssignedValues.GetValue(set, null)))
\r
474 continue; // execution deferred; ignore.
\r
475 foreach (var o in ((IEnumerable)set))
\r
479 foreach (var c in children)
\r
481 FillReferencedObjects(c, values);
\r
485 private void InsertEntity(object entity, QueryContext queryContext)
\r
487 var insertQuery = QueryBuilder.GetInsertQuery(entity, queryContext);
\r
488 QueryRunner.Insert(entity, insertQuery);
\r
490 UpdateReferencedObjects(entity, AutoSync.OnInsert);
\r
491 MoveToAllTrackedEntities(entity, true);
\r
494 private void UpdateEntity(object entity, QueryContext queryContext)
\r
496 if (!AllTrackedEntities.ContainsReference(entity))
\r
497 InsertEntity(entity, queryContext);
\r
498 else if (MemberModificationHandler.IsModified(entity, Mapping))
\r
500 var modifiedMembers = MemberModificationHandler.GetModifiedProperties(entity, Mapping);
\r
501 var updateQuery = QueryBuilder.GetUpdateQuery(entity, modifiedMembers, queryContext);
\r
502 QueryRunner.Update(entity, updateQuery, modifiedMembers);
\r
504 RegisterUpdateAgain(entity);
\r
505 UpdateReferencedObjects(entity, AutoSync.OnUpdate);
\r
506 MoveToAllTrackedEntities(entity, false);
\r
510 private void UpdateReferencedObjects(object root, AutoSync sync)
\r
512 var metaType = Mapping.GetMetaType(root.GetType());
\r
513 foreach (var assoc in metaType.Associations)
\r
515 var memberData = assoc.ThisMember;
\r
516 if (memberData.Association.ThisKey.Any(m => m.AutoSync != sync))
\r
518 var oks = memberData.Association.OtherKey.Select(m => m.StorageMember).ToList();
\r
519 if (oks.Count == 0)
\r
521 var pks = memberData.Association.ThisKey
\r
522 .Select(m => m.StorageMember.GetMemberValue(root))
\r
524 if (pks.Count != pks.Count)
\r
525 throw new InvalidOperationException(
\r
526 string.Format("Count of primary keys ({0}) doesn't match count of other keys ({1}).",
\r
527 pks.Count, oks.Count));
\r
528 var members = memberData.Member.GetMemberValue(root) as IEnumerable;
\r
529 if (members == null)
\r
531 foreach (var member in members)
\r
533 for (int i = 0; i < pks.Count; ++i)
\r
535 oks[i].SetMemberValue(member, pks[i]);
\r
541 private void MoveToAllTrackedEntities(object entity, bool insert)
\r
543 if (!ObjectTrackingEnabled)
\r
545 if (CurrentTransactionEntities.ContainsReference(entity))
\r
547 CurrentTransactionEntities.RegisterToDelete(entity);
\r
549 CurrentTransactionEntities.RegisterDeleted(entity);
\r
551 if (!AllTrackedEntities.ContainsReference(entity))
\r
553 var identityReader = _GetIdentityReader(entity.GetType());
\r
554 AllTrackedEntities.RegisterToWatch(entity, identityReader.GetIdentityKey(entity));
\r
559 /// TODO - allow generated methods to call into stored procedures
\r
562 internal IExecuteResult _ExecuteMethodCall(DataContext context, System.Reflection.MethodInfo method, params object[] sqlParams)
\r
564 using (DatabaseContext.OpenConnection())
\r
566 System.Data.Linq.IExecuteResult result = Vendor.ExecuteMethodCall(context, method, sqlParams);
\r
572 protected IExecuteResult ExecuteMethodCall(object instance, System.Reflection.MethodInfo methodInfo, params object[] parameters)
\r
574 throw new NotImplementedException();
\r
577 #region Identity management
\r
580 internal IIdentityReader _GetIdentityReader(Type t)
\r
582 IIdentityReader identityReader;
\r
583 if (!identityReaders.TryGetValue(t, out identityReader))
\r
585 identityReader = identityReaderFactory.GetReader(t, this);
\r
586 identityReaders[t] = identityReader;
\r
588 return identityReader;
\r
592 internal object _GetRegisteredEntity(object entity)
\r
594 // TODO: check what is faster: by identity or by ref
\r
595 var identityReader = _GetIdentityReader(entity.GetType());
\r
596 var identityKey = identityReader.GetIdentityKey(entity);
\r
597 if (identityKey == null) // if we don't have an entitykey here, it means that the entity has no PK
\r
600 var registeredEntityTrack =
\r
601 CurrentTransactionEntities.FindByIdentity(identityKey) ??
\r
602 AllTrackedEntities.FindByIdentity(identityKey);
\r
603 if (registeredEntityTrack != null)
\r
604 return registeredEntityTrack.Entity;
\r
608 //internal object GetRegisteredEntityByKey(IdentityKey identityKey)
\r
610 // return EntityMap[identityKey];
\r
614 /// Registers an entity in a watch state
\r
616 /// <param name="entity"></param>
\r
617 /// <returns></returns>
\r
619 internal object _GetOrRegisterEntity(object entity)
\r
621 var identityReader = _GetIdentityReader(entity.GetType());
\r
622 var identityKey = identityReader.GetIdentityKey(entity);
\r
623 SetEntitySetsQueries(entity);
\r
624 SetEntityRefQueries(entity);
\r
626 // if we have no identity, we can't track it
\r
627 if (identityKey == null)
\r
630 // try to find an already registered entity and return it
\r
631 var registeredEntityTrack =
\r
632 CurrentTransactionEntities.FindByIdentity(identityKey) ??
\r
633 AllTrackedEntities.FindByIdentity(identityKey);
\r
634 if (registeredEntityTrack != null)
\r
635 return registeredEntityTrack.Entity;
\r
637 // otherwise, register and return
\r
638 AllTrackedEntities.RegisterToWatch(entity, identityKey);
\r
642 readonly IDataMapper DataMapper = ObjectFactory.Get<IDataMapper>();
\r
643 private void SetEntityRefQueries(object entity)
\r
645 if (!this.deferredLoadingEnabled)
\r
648 // BUG: This is ignoring External Mappings from XmlMappingSource.
\r
650 Type thisType = entity.GetType();
\r
651 IEnumerable<MetaAssociation> associationList = Mapping.GetMetaType(entity.GetType()).Associations.Where(a => a.IsForeignKey);
\r
652 foreach (MetaAssociation association in associationList)
\r
654 //example of entityRef:Order.Employee
\r
655 var memberData = association.ThisMember;
\r
656 Type otherTableType = association.OtherType.Type;
\r
657 ParameterExpression p = Expression.Parameter(otherTableType, "other");
\r
659 var otherTable = GetTable(otherTableType);
\r
661 //ie:EmployeeTerritories.EmployeeID
\r
662 var foreignKeys = memberData.Association.ThisKey;
\r
663 BinaryExpression predicate = null;
\r
664 var otherPKs = memberData.Association.OtherKey;
\r
665 IEnumerator<MetaDataMember> otherPKEnumerator = otherPKs.GetEnumerator();
\r
667 if (otherPKs.Count != foreignKeys.Count)
\r
668 throw new InvalidOperationException("Foreign keys don't match ThisKey");
\r
669 foreach (MetaDataMember key in foreignKeys)
\r
671 otherPKEnumerator.MoveNext();
\r
673 var thisForeignKeyProperty = (PropertyInfo)key.Member;
\r
674 object thisForeignKeyValue = thisForeignKeyProperty.GetValue(entity, null);
\r
676 if (thisForeignKeyValue != null)
\r
678 BinaryExpression keyPredicate;
\r
679 if (!(thisForeignKeyProperty.PropertyType.IsNullable()))
\r
681 keyPredicate = Expression.Equal(Expression.MakeMemberAccess(p, otherPKEnumerator.Current.Member),
\r
682 Expression.Constant(thisForeignKeyValue));
\r
686 var ValueProperty = thisForeignKeyProperty.PropertyType.GetProperty("Value");
\r
687 keyPredicate = Expression.Equal(Expression.MakeMemberAccess(p, otherPKEnumerator.Current.Member),
\r
688 Expression.Constant(ValueProperty.GetValue(thisForeignKeyValue, null)));
\r
691 if (predicate == null)
\r
692 predicate = keyPredicate;
\r
694 predicate = Expression.And(predicate, keyPredicate);
\r
697 IEnumerable query = null;
\r
698 if (predicate != null)
\r
700 query = GetOtherTableQuery(predicate, p, otherTableType, otherTable) as IEnumerable;
\r
701 //it would be interesting surround the above query with a .Take(1) expression for performance.
\r
705 FieldInfo entityRefField = (FieldInfo)memberData.StorageMember;
\r
706 object entityRefValue = null;
\r
708 entityRefValue = Activator.CreateInstance(entityRefField.FieldType, query);
\r
710 entityRefValue = Activator.CreateInstance(entityRefField.FieldType);
\r
711 entityRefField.SetValue(entity, entityRefValue);
\r
716 /// 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
717 /// Here we set the query source of each EntitySetProperty
\r
719 /// <param name="entity"></param>
\r
720 private void SetEntitySetsQueries(object entity)
\r
722 if (!this.deferredLoadingEnabled)
\r
725 // BUG: This is ignoring External Mappings from XmlMappingSource.
\r
727 IEnumerable<MetaAssociation> associationList = Mapping.GetMetaType(entity.GetType()).Associations.Where(a => !a.IsForeignKey);
\r
729 if (associationList.Any())
\r
731 foreach (MetaAssociation association in associationList)
\r
733 //example of entitySet: Employee.EmployeeTerritories
\r
734 var memberData = association.ThisMember;
\r
735 Type otherTableType = association.OtherType.Type;
\r
736 ParameterExpression p = Expression.Parameter(otherTableType, "other");
\r
738 //other table:EmployeeTerritories
\r
739 var otherTable = GetTable(otherTableType);
\r
741 var otherKeys = memberData.Association.OtherKey;
\r
742 var thisKeys = memberData.Association.ThisKey;
\r
743 if (otherKeys.Count != thisKeys.Count)
\r
744 throw new InvalidOperationException("This keys don't match OtherKey");
\r
745 BinaryExpression predicate = null;
\r
746 IEnumerator<MetaDataMember> thisKeyEnumerator = thisKeys.GetEnumerator();
\r
747 foreach (MetaDataMember otherKey in otherKeys)
\r
749 thisKeyEnumerator.MoveNext();
\r
750 //other table member:EmployeeTerritories.EmployeeID
\r
751 var otherTableMember = (PropertyInfo)otherKey.Member;
\r
753 BinaryExpression keyPredicate;
\r
754 if (!(otherTableMember.PropertyType.IsNullable()))
\r
756 keyPredicate = Expression.Equal(Expression.MakeMemberAccess(p, otherTableMember),
\r
757 Expression.Constant(thisKeyEnumerator.Current.Member.GetMemberValue(entity)));
\r
761 var ValueProperty = otherTableMember.PropertyType.GetProperty("Value");
\r
762 keyPredicate = Expression.Equal(Expression.MakeMemberAccess(
\r
763 Expression.MakeMemberAccess(p, otherTableMember),
\r
765 Expression.Constant(thisKeyEnumerator.Current.Member.GetMemberValue(entity)));
\r
767 if (predicate == null)
\r
768 predicate = keyPredicate;
\r
770 predicate = Expression.And(predicate, keyPredicate);
\r
773 var query = GetOtherTableQuery(predicate, p, otherTableType, otherTable);
\r
775 var entitySetValue = memberData.Member.GetMemberValue(entity);
\r
777 if (entitySetValue == null)
\r
779 entitySetValue = Activator.CreateInstance(memberData.Member.GetMemberType());
\r
780 memberData.Member.SetMemberValue(entity, entitySetValue);
\r
783 var hasLoadedOrAssignedValues = entitySetValue.GetType().GetProperty("HasLoadedOrAssignedValues");
\r
784 if ((bool)hasLoadedOrAssignedValues.GetValue(entitySetValue, null))
\r
787 var setSourceMethod = entitySetValue.GetType().GetMethod("SetSource");
\r
788 setSourceMethod.Invoke(entitySetValue, new[] { query });
\r
789 //employee.EmployeeTerritories.SetSource(Table[EmployeesTerritories].Where(other=>other.employeeID="WARTH"))
\r
794 private object GetOtherTableQuery(Expression predicate, ParameterExpression parameter, Type otherTableType, IQueryable otherTable)
\r
796 //predicate: other.EmployeeID== "WARTH"
\r
797 Expression lambdaPredicate = Expression.Lambda(predicate, parameter);
\r
798 //lambdaPredicate: other=>other.EmployeeID== "WARTH"
\r
800 var whereMethod = typeof(Queryable)
\r
801 .GetMethods().First(m => m.Name == "Where")
\r
802 .MakeGenericMethod(otherTableType);
\r
805 Expression call = Expression.Call(whereMethod, otherTable.Expression, lambdaPredicate);
\r
806 //Table[EmployeesTerritories].Where(other=>other.employeeID="WARTH")
\r
808 return otherTable.Provider.CreateQuery(call);
\r
813 #region Insert/Update/Delete management
\r
816 /// Registers an entity for insert
\r
818 /// <param name="entity"></param>
\r
819 internal void RegisterInsert(object entity)
\r
821 CurrentTransactionEntities.RegisterToInsert(entity);
\r
825 /// Registers an entity for update
\r
826 /// The entity will be updated only if some of its members have changed after the registration
\r
828 /// <param name="entity"></param>
\r
829 internal void RegisterUpdate(object entity)
\r
831 if (entity == null)
\r
832 throw new ArgumentNullException("entity");
\r
834 if (!this.objectTrackingEnabled)
\r
837 var identityReader = _GetIdentityReader(entity.GetType());
\r
838 var identityKey = identityReader.GetIdentityKey(entity);
\r
839 // if we have no key, we can not watch
\r
840 if (identityKey == null || identityKey.Keys.Count == 0)
\r
843 AllTrackedEntities.RegisterToWatch(entity, identityKey);
\r
847 /// Registers or re-registers an entity and clears its state
\r
849 /// <param name="entity"></param>
\r
850 /// <returns></returns>
\r
851 internal object Register(object entity)
\r
853 if (! this.objectTrackingEnabled)
\r
855 var registeredEntity = _GetOrRegisterEntity(entity);
\r
856 // the fact of registering again clears the modified state, so we're... clear with that
\r
857 MemberModificationHandler.Register(registeredEntity, Mapping);
\r
858 return registeredEntity;
\r
862 /// Registers an entity for update
\r
863 /// The entity will be updated only if some of its members have changed after the registration
\r
865 /// <param name="entity"></param>
\r
866 /// <param name="entityOriginalState"></param>
\r
867 internal void RegisterUpdate(object entity, object entityOriginalState)
\r
869 if (!this.objectTrackingEnabled)
\r
871 RegisterUpdate(entity);
\r
872 MemberModificationHandler.Register(entity, entityOriginalState, Mapping);
\r
876 /// Clears the current state, and marks the object as clean
\r
878 /// <param name="entity"></param>
\r
879 internal void RegisterUpdateAgain(object entity)
\r
881 if (!this.objectTrackingEnabled)
\r
883 MemberModificationHandler.ClearModified(entity, Mapping);
\r
887 /// Registers an entity for delete
\r
889 /// <param name="entity"></param>
\r
890 internal void RegisterDelete(object entity)
\r
892 if (!this.objectTrackingEnabled)
\r
894 CurrentTransactionEntities.RegisterToDelete(entity);
\r
898 /// Unregisters entity after deletion
\r
900 /// <param name="entity"></param>
\r
901 internal void UnregisterDelete(object entity)
\r
903 if (!this.objectTrackingEnabled)
\r
905 CurrentTransactionEntities.RegisterDeleted(entity);
\r
911 /// Changed object determine
\r
913 /// <returns>Lists of inserted, updated, deleted objects</returns>
\r
914 public ChangeSet GetChangeSet()
\r
916 var inserts = new List<object>();
\r
917 var updates = new List<object>();
\r
918 var deletes = new List<object>();
\r
919 foreach (var entityTrack in CurrentTransactionEntities.EnumerateAll()
\r
920 .Concat(AllTrackedEntities.EnumerateAll()))
\r
922 switch (entityTrack.EntityState)
\r
924 case EntityState.ToInsert:
\r
925 inserts.Add(entityTrack.Entity);
\r
927 case EntityState.ToWatch:
\r
928 if (MemberModificationHandler.IsModified(entityTrack.Entity, Mapping))
\r
929 updates.Add(entityTrack.Entity);
\r
931 case EntityState.ToDelete:
\r
932 deletes.Add(entityTrack.Entity);
\r
935 throw new ArgumentOutOfRangeException();
\r
938 return new ChangeSet(inserts, updates, deletes);
\r
942 /// use ExecuteCommand to call raw SQL
\r
944 public int ExecuteCommand(string command, params object[] parameters)
\r
946 var directQuery = QueryBuilder.GetDirectQuery(command, new QueryContext(this));
\r
947 return QueryRunner.Execute(directQuery, parameters);
\r
951 /// Execute raw SQL query and return object
\r
953 public IEnumerable<TResult> ExecuteQuery<TResult>(string query, params object[] parameters) where TResult : class, new()
\r
956 throw new ArgumentNullException("query");
\r
958 return CreateExecuteQueryEnumerable<TResult>(query, parameters);
\r
961 private IEnumerable<TResult> CreateExecuteQueryEnumerable<TResult>(string query, object[] parameters)
\r
962 where TResult : class, new()
\r
964 foreach (TResult result in ExecuteQuery(typeof(TResult), query, parameters))
\r
965 yield return result;
\r
968 public IEnumerable ExecuteQuery(Type elementType, string query, params object[] parameters)
\r
970 if (elementType == null)
\r
971 throw new ArgumentNullException("elementType");
\r
973 throw new ArgumentNullException("query");
\r
975 var queryContext = new QueryContext(this);
\r
976 var directQuery = QueryBuilder.GetDirectQuery(query, queryContext);
\r
977 return QueryRunner.ExecuteSelect(elementType, directQuery, parameters);
\r
981 /// Gets or sets the load options
\r
984 public DataLoadOptions LoadOptions
\r
986 get { throw new NotImplementedException(); }
\r
987 set { throw new NotImplementedException(); }
\r
990 public DbTransaction Transaction { get; set; }
\r
993 /// Runs the given reader and returns columns.
\r
995 /// <typeparam name="TResult">The type of the result.</typeparam>
\r
996 /// <param name="reader">The reader.</param>
\r
997 /// <returns></returns>
\r
998 public IEnumerable<TResult> Translate<TResult>(DbDataReader reader)
\r
1000 if (reader == null)
\r
1001 throw new ArgumentNullException("reader");
\r
1002 return CreateTranslateIterator<TResult>(reader);
\r
1005 IEnumerable<TResult> CreateTranslateIterator<TResult>(DbDataReader reader)
\r
1007 foreach (TResult result in Translate(typeof(TResult), reader))
\r
1008 yield return result;
\r
1011 public IMultipleResults Translate(DbDataReader reader)
\r
1013 throw new NotImplementedException();
\r
1016 public IEnumerable Translate(Type elementType, DbDataReader reader)
\r
1018 if (elementType == null)
\r
1019 throw new ArgumentNullException("elementType");
\r
1020 if (reader == null)
\r
1021 throw new ArgumentNullException("reader");
\r
1023 return QueryRunner.EnumerateResult(elementType, reader, this);
\r
1026 public void Dispose()
\r
1028 //connection closing should not be done here.
\r
1029 //read: http://msdn2.microsoft.com/en-us/library/bb292288.aspx
\r
1033 protected virtual void Dispose(bool disposing)
\r
1035 throw new NotImplementedException();
\r
1039 /// Creates a IDbDataAdapter. Used internally by Vendors
\r
1041 /// <returns></returns>
\r
1042 internal IDbDataAdapter CreateDataAdapter()
\r
1044 return DatabaseContext.CreateDataAdapter();
\r
1048 /// Sets a TextWriter where generated SQL commands are written
\r
1050 public TextWriter Log { get; set; }
\r
1053 /// Writes text on Log (if not null)
\r
1054 /// Internal helper
\r
1056 /// <param name="text"></param>
\r
1057 internal void WriteLog(string text)
\r
1060 Log.WriteLine(text);
\r
1064 /// Write an IDbCommand to Log (if non null)
\r
1066 /// <param name="command"></param>
\r
1067 internal void WriteLog(IDbCommand command)
\r
1071 Log.WriteLine(command.CommandText);
\r
1072 foreach (IDbDataParameter parameter in command.Parameters)
\r
1073 WriteLog(parameter);
\r
1075 Log.Write(" Context: {0}", Vendor.VendorName);
\r
1076 Log.Write(" Model: {0}", Mapping.GetType().Name);
\r
1077 Log.Write(" Build: {0}", Assembly.GetExecutingAssembly().GetName().Version);
\r
1083 /// Writes and IDbDataParameter to Log (if non null)
\r
1085 /// <param name="parameter"></param>
\r
1086 internal void WriteLog(IDbDataParameter parameter)
\r
1090 // -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [2]
\r
1091 // -- <name>: <direction> <type> (...) [<value>]
\r
1092 Log.WriteLine("-- {0}: {1} {2} (Size = {3}; Prec = {4}; Scale = {5}) [{6}]",
\r
1093 parameter.ParameterName, parameter.Direction, parameter.DbType,
\r
1094 parameter.Size, parameter.Precision, parameter.Scale, parameter.Value);
\r
1098 public bool ObjectTrackingEnabled
\r
1100 get { return this.objectTrackingEnabled; }
\r
1103 if (this.currentTransactionEntities != null && value != this.objectTrackingEnabled)
\r
1104 throw new InvalidOperationException("Data context options cannot be modified after results have been returned from a query.");
\r
1105 this.objectTrackingEnabled = value;
\r
1110 public int CommandTimeout
\r
1112 get { throw new NotImplementedException(); }
\r
1113 set { throw new NotImplementedException(); }
\r
1116 public bool DeferredLoadingEnabled
\r
1118 get { return this.deferredLoadingEnabled; }
\r
1121 if (this.currentTransactionEntities != null && value != this.deferredLoadingEnabled)
\r
1122 throw new InvalidOperationException("Data context options cannot be modified after results have been returned from a query.");
\r
1123 this.deferredLoadingEnabled = value;
\r
1128 public ChangeConflictCollection ChangeConflicts
\r
1130 get { throw new NotImplementedException(); }
\r
1134 public DbCommand GetCommand(IQueryable query)
\r
1136 DbCommand dbCommand = GetIDbCommand(query) as DbCommand;
\r
1137 if (dbCommand == null)
\r
1138 throw new InvalidOperationException();
\r
1144 public IDbCommand GetIDbCommand(IQueryable query)
\r
1146 if (query == null)
\r
1147 throw new ArgumentNullException("query");
\r
1149 var qp = query.Provider as QueryProvider;
\r
1151 throw new InvalidOperationException();
\r
1153 if (qp.ExpressionChain.Expressions.Count == 0)
\r
1154 qp.ExpressionChain.Expressions.Add(CreateDefaultQuery(query));
\r
1156 return qp.GetQuery(null).GetCommand().Command;
\r
1159 private Expression CreateDefaultQuery(IQueryable query)
\r
1161 // Manually create the expression tree for: IQueryable<TableType>.Select(e => e)
\r
1162 var identityParameter = Expression.Parameter(query.ElementType, "e");
\r
1163 var identityBody = Expression.Lambda(
\r
1164 typeof(Func<,>).MakeGenericType(query.ElementType, query.ElementType),
\r
1165 identityParameter,
\r
1166 new[] { identityParameter }
\r
1169 return Expression.Call(
\r
1170 typeof(Queryable),
\r
1172 new[] { query.ElementType, query.ElementType },
\r
1174 Expression.Quote(identityBody)
\r
1179 public void Refresh(RefreshMode mode, IEnumerable entities)
\r
1181 throw new NotImplementedException();
\r
1185 public void Refresh(RefreshMode mode, params object[] entities)
\r
1187 throw new NotImplementedException();
\r
1191 public void Refresh(RefreshMode mode, object entity)
\r
1193 throw new NotImplementedException();
\r
1197 public void DeleteDatabase()
\r
1199 throw new NotImplementedException();
\r
1203 public void CreateDatabase()
\r
1205 throw new NotImplementedException();
\r
1209 protected internal IQueryable<TResult> CreateMethodCallQuery<TResult>(object instance, MethodInfo methodInfo, params object[] parameters)
\r
1211 throw new NotImplementedException();
\r
1215 protected internal void ExecuteDynamicDelete(object entity)
\r
1217 throw new NotImplementedException();
\r
1221 protected internal void ExecuteDynamicInsert(object entity)
\r
1223 throw new NotImplementedException();
\r
1227 protected internal void ExecuteDynamicUpdate(object entity)
\r
1229 throw new NotImplementedException();
\r