using System.Collections;\r
using System.Data;\r
using System.Data.Common;\r
+using System.Data.Linq;\r
using System.Data.Linq.Mapping;\r
+using System.Linq.Expressions;\r
using System.Collections.Generic;\r
using System.IO;\r
using System.Linq;\r
using System.Reflection;\r
+using System.Reflection.Emit;\r
\r
#if MONO_STRICT\r
-using System.Data.Linq.Implementation;\r
-using System.Data.Linq.Sugar;\r
-using System.Data.Linq.Identity;\r
-using DbLinq.Util;\r
-using AttributeMappingSource = System.Data.Linq.Mapping.AttributeMappingSource;\r
-using MappingContext = System.Data.Linq.Mapping.MappingContext;\r
-using DbLinq;\r
+using AttributeMappingSource = System.Data.Linq.Mapping.AttributeMappingSource;\r
#else\r
-using DbLinq.Data.Linq.Implementation;\r
-using DbLinq.Data.Linq.Sugar;\r
-using DbLinq.Data.Linq.Identity;\r
-using DbLinq.Util;\r
-using AttributeMappingSource = DbLinq.Data.Linq.Mapping.AttributeMappingSource;\r
-using MappingContext = DbLinq.Data.Linq.Mapping.MappingContext;\r
-using System.Data.Linq;\r
+using AttributeMappingSource = DbLinq.Data.Linq.Mapping.AttributeMappingSource;\r
#endif\r
\r
-using DbLinq.Factory;\r
-using DbLinq.Vendor;\r
+using DbLinq;\r
+using DbLinq.Data.Linq;\r
using DbLinq.Data.Linq.Database;\r
using DbLinq.Data.Linq.Database.Implementation;\r
-using System.Linq.Expressions;\r
-using System.Reflection.Emit;\r
+using DbLinq.Data.Linq.Identity;\r
+using DbLinq.Data.Linq.Implementation;\r
+using DbLinq.Data.Linq.Mapping;\r
+using DbLinq.Data.Linq.Sugar;\r
+using DbLinq.Factory;\r
+using DbLinq.Util;\r
+using DbLinq.Vendor;\r
\r
#if MONO_STRICT\r
namespace System.Data.Linq\r
public partial class DataContext : IDisposable\r
{\r
//private readonly Dictionary<string, ITable> _tableMap = new Dictionary<string, ITable>();\r
- private readonly Dictionary<Type, ITable> _tableMap = new Dictionary<Type, ITable>();\r
+ private readonly Dictionary<Type, ITable> _tableMap = new Dictionary<Type, ITable>();\r
\r
public MetaModel Mapping { get; private set; }\r
// PC question: at ctor, we get a IDbConnection and the Connection property exposes a DbConnection\r
internal IDatabaseContext DatabaseContext { get; private set; }\r
// /all properties...\r
\r
- private readonly EntityTracker entityTracker = new EntityTracker();\r
+ private bool objectTrackingEnabled = true;\r
+ private bool deferredLoadingEnabled = true;\r
+\r
+ private bool queryCacheEnabled = false;\r
+\r
+ /// <summary>\r
+ /// Disable the QueryCache: this is surely good for rarely used Select, since preparing\r
+ /// the SelectQuery to be cached could require more time than build the sql from scratch.\r
+ /// </summary>\r
+ [DBLinqExtended]\r
+ public bool QueryCacheEnabled \r
+ {\r
+ get { return queryCacheEnabled; }\r
+ set { queryCacheEnabled = value; }\r
+ }\r
+\r
+ private IEntityTracker currentTransactionEntities;\r
+ private IEntityTracker CurrentTransactionEntities\r
+ {\r
+ get\r
+ {\r
+ if (this.currentTransactionEntities == null)\r
+ {\r
+ if (this.ObjectTrackingEnabled)\r
+ this.currentTransactionEntities = new EntityTracker();\r
+ else\r
+ this.currentTransactionEntities = new DisabledEntityTracker();\r
+ }\r
+ return this.currentTransactionEntities;\r
+ }\r
+ }\r
+\r
+ private IEntityTracker allTrackedEntities;\r
+ private IEntityTracker AllTrackedEntities\r
+ {\r
+ get\r
+ {\r
+ if (this.allTrackedEntities == null)\r
+ {\r
+ allTrackedEntities = ObjectTrackingEnabled\r
+ ? (IEntityTracker) new EntityTracker()\r
+ : (IEntityTracker) new DisabledEntityTracker();\r
+ }\r
+ return this.allTrackedEntities;\r
+ }\r
+ }\r
\r
private IIdentityReaderFactory identityReaderFactory;\r
private readonly IDictionary<Type, IIdentityReader> identityReaders = new Dictionary<Type, IIdentityReader>();\r
\r
public DataContext(IDbConnection connection, MappingSource mapping)\r
{\r
+ Profiler.At("START DataContext(IDbConnection, MappingSource)");\r
Init(new DatabaseContext(connection), mapping, null);\r
+ Profiler.At("END DataContext(IDbConnection, MappingSource)");\r
}\r
\r
public DataContext(IDbConnection connection)\r
{\r
+ Profiler.At("START DataContext(IDbConnection)");\r
if (connection == null)\r
throw new ArgumentNullException("connection");\r
\r
Init(new DatabaseContext(connection), null, null);\r
+ Profiler.At("END DataContext(IDbConnection)");\r
}\r
\r
[DbLinqToDo]\r
public DataContext(string fileOrServerOrConnection, MappingSource mapping)\r
{\r
- throw new NotImplementedException();\r
+ Profiler.At("START DataContext(string, MappingSource)");\r
+ if (fileOrServerOrConnection == null)\r
+ throw new ArgumentNullException("fileOrServerOrConnection");\r
+ if (mapping == null)\r
+ throw new ArgumentNullException("mapping");\r
+\r
+ if (File.Exists(fileOrServerOrConnection))\r
+ throw new NotImplementedException("File names not supported.");\r
+\r
+ // Is this a decent server name check?\r
+ // It assumes that the connection string will have at least 2\r
+ // parameters (separated by ';')\r
+ if (!fileOrServerOrConnection.Contains(";"))\r
+ throw new NotImplementedException("Server name not supported.");\r
+\r
+ // Assume it's a connection string...\r
+ IVendor ivendor = GetVendor(ref fileOrServerOrConnection);\r
+\r
+ IDbConnection dbConnection = ivendor.CreateDbConnection(fileOrServerOrConnection);\r
+ Init(new DatabaseContext(dbConnection), mapping, ivendor);\r
+ Profiler.At("END DataContext(string, MappingSource)");\r
}\r
\r
/// <summary>\r
/// </summary>\r
/// <param name="connectionString">specifies file or server connection</param>\r
[DbLinqToDo]\r
- public DataContext(string connectionString)\r
+ public DataContext(string fileOrServerOrConnection)\r
{\r
- IVendor ivendor = GetVendor(connectionString);\r
+ Profiler.At("START DataContext(string)");\r
+ IVendor ivendor = GetVendor(ref fileOrServerOrConnection);\r
\r
- IDbConnection dbConnection = ivendor.CreateDbConnection(connectionString);\r
+ IDbConnection dbConnection = ivendor.CreateDbConnection(fileOrServerOrConnection);\r
Init(new DatabaseContext(dbConnection), null, ivendor);\r
\r
+ Profiler.At("END DataContext(string)");\r
}\r
\r
- private IVendor GetVendor(string connectionString)\r
+ private IVendor GetVendor(ref string connectionString)\r
{\r
if (connectionString == null)\r
throw new ArgumentNullException("connectionString");\r
\r
Assembly assy;\r
string vendorClassToLoad;\r
- GetVendorInfo(connectionString, out assy, out vendorClassToLoad);\r
+ GetVendorInfo(ref connectionString, out assy, out vendorClassToLoad);\r
\r
var types =\r
from type in assy.GetTypes()\r
return (IVendor) Activator.CreateInstance(types.First());\r
}\r
\r
- private void GetVendorInfo(string connectionString, out Assembly assembly, out string typeName)\r
+ private void GetVendorInfo(ref string connectionString, out Assembly assembly, out string typeName)\r
{\r
System.Text.RegularExpressions.Regex reProvider\r
- = new System.Text.RegularExpressions.Regex(@"DbLinqProvider=([\w\.]+)");\r
+ = new System.Text.RegularExpressions.Regex(@"DbLinqProvider=([\w\.]+);?");\r
\r
- string assemblyFile = null;\r
+ string assemblyName = null;\r
string vendor;\r
if (!reProvider.IsMatch(connectionString))\r
{\r
vendor = "SqlServer";\r
- assemblyFile = "DbLinq.SqlServer.dll";\r
+ assemblyName = "DbLinq.SqlServer";\r
}\r
else\r
{\r
var match = reProvider.Match(connectionString);\r
vendor = match.Groups[1].Value;\r
- assemblyFile = "DbLinq." + vendor + ".dll";\r
+ assemblyName = "DbLinq." + vendor;\r
\r
//plain DbLinq - non MONO: \r
//IVendor classes are in DLLs such as "DbLinq.MySql.dll"\r
#if MONO_STRICT\r
assembly = typeof (DataContext).Assembly; // System.Data.Linq.dll\r
#else\r
- //TODO: check if DLL is already loaded?\r
- assembly = Assembly.LoadFrom(assemblyFile);\r
+ assembly = Assembly.Load(assemblyName);\r
#endif\r
}\r
catch (Exception e)\r
{\r
throw new ArgumentException(\r
string.Format(\r
- "Unable to load the `{0}' DbLinq vendor within assembly `{1}'.",\r
- assemblyFile, vendor),\r
+ "Unable to load the `{0}' DbLinq vendor within assembly '{1}.dll'.",\r
+ assemblyName, vendor),\r
"connectionString", e);\r
}\r
}\r
if (databaseContext.Connection.ConnectionString == null)\r
throw new NullReferenceException();\r
\r
+ string connectionString = databaseContext.Connection.ConnectionString;\r
_VendorProvider = ObjectFactory.Get<IVendorProvider>();\r
Vendor = vendor ?? \r
- (databaseContext.Connection.ConnectionString != null\r
- ? GetVendor(databaseContext.Connection.ConnectionString)\r
- : null) ??\r
+ (connectionString != null ? GetVendor(ref connectionString) : null) ??\r
+#if MOBILE\r
+ _VendorProvider.FindVendorByProviderType(typeof(DbLinq.Sqlite.SqliteSqlProvider));\r
+#else\r
_VendorProvider.FindVendorByProviderType(typeof(SqlClient.Sql2005Provider));\r
-\r
+#endif\r
+ \r
DatabaseContext = databaseContext;\r
\r
MemberModificationHandler = ObjectFactory.Create<IMemberModificationHandler>(); // not a singleton: object is stateful\r
Mapping = mappingSource.GetModel(GetType());\r
}\r
\r
- /// <summary>\r
- /// Checks if the table is allready mapped or maps it if not.\r
- /// </summary>\r
- /// <param name="tableType">Type of the table.</param>\r
- /// <exception cref="InvalidOperationException">Thrown if the table is not mappable.</exception>\r
- private void CheckTableMapping(Type tableType)\r
- {\r
- //This will throw an exception if the table is not found\r
- if(Mapping.GetTable(tableType) == null)\r
- {\r
- throw new InvalidOperationException("The type '" + tableType.Name + "' is not mapped as a Table.");\r
- }\r
- }\r
+ /// <summary>\r
+ /// Checks if the table is allready mapped or maps it if not.\r
+ /// </summary>\r
+ /// <param name="tableType">Type of the table.</param>\r
+ /// <exception cref="InvalidOperationException">Thrown if the table is not mappable.</exception>\r
+ private void CheckTableMapping(Type tableType)\r
+ {\r
+ //This will throw an exception if the table is not found\r
+ if(Mapping.GetTable(tableType) == null)\r
+ {\r
+ throw new InvalidOperationException("The type '" + tableType.Name + "' is not mapped as a Table.");\r
+ }\r
+ }\r
\r
- /// <summary>\r
- /// Returns a Table for the type TEntity.\r
- /// </summary>\r
- /// <exception cref="InvalidOperationException">If the type TEntity is not mappable as a Table.</exception>\r
- /// <typeparam name="TEntity">The table type.</typeparam>\r
- public Table<TEntity> GetTable<TEntity>() where TEntity : class\r
+ /// <summary>\r
+ /// Returns a Table for the type TEntity.\r
+ /// </summary>\r
+ /// <exception cref="InvalidOperationException">If the type TEntity is not mappable as a Table.</exception>\r
+ /// <typeparam name="TEntity">The table type.</typeparam>\r
+ public Table<TEntity> GetTable<TEntity>() where TEntity : class\r
{\r
return (Table<TEntity>)GetTable(typeof(TEntity));\r
}\r
\r
- /// <summary>\r
- /// Returns a Table for the given type.\r
- /// </summary>\r
- /// <param name="type">The table type.</param>\r
- /// <exception cref="InvalidOperationException">If the type is not mappable as a Table.</exception>\r
+ /// <summary>\r
+ /// Returns a Table for the given type.\r
+ /// </summary>\r
+ /// <param name="type">The table type.</param>\r
+ /// <exception cref="InvalidOperationException">If the type is not mappable as a Table.</exception>\r
public ITable GetTable(Type type)\r
{\r
- lock (_tableMap)\r
- {\r
- ITable tableExisting;\r
- if (_tableMap.TryGetValue(type, out tableExisting))\r
- return tableExisting;\r
-\r
- //Check for table mapping\r
- CheckTableMapping(type);\r
-\r
- var tableNew = Activator.CreateInstance(\r
- typeof(Table<>).MakeGenericType(type)\r
- , BindingFlags.NonPublic | BindingFlags.Instance\r
- , null\r
- , new object[] { this }\r
- , System.Globalization.CultureInfo.CurrentCulture) as ITable;\r
-\r
- _tableMap[type] = tableNew;\r
- return tableNew;\r
- }\r
+ Profiler.At("DataContext.GetTable(typeof({0}))", type != null ? type.Name : null);\r
+ ITable tableExisting;\r
+ if (_tableMap.TryGetValue(type, out tableExisting))\r
+ return tableExisting;\r
+\r
+ //Check for table mapping\r
+ CheckTableMapping(type);\r
+\r
+ var tableNew = Activator.CreateInstance(\r
+ typeof(Table<>).MakeGenericType(type)\r
+ , BindingFlags.NonPublic | BindingFlags.Instance\r
+ , null\r
+ , new object[] { this }\r
+ , System.Globalization.CultureInfo.CurrentCulture) as ITable;\r
+\r
+ _tableMap[type] = tableNew;\r
+ return tableNew;\r
}\r
\r
public void SubmitChanges()\r
/// <param name="failureMode"></param>\r
public virtual void SubmitChanges(ConflictMode failureMode)\r
{\r
+ if (this.objectTrackingEnabled == false)\r
+ throw new InvalidOperationException("Object tracking is not enabled for the current data context instance.");\r
using (DatabaseContext.OpenConnection()) //ConnMgr will close connection for us\r
- using (IDatabaseTransaction transaction = DatabaseContext.Transaction())\r
{\r
- var queryContext = new QueryContext(this);\r
- var entityTracks = entityTracker.EnumerateAll().ToList();\r
- foreach (var entityTrack in entityTracks)\r
+ if (Transaction != null)\r
+ SubmitChangesImpl(failureMode);\r
+ else\r
{\r
- switch (entityTrack.EntityState)\r
+ using (IDbTransaction transaction = DatabaseContext.CreateTransaction())\r
{\r
- case EntityState.ToInsert:\r
- var insertQuery = QueryBuilder.GetInsertQuery(entityTrack.Entity, queryContext);\r
- QueryRunner.Insert(entityTrack.Entity, insertQuery);\r
- Register(entityTrack.Entity);\r
- break;\r
- case EntityState.ToWatch:\r
- if (MemberModificationHandler.IsModified(entityTrack.Entity, Mapping))\r
- {\r
- var modifiedMembers = MemberModificationHandler.GetModifiedProperties(entityTrack.Entity, Mapping);\r
- var updateQuery = QueryBuilder.GetUpdateQuery(entityTrack.Entity, modifiedMembers, queryContext);\r
- QueryRunner.Update(entityTrack.Entity, updateQuery, modifiedMembers);\r
-\r
- RegisterUpdateAgain(entityTrack.Entity);\r
- }\r
- break;\r
- case EntityState.ToDelete:\r
- var deleteQuery = QueryBuilder.GetDeleteQuery(entityTrack.Entity, queryContext);\r
- QueryRunner.Delete(entityTrack.Entity, deleteQuery);\r
-\r
- UnregisterDelete(entityTrack.Entity);\r
- break;\r
- default:\r
- throw new ArgumentOutOfRangeException();\r
+ try\r
+ {\r
+ Transaction = (DbTransaction) transaction;\r
+ SubmitChangesImpl(failureMode);\r
+ // TODO: handle conflicts (which can only occur when concurrency mode is implemented)\r
+ transaction.Commit();\r
+ }\r
+ finally\r
+ {\r
+ Transaction = null;\r
+ }\r
}\r
}\r
- // TODO: handle conflicts (which can only occur when concurrency mode is implemented)\r
- transaction.Commit();\r
+ }\r
+ }\r
+\r
+ void SubmitChangesImpl(ConflictMode failureMode)\r
+ {\r
+ var queryContext = new QueryContext(this);\r
+\r
+ // There's no sense in updating an entity when it's going to \r
+ // be deleted in the current transaction, so do deletes first.\r
+ foreach (var entityTrack in CurrentTransactionEntities.EnumerateAll().ToList())\r
+ {\r
+ switch (entityTrack.EntityState)\r
+ {\r
+ case EntityState.ToDelete:\r
+ var deleteQuery = QueryBuilder.GetDeleteQuery(entityTrack.Entity, queryContext);\r
+ QueryRunner.Delete(entityTrack.Entity, deleteQuery);\r
+\r
+ UnregisterDelete(entityTrack.Entity);\r
+ AllTrackedEntities.RegisterToDelete(entityTrack.Entity);\r
+ AllTrackedEntities.RegisterDeleted(entityTrack.Entity);\r
+ break;\r
+ default:\r
+ // ignore.\r
+ break;\r
+ }\r
+ }\r
+ foreach (var entityTrack in CurrentTransactionEntities.EnumerateAll()\r
+ .Concat(AllTrackedEntities.EnumerateAll())\r
+ .ToList())\r
+ {\r
+ switch (entityTrack.EntityState)\r
+ {\r
+ case EntityState.ToInsert:\r
+ foreach (var toInsert in GetReferencedObjects(entityTrack.Entity))\r
+ {\r
+ InsertEntity(toInsert, queryContext);\r
+ }\r
+ break;\r
+ case EntityState.ToWatch:\r
+ foreach (var toUpdate in GetReferencedObjects(entityTrack.Entity))\r
+ {\r
+ UpdateEntity(toUpdate, queryContext);\r
+ }\r
+ break;\r
+ default:\r
+ throw new ArgumentOutOfRangeException();\r
+ }\r
+ }\r
+ }\r
+\r
+ private IEnumerable<object> GetReferencedObjects(object value)\r
+ {\r
+ var values = new EntitySet<object>();\r
+ FillReferencedObjects(value, values);\r
+ return values;\r
+ }\r
+\r
+ // Breadth-first traversal of an object graph\r
+ private void FillReferencedObjects(object parent, EntitySet<object> values)\r
+ {\r
+ if (parent == null)\r
+ return;\r
+ var children = new Queue<object>();\r
+ children.Enqueue(parent);\r
+ while (children.Count > 0)\r
+ {\r
+ object value = children.Dequeue();\r
+ values.Add(value);\r
+ IEnumerable<MetaAssociation> associationList = Mapping.GetMetaType(value.GetType()).Associations.Where(a => !a.IsForeignKey);\r
+ if (associationList.Any())\r
+ {\r
+ foreach (MetaAssociation association in associationList)\r
+ {\r
+ var memberData = association.ThisMember;\r
+ var entitySetValue = memberData.Member.GetMemberValue(value);\r
+\r
+ if (entitySetValue != null)\r
+ {\r
+ var hasLoadedOrAssignedValues = entitySetValue.GetType().GetProperty("HasLoadedOrAssignedValues");\r
+ if (!((bool)hasLoadedOrAssignedValues.GetValue(entitySetValue, null)))\r
+ continue; // execution deferred; ignore.\r
+ foreach (var o in ((IEnumerable)entitySetValue))\r
+ children.Enqueue(o);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ private void InsertEntity(object entity, QueryContext queryContext)\r
+ {\r
+ var insertQuery = QueryBuilder.GetInsertQuery(entity, queryContext);\r
+ QueryRunner.Insert(entity, insertQuery);\r
+ Register(entity);\r
+ UpdateReferencedObjects(entity);\r
+ MoveToAllTrackedEntities(entity, true);\r
+ }\r
+\r
+ private void UpdateEntity(object entity, QueryContext queryContext)\r
+ {\r
+ if (!AllTrackedEntities.ContainsReference(entity))\r
+ InsertEntity(entity, queryContext);\r
+ else if (MemberModificationHandler.IsModified(entity, Mapping))\r
+ {\r
+ var modifiedMembers = MemberModificationHandler.GetModifiedProperties(entity, Mapping);\r
+ var updateQuery = QueryBuilder.GetUpdateQuery(entity, modifiedMembers, queryContext);\r
+ QueryRunner.Update(entity, updateQuery, modifiedMembers);\r
+\r
+ RegisterUpdateAgain(entity);\r
+ UpdateReferencedObjects(entity);\r
+ MoveToAllTrackedEntities(entity, false);\r
+ }\r
+ }\r
+\r
+ private void UpdateReferencedObjects(object root)\r
+ {\r
+ var metaType = Mapping.GetMetaType(root.GetType());\r
+ foreach (var assoc in metaType.Associations)\r
+ {\r
+ var memberData = assoc.ThisMember;\r
+ //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
+ //Confirmed against default .NET l2sql - association columns are always set, even if AutoSync==AutoSync.Never\r
+ //if (memberData.Association.ThisKey.Any(m => (m.AutoSync != AutoSync.Always) && (m.AutoSync != sync)))\r
+ // continue;\r
+ var oks = memberData.Association.OtherKey.Select(m => m.StorageMember).ToList();\r
+ if (oks.Count == 0)\r
+ continue;\r
+ var pks = memberData.Association.ThisKey\r
+ .Select(m => m.StorageMember.GetMemberValue(root))\r
+ .ToList();\r
+ if (pks.Count != oks.Count)\r
+ throw new InvalidOperationException(\r
+ string.Format("Count of primary keys ({0}) doesn't match count of other keys ({1}).",\r
+ pks.Count, oks.Count));\r
+ var members = memberData.Member.GetMemberValue(root) as IEnumerable;\r
+ if (members == null)\r
+ continue;\r
+ foreach (var member in members)\r
+ {\r
+ for (int i = 0; i < pks.Count; ++i)\r
+ {\r
+ oks[i].SetMemberValue(member, pks[i]);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ private void MoveToAllTrackedEntities(object entity, bool insert)\r
+ {\r
+ if (!ObjectTrackingEnabled)\r
+ return;\r
+ if (CurrentTransactionEntities.ContainsReference(entity))\r
+ {\r
+ CurrentTransactionEntities.RegisterToDelete(entity);\r
+ if (!insert)\r
+ CurrentTransactionEntities.RegisterDeleted(entity);\r
+ }\r
+ if (!AllTrackedEntities.ContainsReference(entity))\r
+ {\r
+ var identityReader = _GetIdentityReader(entity.GetType());\r
+ AllTrackedEntities.RegisterToWatch(entity, identityReader.GetIdentityKey(entity));\r
}\r
}\r
\r
internal IIdentityReader _GetIdentityReader(Type t)\r
{\r
IIdentityReader identityReader;\r
- lock (identityReaders)\r
+ if (!identityReaders.TryGetValue(t, out identityReader))\r
{\r
- if (!identityReaders.TryGetValue(t, out identityReader))\r
- {\r
- identityReader = identityReaderFactory.GetReader(t, this);\r
- identityReaders[t] = identityReader;\r
- }\r
+ identityReader = identityReaderFactory.GetReader(t, this);\r
+ identityReaders[t] = identityReader;\r
}\r
return identityReader;\r
}\r
if (identityKey == null) // if we don't have an entitykey here, it means that the entity has no PK\r
return entity;\r
// even \r
- var registeredEntityTrack = entityTracker.FindByIdentity(identityKey);\r
+ var registeredEntityTrack = \r
+ CurrentTransactionEntities.FindByIdentity(identityKey) ??\r
+ AllTrackedEntities.FindByIdentity(identityKey);\r
if (registeredEntityTrack != null)\r
return registeredEntityTrack.Entity;\r
return null;\r
return entity;\r
\r
// try to find an already registered entity and return it\r
- var registeredEntityTrack = entityTracker.FindByIdentity(identityKey);\r
+ var registeredEntityTrack = \r
+ CurrentTransactionEntities.FindByIdentity(identityKey) ??\r
+ AllTrackedEntities.FindByIdentity(identityKey);\r
if (registeredEntityTrack != null)\r
return registeredEntityTrack.Entity;\r
\r
// otherwise, register and return\r
- entityTracker.RegisterToWatch(entity, identityKey);\r
+ AllTrackedEntities.RegisterToWatch(entity, identityKey);\r
return entity;\r
}\r
\r
readonly IDataMapper DataMapper = ObjectFactory.Get<IDataMapper>();\r
- private void SetEntityRefQueries(object entity)\r
- {\r
- Type thisType = entity.GetType();\r
- IList<MemberInfo> properties = DataMapper.GetEntityRefAssociations(thisType);\r
-\r
-\r
- foreach (PropertyInfo prop in properties)\r
- {\r
- //example of entityRef:Order.Employee\r
- AssociationAttribute associationInfo = prop.GetAttribute<AssociationAttribute>();\r
- Type otherTableType = prop.PropertyType;\r
- IList<MemberInfo> otherPKs = DataMapper.GetPrimaryKeys(Mapping.GetTable(otherTableType));\r
-\r
- if (otherPKs.Count > 1)\r
- throw new NotSupportedException("Multiple keys object not supported yet.");\r
-\r
- var otherTable = GetTable(otherTableType);\r
-\r
- //ie:EmployeeTerritories.EmployeeID\r
-\r
- var thisForeignKeyProperty = thisType.GetProperty(associationInfo.ThisKey);\r
- object thisForeignKeyValue = thisForeignKeyProperty.GetValue(entity, null);\r
-\r
- IEnumerable query = null;\r
- if (thisForeignKeyValue != null)\r
- {\r
- ParameterExpression p = Expression.Parameter(otherTableType, "other");\r
- Expression predicate;\r
- if (!(thisForeignKeyProperty.PropertyType.IsNullable()))\r
- {\r
- predicate = Expression.Equal(Expression.MakeMemberAccess(p, otherPKs.First()),\r
- Expression.Constant(thisForeignKeyValue));\r
- }\r
- else\r
- {\r
- var ValueProperty = thisForeignKeyProperty.PropertyType.GetProperty("Value");\r
- predicate = Expression.Equal(Expression.MakeMemberAccess(p, otherPKs.First()),\r
- Expression.Constant(ValueProperty.GetValue(thisForeignKeyValue, null)));\r
- }\r
-\r
- query = GetOtherTableQuery(predicate, p, otherTableType, otherTable) as IEnumerable;\r
- //it would be interesting surround the above query with a .Take(1) expression for performance.\r
- }\r
+ private void SetEntityRefQueries(object entity)\r
+ {\r
+ if (!this.deferredLoadingEnabled)\r
+ return;\r
\r
+ // BUG: This is ignoring External Mappings from XmlMappingSource.\r
\r
- FieldInfo entityRefField = entity.GetType().GetField(associationInfo.Storage, BindingFlags.NonPublic | BindingFlags.Instance);\r
- object entityRefValue = null;\r
- if (query != null)\r
- entityRefValue = Activator.CreateInstance(entityRefField.FieldType, query);\r
- else\r
- entityRefValue = Activator.CreateInstance(entityRefField.FieldType);\r
- entityRefField.SetValue(entity, entityRefValue);\r
- }\r
- }\r
+ Type thisType = entity.GetType();\r
+ IEnumerable<MetaAssociation> associationList = Mapping.GetMetaType(entity.GetType()).Associations.Where(a => a.IsForeignKey);\r
+ foreach (MetaAssociation association in associationList)\r
+ {\r
+ //example of entityRef:Order.Employee\r
+ var memberData = association.ThisMember;\r
+ Type otherTableType = association.OtherType.Type;\r
+ ParameterExpression p = Expression.Parameter(otherTableType, "other");\r
+\r
+ var otherTable = GetTable(otherTableType);\r
+\r
+ //ie:EmployeeTerritories.EmployeeID\r
+ var foreignKeys = memberData.Association.ThisKey;\r
+ BinaryExpression predicate = null;\r
+ var otherPKs = memberData.Association.OtherKey;\r
+ IEnumerator<MetaDataMember> otherPKEnumerator = otherPKs.GetEnumerator();\r
+\r
+ if (otherPKs.Count != foreignKeys.Count)\r
+ throw new InvalidOperationException("Foreign keys don't match ThisKey");\r
+ foreach (MetaDataMember key in foreignKeys)\r
+ {\r
+ otherPKEnumerator.MoveNext();\r
+\r
+ var thisForeignKeyProperty = (PropertyInfo)key.Member;\r
+ object thisForeignKeyValue = thisForeignKeyProperty.GetValue(entity, null);\r
+\r
+ if (thisForeignKeyValue != null)\r
+ {\r
+ BinaryExpression keyPredicate;\r
+ if (!(thisForeignKeyProperty.PropertyType.IsNullable()))\r
+ {\r
+ keyPredicate = Expression.Equal(Expression.MakeMemberAccess(p, otherPKEnumerator.Current.Member),\r
+ Expression.Constant(thisForeignKeyValue));\r
+ }\r
+ else\r
+ {\r
+ var ValueProperty = thisForeignKeyProperty.PropertyType.GetProperty("Value");\r
+ keyPredicate = Expression.Equal(Expression.MakeMemberAccess(p, otherPKEnumerator.Current.Member),\r
+ Expression.Constant(ValueProperty.GetValue(thisForeignKeyValue, null)));\r
+ }\r
+\r
+ if (predicate == null)\r
+ predicate = keyPredicate;\r
+ else\r
+ predicate = Expression.And(predicate, keyPredicate);\r
+ }\r
+ }\r
+ IEnumerable query = null;\r
+ if (predicate != null)\r
+ {\r
+ query = GetOtherTableQuery(predicate, p, otherTableType, otherTable) as IEnumerable;\r
+ //it would be interesting surround the above query with a .Take(1) expression for performance.\r
+ }\r
+\r
+ // If no separate Storage is specified, use the member directly\r
+ MemberInfo storage = memberData.StorageMember;\r
+ if (storage == null)\r
+ storage = memberData.Member;\r
+\r
+ // Check that the storage is a field or a writable property\r
+ if (!(storage is FieldInfo) && !(storage is PropertyInfo && ((PropertyInfo)storage).CanWrite)) {\r
+ throw new InvalidOperationException(String.Format(\r
+ "Member {0}.{1} is not a field nor a writable property",\r
+ storage.DeclaringType, storage.Name));\r
+ }\r
+\r
+ Type storageType = storage.GetMemberType();\r
+\r
+ object entityRefValue = null;\r
+ if (query != null)\r
+ entityRefValue = Activator.CreateInstance(storageType, query);\r
+ else\r
+ entityRefValue = Activator.CreateInstance(storageType);\r
+\r
+ storage.SetMemberValue(entity, entityRefValue);\r
+ }\r
+ }\r
\r
/// <summary>\r
/// 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
/// <param name="entity"></param>\r
private void SetEntitySetsQueries(object entity)\r
{\r
- IList<MemberInfo> properties = DataMapper.GetEntitySetAssociations(entity.GetType());\r
- \r
- if (properties.Any()) {\r
- IList<MemberInfo> thisPKs = DataMapper.GetPrimaryKeys(Mapping.GetTable(entity.GetType()));\r
-\r
- if (thisPKs.Count > 1)\r
- throw new NotSupportedException("Multiple keys object not supported yet.");\r
+ if (!this.deferredLoadingEnabled)\r
+ return;\r
\r
- object primaryKeyValue = (thisPKs.First() as PropertyInfo).GetValue(entity, null);\r
+ // BUG: This is ignoring External Mappings from XmlMappingSource.\r
\r
+ IEnumerable<MetaAssociation> associationList = Mapping.GetMetaType(entity.GetType()).Associations.Where(a => !a.IsForeignKey);\r
\r
- foreach (PropertyInfo prop in properties)\r
+ if (associationList.Any())\r
+ {\r
+ foreach (MetaAssociation association in associationList)\r
{\r
- //example of entitySet: Employee.EmployeeTerritories\r
- var associationInfo = prop.GetAttribute<AssociationAttribute>();\r
- Type otherTableType = prop.PropertyType.GetGenericArguments().First();\r
+ //example of entitySet: Employee.EmployeeTerritories\r
+ var memberData = association.ThisMember;\r
+ Type otherTableType = association.OtherType.Type;\r
+ ParameterExpression p = Expression.Parameter(otherTableType, "other");\r
\r
//other table:EmployeeTerritories\r
var otherTable = GetTable(otherTableType);\r
- //other table member:EmployeeTerritories.EmployeeID\r
- var otherTableMember = otherTableType.GetProperty(associationInfo.OtherKey);\r
\r
-\r
- ParameterExpression p = Expression.Parameter(otherTableType, "other");\r
- Expression predicate;\r
- if (!(otherTableMember.PropertyType.IsNullable()))\r
+ var otherKeys = memberData.Association.OtherKey;\r
+ var thisKeys = memberData.Association.ThisKey;\r
+ if (otherKeys.Count != thisKeys.Count)\r
+ throw new InvalidOperationException("This keys don't match OtherKey");\r
+ BinaryExpression predicate = null;\r
+ IEnumerator<MetaDataMember> thisKeyEnumerator = thisKeys.GetEnumerator();\r
+ foreach (MetaDataMember otherKey in otherKeys)\r
{\r
- predicate = Expression.Equal(Expression.MakeMemberAccess(p, otherTableMember),\r
- Expression.Constant(primaryKeyValue));\r
- }\r
- else\r
- {\r
- var ValueProperty = otherTableMember.PropertyType.GetProperty("Value");\r
- predicate = Expression.Equal(Expression.MakeMemberAccess(\r
- Expression.MakeMemberAccess(p, otherTableMember),\r
- ValueProperty),\r
- Expression.Constant(primaryKeyValue));\r
+ thisKeyEnumerator.MoveNext();\r
+ //other table member:EmployeeTerritories.EmployeeID\r
+ var otherTableMember = (PropertyInfo)otherKey.Member;\r
+\r
+ BinaryExpression keyPredicate;\r
+ if (!(otherTableMember.PropertyType.IsNullable()))\r
+ {\r
+ keyPredicate = Expression.Equal(Expression.MakeMemberAccess(p, otherTableMember),\r
+ Expression.Constant(thisKeyEnumerator.Current.Member.GetMemberValue(entity)));\r
+ }\r
+ else\r
+ {\r
+ var ValueProperty = otherTableMember.PropertyType.GetProperty("Value");\r
+ keyPredicate = Expression.Equal(Expression.MakeMemberAccess(\r
+ Expression.MakeMemberAccess(p, otherTableMember),\r
+ ValueProperty),\r
+ Expression.Constant(thisKeyEnumerator.Current.Member.GetMemberValue(entity)));\r
+ }\r
+ if (predicate == null)\r
+ predicate = keyPredicate;\r
+ else\r
+ predicate = Expression.And(predicate, keyPredicate);\r
}\r
\r
var query = GetOtherTableQuery(predicate, p, otherTableType, otherTable);\r
\r
- var entitySetValue = prop.GetValue(entity, null);\r
+ var entitySetValue = memberData.Member.GetMemberValue(entity);\r
\r
if (entitySetValue == null)\r
{\r
- entitySetValue = Activator.CreateInstance(prop.PropertyType);\r
- prop.SetValue(entity, entitySetValue, null);\r
+ entitySetValue = Activator.CreateInstance(memberData.Member.GetMemberType());\r
+ memberData.Member.SetMemberValue(entity, entitySetValue);\r
}\r
\r
+ var hasLoadedOrAssignedValues = entitySetValue.GetType().GetProperty("HasLoadedOrAssignedValues");\r
+ if ((bool)hasLoadedOrAssignedValues.GetValue(entitySetValue, null))\r
+ continue;\r
+\r
var setSourceMethod = entitySetValue.GetType().GetMethod("SetSource");\r
setSourceMethod.Invoke(entitySetValue, new[] { query });\r
//employee.EmployeeTerritories.SetSource(Table[EmployeesTerritories].Where(other=>other.employeeID="WARTH"))\r
}\r
}\r
\r
- private object GetOtherTableQuery(Expression predicate, ParameterExpression parameter, Type otherTableType, IQueryable otherTable)\r
+ private static MethodInfo _WhereMethod;\r
+ internal object GetOtherTableQuery(Expression predicate, ParameterExpression parameter, Type otherTableType, IQueryable otherTable)\r
{\r
+ if (_WhereMethod == null)\r
+ System.Threading.Interlocked.CompareExchange (ref _WhereMethod, typeof(Queryable).GetMethods().First(m => m.Name == "Where"), null);\r
+\r
//predicate: other.EmployeeID== "WARTH"\r
Expression lambdaPredicate = Expression.Lambda(predicate, parameter);\r
//lambdaPredicate: other=>other.EmployeeID== "WARTH"\r
\r
- var whereMethod = typeof(Queryable)\r
- .GetMethods().First(m => m.Name == "Where")\r
- .MakeGenericMethod(otherTableType);\r
-\r
-\r
- Expression call = Expression.Call(whereMethod, otherTable.Expression, lambdaPredicate);\r
+ Expression call = Expression.Call(_WhereMethod.MakeGenericMethod(otherTableType), otherTable.Expression, lambdaPredicate);\r
//Table[EmployeesTerritories].Where(other=>other.employeeID="WARTH")\r
\r
return otherTable.Provider.CreateQuery(call);\r
/// <param name="entity"></param>\r
internal void RegisterInsert(object entity)\r
{\r
- entityTracker.RegisterToInsert(entity);\r
+ CurrentTransactionEntities.RegisterToInsert(entity);\r
}\r
\r
- /// <summary>\r
- /// Registers an entity for update\r
- /// The entity will be updated only if some of its members have changed after the registration\r
- /// </summary>\r
- /// <param name="entity"></param>\r
- internal void RegisterUpdate(object entity)\r
+ private void DoRegisterUpdate(object entity)\r
{\r
if (entity == null)\r
throw new ArgumentNullException("entity");\r
\r
+ if (!this.objectTrackingEnabled)\r
+ return;\r
+\r
var identityReader = _GetIdentityReader(entity.GetType());\r
var identityKey = identityReader.GetIdentityKey(entity);\r
- Console.WriteLine("# identityKey={0}", identityKey == null ? "<null>" : identityKey.ToString());\r
// if we have no key, we can not watch\r
- if (identityKey == null)\r
+ if (identityKey == null || identityKey.Keys.Count == 0)\r
return;\r
// register entity\r
- entityTracker.RegisterToWatch(entity, identityKey);\r
+ AllTrackedEntities.RegisterToWatch(entity, identityKey);\r
+ }\r
+\r
+ /// <summary>\r
+ /// Registers an entity for update\r
+ /// The entity will be updated only if some of its members have changed after the registration\r
+ /// </summary>\r
+ /// <param name="entity"></param>\r
+ internal void RegisterUpdate(object entity)\r
+ {\r
+ DoRegisterUpdate(entity);\r
+ MemberModificationHandler.Register(entity, Mapping);\r
}\r
\r
/// <summary>\r
/// <returns></returns>\r
internal object Register(object entity)\r
{\r
+ if (! this.objectTrackingEnabled)\r
+ return entity;\r
var registeredEntity = _GetOrRegisterEntity(entity);\r
// the fact of registering again clears the modified state, so we're... clear with that\r
MemberModificationHandler.Register(registeredEntity, Mapping);\r
/// <param name="entityOriginalState"></param>\r
internal void RegisterUpdate(object entity, object entityOriginalState)\r
{\r
- RegisterUpdate(entity);\r
+ if (!this.objectTrackingEnabled)\r
+ return;\r
+ DoRegisterUpdate(entity);\r
MemberModificationHandler.Register(entity, entityOriginalState, Mapping);\r
}\r
\r
/// <param name="entity"></param>\r
internal void RegisterUpdateAgain(object entity)\r
{\r
+ if (!this.objectTrackingEnabled)\r
+ return;\r
MemberModificationHandler.ClearModified(entity, Mapping);\r
}\r
\r
/// <param name="entity"></param>\r
internal void RegisterDelete(object entity)\r
{\r
- entityTracker.RegisterToDelete(entity);\r
+ if (!this.objectTrackingEnabled)\r
+ return;\r
+ CurrentTransactionEntities.RegisterToDelete(entity);\r
}\r
\r
/// <summary>\r
/// <param name="entity"></param>\r
internal void UnregisterDelete(object entity)\r
{\r
- entityTracker.RegisterDeleted(entity);\r
+ if (!this.objectTrackingEnabled)\r
+ return;\r
+ CurrentTransactionEntities.RegisterDeleted(entity);\r
}\r
\r
#endregion\r
var inserts = new List<object>();\r
var updates = new List<object>();\r
var deletes = new List<object>();\r
- foreach (var entityTrack in entityTracker.EnumerateAll())\r
+ foreach (var entityTrack in CurrentTransactionEntities.EnumerateAll()\r
+ .Concat(AllTrackedEntities.EnumerateAll()))\r
{\r
switch (entityTrack.EntityState)\r
{\r
/// <summary>\r
/// Execute raw SQL query and return object\r
/// </summary>\r
- public IEnumerable<TResult> ExecuteQuery<TResult>(string query, params object[] parameters) where TResult : class, new()\r
+ public IEnumerable<TResult> ExecuteQuery<TResult>(string query, params object[] parameters) where TResult : new()\r
{\r
if (query == null)\r
throw new ArgumentNullException("query");\r
}\r
\r
private IEnumerable<TResult> CreateExecuteQueryEnumerable<TResult>(string query, object[] parameters)\r
- where TResult : class, new()\r
+ where TResult : new()\r
{\r
foreach (TResult result in ExecuteQuery(typeof(TResult), query, parameters))\r
yield return result;\r
\r
public IEnumerable ExecuteQuery(Type elementType, string query, params object[] parameters)\r
{\r
- Console.WriteLine("# ExecuteQuery: query={0}", query != null ? query : "<null>");\r
if (elementType == null)\r
throw new ArgumentNullException("elementType");\r
if (query == null)\r
/// Gets or sets the load options\r
/// </summary>\r
[DbLinqToDo]\r
- public DataLoadOptions LoadOptions { get; set; }\r
+ public DataLoadOptions LoadOptions\r
+ {\r
+ get { throw new NotImplementedException(); }\r
+ set { throw new NotImplementedException(); }\r
+ }\r
\r
- public DbTransaction Transaction { get; set; }\r
+ public DbTransaction Transaction {\r
+ get { return (DbTransaction) DatabaseContext.CurrentTransaction; }\r
+ set { DatabaseContext.CurrentTransaction = value; }\r
+ }\r
\r
/// <summary>\r
/// Runs the given reader and returns columns.\r
{\r
//connection closing should not be done here.\r
//read: http://msdn2.microsoft.com/en-us/library/bb292288.aspx\r
+\r
+ //We own the instance of MemberModificationHandler - we must unregister listeners of entities we attached to\r
+ MemberModificationHandler.UnregisterAll();\r
}\r
\r
[DbLinqToDo]\r
}\r
}\r
\r
-\r
- [DbLinqToDo]\r
public bool ObjectTrackingEnabled\r
{\r
- get { throw new NotImplementedException(); }\r
- set { throw new NotImplementedException(); }\r
+ get { return this.objectTrackingEnabled; }\r
+ set \r
+ {\r
+ if (this.currentTransactionEntities != null && value != this.objectTrackingEnabled)\r
+ throw new InvalidOperationException("Data context options cannot be modified after results have been returned from a query.");\r
+ this.objectTrackingEnabled = value;\r
+ }\r
}\r
\r
[DbLinqToDo]\r
set { throw new NotImplementedException(); }\r
}\r
\r
- [DbLinqToDo]\r
public bool DeferredLoadingEnabled\r
{\r
- get { throw new NotImplementedException(); }\r
- set { throw new NotImplementedException(); }\r
+ get { return this.deferredLoadingEnabled; }\r
+ set\r
+ {\r
+ if (this.currentTransactionEntities != null && value != this.deferredLoadingEnabled)\r
+ throw new InvalidOperationException("Data context options cannot be modified after results have been returned from a query.");\r
+ this.deferredLoadingEnabled = value;\r
+ }\r
}\r
\r
[DbLinqToDo]\r
\r
[DbLinqToDo]\r
public DbCommand GetCommand(IQueryable query)\r
+ {\r
+ DbCommand dbCommand = GetIDbCommand(query) as DbCommand;\r
+ if (dbCommand == null)\r
+ throw new InvalidOperationException();\r
+\r
+ return dbCommand;\r
+ }\r
+\r
+ [DBLinqExtended]\r
+ public IDbCommand GetIDbCommand(IQueryable query)\r
{\r
if (query == null)\r
throw new ArgumentNullException("query");\r
if (qp == null)\r
throw new InvalidOperationException();\r
\r
- IDbCommand dbCommand = qp.GetQuery(null).GetCommand().Command;\r
- if (!(dbCommand is DbCommand))\r
- throw new InvalidOperationException();\r
+ if (qp.ExpressionChain.Expressions.Count == 0)\r
+ qp.ExpressionChain.Expressions.Add(CreateDefaultQuery(query));\r
\r
- return (DbCommand)dbCommand;\r
+ return qp.GetQuery(null).GetCommand().Command;\r
+ }\r
+\r
+ private Expression CreateDefaultQuery(IQueryable query)\r
+ {\r
+ // Manually create the expression tree for: IQueryable<TableType>.Select(e => e)\r
+ var identityParameter = Expression.Parameter(query.ElementType, "e");\r
+ var identityBody = Expression.Lambda(\r
+ typeof(Func<,>).MakeGenericType(query.ElementType, query.ElementType),\r
+ identityParameter,\r
+ new[] { identityParameter }\r
+ );\r
+\r
+ return Expression.Call(\r
+ typeof(Queryable),\r
+ "Select",\r
+ new[] { query.ElementType, query.ElementType },\r
+ query.Expression,\r
+ Expression.Quote(identityBody)\r
+ );\r
}\r
\r
[DbLinqToDo]\r