From c4985f60dbe62068bcc3695f89b10839919faff7 Mon Sep 17 00:00:00 2001 From: Frans Bouma Date: Mon, 29 Dec 2014 12:26:23 +0100 Subject: [PATCH] Performed lots of clean up - moved every type to its own file - added logical folders and grouped files into folders so they're easier to find - moved some internal types to other namespaces as they were in the wrong namespace - added some warnings on sqlclient using code in generic classes. --- src/BindingLists/BindingList.cs | 34 + src/{ => BindingLists}/DataBindingList.cs | 21 +- .../EntitySetDataBindingList.cs | 3 +- src/{ => BindingLists}/SortableBindingList.cs | 2 +- src/ChangeConflicts.cs | 452 ------- src/ChangeDirector.cs | 420 ------- .../ChangeConflictCollection.cs | 142 +++ src/ChangeManagement/ChangeConflictSession.cs | 44 + src/ChangeManagement/ChangeDirector.cs | 41 + src/ChangeManagement/ChangeProcessor.cs | 919 +++++++++++++++ src/ChangeManagement/ChangeSet.cs | 72 ++ src/ChangeManagement/ChangeTracker.cs | 1049 +++++++++++++++++ src/ChangeManagement/DataManipulation.cs | 136 +++ src/ChangeManagement/MemberChangeConflict.cs | 94 ++ src/ChangeManagement/ModifiedMemberInfo.cs | 56 + src/ChangeManagement/ObjectChangeConflict.cs | 287 +++++ src/ChangeManagement/RelatedItem.cs | 27 + .../StandardChangeDirector.cs | 482 ++++++++ src/ChangeManagement/TrackedObject.cs | 83 ++ src/ChangeProcessor.cs | 765 ------------ src/ChangeTracker.cs | 923 --------------- src/ConstantsEnums.cs | 71 ++ src/Exceptions.cs | 57 - src/Exceptions/ChangeConflictException.cs | 18 + src/Exceptions/DuplicateKeyException.cs | 44 + ...ignKeyReferenceAlreadyHasValueException.cs | 21 + src/FunctionResults.cs | 53 - src/IdentityManager.cs | 377 ------ src/IdentityMangement/IdentityCache.cs | 23 + src/IdentityMangement/IdentityCacheOfTK.cs | 155 +++ src/IdentityMangement/IdentityManager.cs | 41 + src/IdentityMangement/KeyManager.cs | 20 + src/IdentityMangement/KeyManagerOfTK.cs | 22 + src/IdentityMangement/MultiKey.cs | 60 + src/IdentityMangement/MultiKeyManager.cs | 75 ++ src/IdentityMangement/SingleKeyManager.cs | 64 + .../StandardIdentityManager.cs | 96 ++ src/Interfaces/ICompiledQuery.cs | 34 + src/{Provider => Interfaces}/IDataServices.cs | 9 +- src/Interfaces/IDeferredSourceFactory.cs | 18 + src/Interfaces/IExecuteResult.cs | 27 + src/Interfaces/IFunctionResult.cs | 18 + src/Interfaces/IMultipleResults.cs | 19 + src/Interfaces/ISingleResult.cs | 13 + src/Interfaces/ITable.cs | 129 ++ src/Interfaces/ITableOfT.cs | 60 + src/Mapping/EntityRef.cs | 98 ++ src/Mapping/EntitySet.cs | 633 ++++++++++ src/Mapping/ItemList.cs | 134 +++ src/Mapping/Link.cs | 95 ++ src/Mapping/SourceState.cs | 20 + src/Mapping/Table.cs | 695 +++++++++++ src/{ => Miscellaneous}/DbConvert.cs | 4 +- src/{misc => Miscellaneous}/SecurityUtils.cs | 0 src/Provider/IProvider.cs | 122 +- src/{ => Querying}/CompiledQuery.cs | 0 src/{ => Querying}/DataContext.cs | 781 +----------- .../DataLoadOptions.cs} | 99 +- src/{ => Querying}/DataQuery.cs | 1 + src/{ => Querying}/DataServices.cs | 334 +++--- src/{ => Querying}/SubqueryRules.cs | 0 src/SD.Tools.LinqToSQL2.csproj | 75 +- src/SqlClient/Query/Funcletizer.cs | 2 +- src/SqlClient/Query/QueryConverter.cs | 2 +- src/SqlClient/SqlProvider.cs | 8 +- src/Types.cs | 869 -------------- src/Types/Binary.cs | 144 +++ 67 files changed, 6613 insertions(+), 5079 deletions(-) create mode 100644 src/BindingLists/BindingList.cs rename src/{ => BindingLists}/DataBindingList.cs (77%) rename src/{ => BindingLists}/EntitySetDataBindingList.cs (98%) rename src/{ => BindingLists}/SortableBindingList.cs (99%) delete mode 100644 src/ChangeConflicts.cs delete mode 100644 src/ChangeDirector.cs create mode 100644 src/ChangeManagement/ChangeConflictCollection.cs create mode 100644 src/ChangeManagement/ChangeConflictSession.cs create mode 100644 src/ChangeManagement/ChangeDirector.cs create mode 100644 src/ChangeManagement/ChangeProcessor.cs create mode 100644 src/ChangeManagement/ChangeSet.cs create mode 100644 src/ChangeManagement/ChangeTracker.cs create mode 100644 src/ChangeManagement/DataManipulation.cs create mode 100644 src/ChangeManagement/MemberChangeConflict.cs create mode 100644 src/ChangeManagement/ModifiedMemberInfo.cs create mode 100644 src/ChangeManagement/ObjectChangeConflict.cs create mode 100644 src/ChangeManagement/RelatedItem.cs create mode 100644 src/ChangeManagement/StandardChangeDirector.cs create mode 100644 src/ChangeManagement/TrackedObject.cs delete mode 100644 src/ChangeProcessor.cs delete mode 100644 src/ChangeTracker.cs create mode 100644 src/ConstantsEnums.cs delete mode 100644 src/Exceptions.cs create mode 100644 src/Exceptions/ChangeConflictException.cs create mode 100644 src/Exceptions/DuplicateKeyException.cs create mode 100644 src/Exceptions/ForeignKeyReferenceAlreadyHasValueException.cs delete mode 100644 src/FunctionResults.cs delete mode 100644 src/IdentityManager.cs create mode 100644 src/IdentityMangement/IdentityCache.cs create mode 100644 src/IdentityMangement/IdentityCacheOfTK.cs create mode 100644 src/IdentityMangement/IdentityManager.cs create mode 100644 src/IdentityMangement/KeyManager.cs create mode 100644 src/IdentityMangement/KeyManagerOfTK.cs create mode 100644 src/IdentityMangement/MultiKey.cs create mode 100644 src/IdentityMangement/MultiKeyManager.cs create mode 100644 src/IdentityMangement/SingleKeyManager.cs create mode 100644 src/IdentityMangement/StandardIdentityManager.cs create mode 100644 src/Interfaces/ICompiledQuery.cs rename src/{Provider => Interfaces}/IDataServices.cs (74%) create mode 100644 src/Interfaces/IDeferredSourceFactory.cs create mode 100644 src/Interfaces/IExecuteResult.cs create mode 100644 src/Interfaces/IFunctionResult.cs create mode 100644 src/Interfaces/IMultipleResults.cs create mode 100644 src/Interfaces/ISingleResult.cs create mode 100644 src/Interfaces/ITable.cs create mode 100644 src/Interfaces/ITableOfT.cs create mode 100644 src/Mapping/EntityRef.cs create mode 100644 src/Mapping/EntitySet.cs create mode 100644 src/Mapping/ItemList.cs create mode 100644 src/Mapping/Link.cs create mode 100644 src/Mapping/SourceState.cs create mode 100644 src/Mapping/Table.cs rename src/{ => Miscellaneous}/DbConvert.cs (97%) rename src/{misc => Miscellaneous}/SecurityUtils.cs (100%) rename src/{ => Querying}/CompiledQuery.cs (100%) rename src/{ => Querying}/DataContext.cs (53%) rename src/{DataShape.cs => Querying/DataLoadOptions.cs} (93%) rename src/{ => Querying}/DataQuery.cs (98%) rename src/{ => Querying}/DataServices.cs (97%) rename src/{ => Querying}/SubqueryRules.cs (100%) delete mode 100644 src/Types.cs create mode 100644 src/Types/Binary.cs diff --git a/src/BindingLists/BindingList.cs b/src/BindingLists/BindingList.cs new file mode 100644 index 0000000..5ca1faf --- /dev/null +++ b/src/BindingLists/BindingList.cs @@ -0,0 +1,34 @@ +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Data.Linq.Mapping; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace System.Data.Linq.BindingLists +{ + internal static class BindingList + { + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + internal static IBindingList Create(DataContext context, IEnumerable sequence) + { + List list = sequence.ToList(); + MetaTable metaTable = context.Services.Model.GetTable(typeof(T)); + if(metaTable != null) + { + ITable table = context.GetTable(metaTable.RowType.Type); + Type bindingType = typeof(DataBindingList<>).MakeGenericType(metaTable.RowType.Type); + return (IBindingList)Activator.CreateInstance(bindingType, + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, + new object[] { list, table }, null + ); + } + else + { + return new SortableBindingList(list); + } + } + } +} + diff --git a/src/DataBindingList.cs b/src/BindingLists/DataBindingList.cs similarity index 77% rename from src/DataBindingList.cs rename to src/BindingLists/DataBindingList.cs index 4c75782..a6e236e 100644 --- a/src/DataBindingList.cs +++ b/src/BindingLists/DataBindingList.cs @@ -6,25 +6,8 @@ using System.Reflection; using System.Runtime.CompilerServices; -namespace System.Data.Linq.Provider { - internal static class BindingList { - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] - internal static IBindingList Create(DataContext context, IEnumerable sequence) { - List list = sequence.ToList(); - MetaTable metaTable = context.Services.Model.GetTable(typeof(T)); - if (metaTable != null) { - ITable table = context.GetTable(metaTable.RowType.Type); - Type bindingType = typeof(DataBindingList<>).MakeGenericType(metaTable.RowType.Type); - return (IBindingList)Activator.CreateInstance(bindingType, - BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, - new object[] { list, table }, null - ); - } else { - return new SortableBindingList(list); - } - } - } - +namespace System.Data.Linq.BindingLists +{ internal class DataBindingList : SortableBindingList where TEntity : class { private Table data; diff --git a/src/EntitySetDataBindingList.cs b/src/BindingLists/EntitySetDataBindingList.cs similarity index 98% rename from src/EntitySetDataBindingList.cs rename to src/BindingLists/EntitySetDataBindingList.cs index d9d99a4..82525d1 100644 --- a/src/EntitySetDataBindingList.cs +++ b/src/BindingLists/EntitySetDataBindingList.cs @@ -4,7 +4,8 @@ using System.Reflection; using System.Diagnostics.CodeAnalysis; -namespace System.Data.Linq { +namespace System.Data.Linq.BindingLists +{ internal class EntitySetBindingList : SortableBindingList where TEntity : class { private EntitySet data; diff --git a/src/SortableBindingList.cs b/src/BindingLists/SortableBindingList.cs similarity index 99% rename from src/SortableBindingList.cs rename to src/BindingLists/SortableBindingList.cs index 336c879..8a53ded 100644 --- a/src/SortableBindingList.cs +++ b/src/BindingLists/SortableBindingList.cs @@ -8,7 +8,7 @@ using System.Reflection; using System.Xml.Linq; -namespace System.Data.Linq +namespace System.Data.Linq.BindingLists { /// /// Adds sorting feature to BindingList diff --git a/src/ChangeConflicts.cs b/src/ChangeConflicts.cs deleted file mode 100644 index 8153352..0000000 --- a/src/ChangeConflicts.cs +++ /dev/null @@ -1,452 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Text; -using System.Reflection; -using System.Linq; -using System.Diagnostics; - -namespace System.Data.Linq { - using System.Data.Linq.Mapping; - using System.Data.Linq.Provider; - using System.Diagnostics.CodeAnalysis; - - public sealed class ChangeConflictCollection : ICollection, ICollection, IEnumerable, IEnumerable { - private List conflicts; - - internal ChangeConflictCollection() { - this.conflicts = new List(); - } - - /// - /// The number of conflicts in the collection - /// - public int Count { - get { return this.conflicts.Count; } - } - - public ObjectChangeConflict this[int index] { - get { return this.conflicts[index]; } - } - - bool ICollection.IsReadOnly { - get { return true; } - } - - void ICollection.Add(ObjectChangeConflict item) { - throw Error.CannotAddChangeConflicts(); - } - - /// - /// Removes the specified conflict from the collection. - /// - /// The conflict to remove - /// - public bool Remove(ObjectChangeConflict item) { - return this.conflicts.Remove(item); - } - - /// - /// Removes all conflicts from the collection - /// - public void Clear() { - this.conflicts.Clear(); - } - - /// - /// Returns true if the specified conflict is a member of the collection. - /// - /// - /// - public bool Contains(ObjectChangeConflict item) { - return this.conflicts.Contains(item); - } - - public void CopyTo(ObjectChangeConflict[] array, int arrayIndex) { - this.conflicts.CopyTo(array, arrayIndex); - } - - /// - /// Returns the enumerator for the collection. - /// - /// - public IEnumerator GetEnumerator() { - return this.conflicts.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() { - return this.conflicts.GetEnumerator(); - } - - bool ICollection.IsSynchronized { - get { return false; } - } - - object ICollection.SyncRoot { - get { return null; } - } - - void ICollection.CopyTo(Array array, int index) { - ((ICollection)this.conflicts).CopyTo(array, index); - } - - /// - /// Resolves all conflicts in the collection using the specified strategy. - /// - /// The strategy to use to resolve the conflicts. - public void ResolveAll(RefreshMode mode) { - this.ResolveAll(mode, true); - } - - /// - /// Resolves all conflicts in the collection using the specified strategy. - /// - /// The strategy to use to resolve the conflicts. - /// If true conflicts resulting from the modified - /// object no longer existing in the database will be automatically resolved. - public void ResolveAll(RefreshMode mode, bool autoResolveDeletes) { - foreach (ObjectChangeConflict c in this.conflicts) { - if (!c.IsResolved) { - c.Resolve(mode, autoResolveDeletes); - } - } - } - - internal void Fill(List conflictList) { - this.conflicts = conflictList; - } - } - - internal sealed class ChangeConflictSession { - private DataContext context; - private DataContext refreshContext; - - internal ChangeConflictSession(DataContext context) { - this.context = context; - } - - internal DataContext Context { - get { return this.context; } - } - - internal DataContext RefreshContext { - get { - if (this.refreshContext == null) { - this.refreshContext = this.context.CreateRefreshContext(); - } - return this.refreshContext; - } - } - } - - /// - /// Represents an update with one or more optimistic concurrency conflicts. - /// - public sealed class ObjectChangeConflict { - private ChangeConflictSession session; - private TrackedObject trackedObject; - private bool isResolved; - private ReadOnlyCollection memberConflicts; - private object database; - private object original; - private bool? isDeleted; - - /// - /// Constructor. - /// - /// The session in which the conflicts occurred. - /// The tracked item in conflict. - internal ObjectChangeConflict(ChangeConflictSession session, TrackedObject trackedObject) { - this.session = session; - this.trackedObject = trackedObject; - this.original = trackedObject.CreateDataCopy(trackedObject.Original); - } - - /// - /// Constructor. - /// - /// The session in which the conflicts occurred. - /// The tracked item in conflict. - /// True if the item in conflict no longer exists in the database. - internal ObjectChangeConflict(ChangeConflictSession session, TrackedObject trackedObject, bool isDeleted) - : this(session, trackedObject) { - this.isDeleted = isDeleted; - } - - internal ChangeConflictSession Session { - get { return this.session; } - } - - internal TrackedObject TrackedObject { - get { return this.trackedObject; } - } - - /// - /// The object in conflict. - /// - public object Object { - get { return this.trackedObject.Current; } - } - - /// - /// An instance containing the baseline original values used to perform the concurrency check. - /// - internal object Original { - get { return this.original; } - } - - /// - /// True if the conflicts for this object have already been resovled. - /// - public bool IsResolved { - get { return this.isResolved; } - } - - /// - /// True if the object in conflict has been deleted from the database. - /// - public bool IsDeleted { - get { - if (this.isDeleted.HasValue) { - return this.isDeleted.Value; - } - return (this.Database == null); - } - } - - /// - /// An instance containing the most recent values from the database - /// - internal object Database { - get { - if (this.database == null) { - // use the 'refresh' context to retrieve the current database state - DataContext ctxt = this.session.RefreshContext; - object[] keyValues = CommonDataServices.GetKeyValues(this.trackedObject.Type, this.original); - this.database = ctxt.Services.GetObjectByKey(this.trackedObject.Type, keyValues); - } - return this.database; - } - } - - /// - /// Resolve member conflicts keeping current values and resetting the baseline 'Original' values - /// to match the more recent 'Database' values. - /// - public void Resolve() { - this.Resolve(RefreshMode.KeepCurrentValues, true); - } - - /// - /// Resolve member conflicts using the mode specified and resetting the baseline 'Original' values - /// to match the more recent 'Database' values. - /// - /// The mode that determines how the current values are - /// changed in order to resolve the conflict - public void Resolve(RefreshMode refreshMode) { - this.Resolve(refreshMode, false); - } - - /// - /// Resolve member conflicts using the mode specified and resetting the baseline 'Original' values - /// to match the more recent 'Database' values. - /// - /// The mode that determines how the current values are - /// changed in order to resolve the conflict - /// If true conflicts resulting from the modified - /// object no longer existing in the database will be automatically resolved. - public void Resolve(RefreshMode refreshMode, bool autoResolveDeletes) { - if (autoResolveDeletes && this.IsDeleted) { - this.ResolveDelete(); - } - else { - // We make these calls explicity rather than simply calling - // DataContext.Refresh (which does virtually the same thing) - // since we want to cache the database value read. - if (this.Database == null) { - throw Error.RefreshOfDeletedObject(); - } - trackedObject.Refresh(refreshMode, this.Database); - this.isResolved = true; - } - } - - /// - /// Resolve a conflict where we have updated an entity that no longer exists - /// in the database. - /// - private void ResolveDelete() { - Debug.Assert(this.IsDeleted); - // If the user is attempting to update an entity that no longer exists - // in the database, we first need to [....] the delete into the local cache. - if (!trackedObject.IsDeleted) { - trackedObject.ConvertToDeleted(); - } - - // As the object have been deleted, it needs to leave the cache - this.Session.Context.Services.RemoveCachedObjectLike(trackedObject.Type, trackedObject.Original); - - // Now that our cache is in [....], we accept the changes - this.trackedObject.AcceptChanges(); - this.isResolved = true; - } - - /// - /// Returns a collection of all member conflicts that caused the update to fail. - /// - public ReadOnlyCollection MemberConflicts { - get { - if (this.memberConflicts == null) { - var list = new List(); - if (this.Database != null) { - // determine which members are in conflict - foreach (MetaDataMember metaMember in trackedObject.Type.PersistentDataMembers) { - if (!metaMember.IsAssociation && this.HasMemberConflict(metaMember)) { - list.Add(new MemberChangeConflict(this, metaMember)); - } - } - } - this.memberConflicts = list.AsReadOnly(); - } - return this.memberConflicts; - } - } - - private bool HasMemberConflict(MetaDataMember member) { - object oValue = member.StorageAccessor.GetBoxedValue(this.original); - if (!member.DeclaringType.Type.IsAssignableFrom(this.database.GetType())) { - return false; - } - object dValue = member.StorageAccessor.GetBoxedValue(this.database); - return !this.AreEqual(member, oValue, dValue); - } - - private bool AreEqual(MetaDataMember member, object v1, object v2) { - if (v1 == null && v2 == null) - return true; - if (v1 == null || v2 == null) - return false; - if (member.Type == typeof(char[])) { - return this.AreEqual((char[])v1, (char[])v2); - } - else if (member.Type == typeof(byte[])) { - return this.AreEqual((byte[])v1, (byte[])v2); - } - else { - return object.Equals(v1, v2); - } - } - - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")] - private bool AreEqual(char[] a1, char[] a2) { - if (a1.Length != a2.Length) - return false; - for (int i = 0, n = a1.Length; i < n; i++) { - if (a1[i] != a2[i]) - return false; - } - return true; - } - - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")] - private bool AreEqual(byte[] a1, byte[] a2) { - if (a1.Length != a2.Length) - return false; - for (int i = 0, n = a1.Length; i < n; i++) { - if (a1[i] != a2[i]) - return false; - } - return true; - } - - internal void OnMemberResolved() { - if (!this.IsResolved) { - int nResolved = this.memberConflicts.AsEnumerable().Count(m => m.IsResolved); - if (nResolved == this.memberConflicts.Count) { - this.Resolve(RefreshMode.KeepCurrentValues, false); - } - } - } - } - - /// - /// Represents a single optimistic concurrency member conflict. - /// - public sealed class MemberChangeConflict { - private ObjectChangeConflict conflict; - private MetaDataMember metaMember; - private object originalValue; - private object databaseValue; - private object currentValue; - bool isResolved; - - internal MemberChangeConflict(ObjectChangeConflict conflict, MetaDataMember metaMember) { - this.conflict = conflict; - this.metaMember = metaMember; - this.originalValue = metaMember.StorageAccessor.GetBoxedValue(conflict.Original); - this.databaseValue = metaMember.StorageAccessor.GetBoxedValue(conflict.Database); - this.currentValue = metaMember.StorageAccessor.GetBoxedValue(conflict.TrackedObject.Current); - } - - /// - /// The previous client value. - /// - public object OriginalValue { - get { return this.originalValue; } - } - - /// - /// The current database value. - /// - public object DatabaseValue { - get { return this.databaseValue; } - } - - /// - /// The current client value. - /// - public object CurrentValue { - get { return this.currentValue; } - } - - /// - /// MemberInfo for the member in conflict. - /// - public MemberInfo Member { - get { return this.metaMember.Member; } - } - - /// - /// Updates the current value to the specified value. - /// - public void Resolve(object value) { - this.conflict.TrackedObject.RefreshMember(this.metaMember, RefreshMode.OverwriteCurrentValues, value); - this.isResolved = true; - this.conflict.OnMemberResolved(); - } - - /// - /// Updates the current value using the specified strategy. - /// - public void Resolve(RefreshMode refreshMode) { - this.conflict.TrackedObject.RefreshMember(this.metaMember, refreshMode, this.databaseValue); - this.isResolved = true; - this.conflict.OnMemberResolved(); - } - - /// - /// True if the value was modified by the client. - /// - public bool IsModified { - get { return this.conflict.TrackedObject.HasChangedValue(this.metaMember); } - } - - /// - /// True if the member conflict has been resolved. - /// - public bool IsResolved { - get { return this.isResolved; } - } - } -} diff --git a/src/ChangeDirector.cs b/src/ChangeDirector.cs deleted file mode 100644 index 96fc7db..0000000 --- a/src/ChangeDirector.cs +++ /dev/null @@ -1,420 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq.Expressions; -using System.Text; -using System.Reflection; -using System.Linq; -using System.Security.Permissions; -using System.Security; - -namespace System.Data.Linq { - using System.Data.Linq.Mapping; - using System.Data.Linq.Provider; - using System.Diagnostics.CodeAnalysis; - - /// - /// Controls how inserts, updates and deletes are performed. - /// - internal abstract class ChangeDirector { - internal abstract int Insert(TrackedObject item); - internal abstract int DynamicInsert(TrackedObject item); - internal abstract void AppendInsertText(TrackedObject item, StringBuilder appendTo); - - internal abstract int Update(TrackedObject item); - internal abstract int DynamicUpdate(TrackedObject item); - internal abstract void AppendUpdateText(TrackedObject item, StringBuilder appendTo); - - internal abstract int Delete(TrackedObject item); - internal abstract int DynamicDelete(TrackedObject item); - internal abstract void AppendDeleteText(TrackedObject item, StringBuilder appendTo); - - internal abstract void RollbackAutoSync(); - internal abstract void ClearAutoSyncRollback(); - - internal static ChangeDirector CreateChangeDirector(DataContext context) { - return new StandardChangeDirector(context); - } - - /// - /// Implementation of ChangeDirector which calls user code if possible - /// and othewise falls back to creating SQL for 'INSERT', 'UPDATE' and 'DELETE'. - /// - internal class StandardChangeDirector : ChangeDirector { - private enum UpdateType { Insert, Update, Delete }; - private enum AutoSyncBehavior { ApplyNewAutoSync, RollbackSavedValues } - - DataContext context; - [SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification="[....]: FxCop bug Dev10:423110 -- List> is not supposed to be flagged as a violation.")] - List> syncRollbackItems; - - internal StandardChangeDirector(DataContext context) { - this.context = context; - } - - [SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification="[....]: FxCop bug Dev10:423110 -- List> is not supposed to be flagged as a violation.")] - private List> SyncRollbackItems { - get { - if (syncRollbackItems == null) { - syncRollbackItems = new List>(); - } - return syncRollbackItems; - } - } - - internal override int Insert(TrackedObject item) { - if (item.Type.Table.InsertMethod != null) { - try { - item.Type.Table.InsertMethod.Invoke(this.context, new object[] { item.Current }); - } - catch (TargetInvocationException tie) { - if (tie.InnerException != null) { - throw tie.InnerException; - } - throw; - } - return 1; - } - else { - return DynamicInsert(item); - } - } - - internal override int DynamicInsert(TrackedObject item) { - Expression cmd = this.GetInsertCommand(item); - if (cmd.Type == typeof(int)) { - return (int)this.context.Provider.Execute(cmd).ReturnValue; - } - else { - IEnumerable facts = (IEnumerable)this.context.Provider.Execute(cmd).ReturnValue; - object[] syncResults = (object[])facts.FirstOrDefault(); - if (syncResults != null) { - // [....] any auto gen or computed members - AutoSyncMembers(syncResults, item, UpdateType.Insert, AutoSyncBehavior.ApplyNewAutoSync); - return 1; - } - else { - throw Error.InsertAutoSyncFailure(); - } - } - } - - internal override void AppendInsertText(TrackedObject item, StringBuilder appendTo) { - if (item.Type.Table.InsertMethod != null) { - appendTo.Append(Strings.InsertCallbackComment); - } - else { - Expression cmd = this.GetInsertCommand(item); - appendTo.Append(this.context.Provider.GetQueryText(cmd)); - appendTo.AppendLine(); - } - } - - /// - /// Update the item, returning 0 if the update fails, 1 if it succeeds. - /// - internal override int Update(TrackedObject item) { - if (item.Type.Table.UpdateMethod != null) { - // create a copy - don't allow the override to modify our - // internal original values - try { - item.Type.Table.UpdateMethod.Invoke(this.context, new object[] { item.Current }); - } - catch (TargetInvocationException tie) { - if (tie.InnerException != null) { - throw tie.InnerException; - } - throw; - } - return 1; - } - else { - return DynamicUpdate(item); - } - } - - internal override int DynamicUpdate(TrackedObject item) { - Expression cmd = this.GetUpdateCommand(item); - if (cmd.Type == typeof(int)) { - return (int)this.context.Provider.Execute(cmd).ReturnValue; - } - else { - IEnumerable facts = (IEnumerable)this.context.Provider.Execute(cmd).ReturnValue; - object[] syncResults = (object[])facts.FirstOrDefault(); - if (syncResults != null) { - // [....] any auto gen or computed members - AutoSyncMembers(syncResults, item, UpdateType.Update, AutoSyncBehavior.ApplyNewAutoSync); - return 1; - } - else { - return 0; - } - } - } - - internal override void AppendUpdateText(TrackedObject item, StringBuilder appendTo) { - if (item.Type.Table.UpdateMethod != null) { - appendTo.Append(Strings.UpdateCallbackComment); - } - else { - Expression cmd = this.GetUpdateCommand(item); - appendTo.Append(this.context.Provider.GetQueryText(cmd)); - appendTo.AppendLine(); - } - } - - internal override int Delete(TrackedObject item) { - if (item.Type.Table.DeleteMethod != null) { - try { - item.Type.Table.DeleteMethod.Invoke(this.context, new object[] { item.Current }); - } - catch (TargetInvocationException tie) { - if (tie.InnerException != null) { - throw tie.InnerException; - } - throw; - } - return 1; - } - else { - return DynamicDelete(item); - } - } - - internal override int DynamicDelete(TrackedObject item) { - Expression cmd = this.GetDeleteCommand(item); - int ret = (int)this.context.Provider.Execute(cmd).ReturnValue; - if (ret == 0) { - // we don't yet know if the delete failed because the check constaint did not match - // or item was already deleted. Verify the item exists - cmd = this.GetDeleteVerificationCommand(item); - ret = ((int?)this.context.Provider.Execute(cmd).ReturnValue) ?? -1; - } - return ret; - } - - internal override void AppendDeleteText(TrackedObject item, StringBuilder appendTo) { - if (item.Type.Table.DeleteMethod != null) { - appendTo.Append(Strings.DeleteCallbackComment); - } - else { - Expression cmd = this.GetDeleteCommand(item); - appendTo.Append(this.context.Provider.GetQueryText(cmd)); - appendTo.AppendLine(); - } - } - - [SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification="[....]: FxCop bug Dev10:423110 -- List> is not supposed to be flagged as a violation.")] - internal override void RollbackAutoSync() { - // Rolls back any AutoSync values that may have been set already - // Those values are no longer valid since the transaction will be rolled back on the server - if (this.syncRollbackItems != null) { - foreach (KeyValuePair rollbackItemPair in this.SyncRollbackItems) { - TrackedObject rollbackItem = rollbackItemPair.Key; - object[] rollbackValues = rollbackItemPair.Value; - - AutoSyncMembers( - rollbackValues, - rollbackItem, - rollbackItem.IsNew ? UpdateType.Insert : UpdateType.Update, - AutoSyncBehavior.RollbackSavedValues); - } - } - } - - [SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification="[....]: FxCop bug Dev10:423110 -- List> is not supposed to be flagged as a violation.")] - internal override void ClearAutoSyncRollback() { - this.syncRollbackItems = null; - } - - private Expression GetInsertCommand(TrackedObject item) { - MetaType mt = item.Type; - - // bind to InsertFacts if there are any members to syncronize - List membersToSync = GetAutoSyncMembers(mt, UpdateType.Insert); - ParameterExpression p = Expression.Parameter(item.Type.Table.RowType.Type, "p"); - if (membersToSync.Count > 0) { - Expression autoSync = this.CreateAutoSync(membersToSync, p); - LambdaExpression resultSelector = Expression.Lambda(autoSync, p); - return Expression.Call(typeof(DataManipulation), "Insert", new Type[] { item.Type.InheritanceRoot.Type, resultSelector.Body.Type }, Expression.Constant(item.Current), resultSelector); - } - else { - return Expression.Call(typeof(DataManipulation), "Insert", new Type[] { item.Type.InheritanceRoot.Type }, Expression.Constant(item.Current)); - } - } - - /// - /// For the meta members specified, create an array initializer for each and bind to - /// an output array. - /// - private Expression CreateAutoSync(List membersToSync, Expression source) { - System.Diagnostics.Debug.Assert(membersToSync.Count > 0); - int i = 0; - Expression[] initializers = new Expression[membersToSync.Count]; - foreach (MetaDataMember mm in membersToSync) { - initializers[i++] = Expression.Convert(this.GetMemberExpression(source, mm.Member), typeof(object)); - } - return Expression.NewArrayInit(typeof(object), initializers); - } - - private static List GetAutoSyncMembers(MetaType metaType, UpdateType updateType) { - List membersToSync = new List(); - foreach (MetaDataMember metaMember in metaType.PersistentDataMembers.OrderBy(m => m.Ordinal)) { - // add all auto generated members for the specified update type to the auto-[....] list - if ((updateType == UpdateType.Insert && metaMember.AutoSync == AutoSync.OnInsert) || - (updateType == UpdateType.Update && metaMember.AutoSync == AutoSync.OnUpdate) || - metaMember.AutoSync == AutoSync.Always) { - membersToSync.Add(metaMember); - } - } - return membersToSync; - } - - /// - /// Synchronize the specified item by copying in data from the specified results. - /// Used to [....] members after successful insert or update, but also used to rollback to previous values if a failure - /// occurs on other entities in the same SubmitChanges batch. - /// - /// - /// If AutoSyncBehavior.ApplyNewAutoSync, the current value of the property is saved before the [....] occurs. This is used for normal synchronization after a successful update/insert. - /// Otherwise, the current value is not saved. This is used for rollback operations when something in the SubmitChanges batch failed, rendering the previously-[....]'d values invalid. - /// - [SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification="[....]: FxCop bug Dev10:423110 -- List> is not supposed to be flagged as a violation.")] - private void AutoSyncMembers(object[] syncResults, TrackedObject item, UpdateType updateType, AutoSyncBehavior autoSyncBehavior) { - System.Diagnostics.Debug.Assert(item != null); - System.Diagnostics.Debug.Assert(item.IsNew || item.IsPossiblyModified, "AutoSyncMembers should only be called for new and modified objects."); - object[] syncRollbackValues = null; - if (syncResults != null) { - int idx = 0; - List membersToSync = GetAutoSyncMembers(item.Type, updateType); - System.Diagnostics.Debug.Assert(syncResults.Length == membersToSync.Count); - if (autoSyncBehavior == AutoSyncBehavior.ApplyNewAutoSync) { - syncRollbackValues = new object[syncResults.Length]; - } - foreach (MetaDataMember mm in membersToSync) { - object value = syncResults[idx]; - object current = item.Current; - MetaAccessor accessor = - (mm.Member is PropertyInfo && ((PropertyInfo)mm.Member).CanWrite) - ? mm.MemberAccessor - : mm.StorageAccessor; - - if (syncRollbackValues != null) { - syncRollbackValues[idx] = accessor.GetBoxedValue(current); - } - accessor.SetBoxedValue(ref current, DBConvert.ChangeType(value, mm.Type)); - idx++; - } - } - if (syncRollbackValues != null) { - this.SyncRollbackItems.Add(new KeyValuePair(item, syncRollbackValues)); - } - } - - private Expression GetUpdateCommand(TrackedObject tracked) { - object database = tracked.Original; - MetaType rowType = tracked.Type.GetInheritanceType(database.GetType()); - MetaType rowTypeRoot = rowType.InheritanceRoot; - - ParameterExpression p = Expression.Parameter(rowTypeRoot.Type, "p"); - Expression pv = p; - if (rowType != rowTypeRoot) { - pv = Expression.Convert(p, rowType.Type); - } - - Expression check = this.GetUpdateCheck(pv, tracked); - if (check != null) { - check = Expression.Lambda(check, p); - } - - // bind to out array if there are any members to synchronize - List membersToSync = GetAutoSyncMembers(rowType, UpdateType.Update); - if (membersToSync.Count > 0) { - Expression autoSync = this.CreateAutoSync(membersToSync, pv); - LambdaExpression resultSelector = Expression.Lambda(autoSync, p); - if (check != null) { - return Expression.Call(typeof(DataManipulation), "Update", new Type[] { rowTypeRoot.Type, resultSelector.Body.Type }, Expression.Constant(tracked.Current), check, resultSelector); - } - else { - return Expression.Call(typeof(DataManipulation), "Update", new Type[] { rowTypeRoot.Type, resultSelector.Body.Type }, Expression.Constant(tracked.Current), resultSelector); - } - } - else if (check != null) { - return Expression.Call(typeof(DataManipulation), "Update", new Type[] { rowTypeRoot.Type }, Expression.Constant(tracked.Current), check); - } - else { - return Expression.Call(typeof(DataManipulation), "Update", new Type[] { rowTypeRoot.Type }, Expression.Constant(tracked.Current)); - } - } - - private Expression GetUpdateCheck(Expression serverItem, TrackedObject tracked) { - MetaType mt = tracked.Type; - if (mt.VersionMember != null) { - return Expression.Equal( - this.GetMemberExpression(serverItem, mt.VersionMember.Member), - this.GetMemberExpression(Expression.Constant(tracked.Current), mt.VersionMember.Member) - ); - } - else { - Expression expr = null; - foreach (MetaDataMember mm in mt.PersistentDataMembers) { - if (!mm.IsPrimaryKey) { - UpdateCheck check = mm.UpdateCheck; - if (check == UpdateCheck.Always || - (check == UpdateCheck.WhenChanged && tracked.HasChangedValue(mm))) { - object memberValue = mm.MemberAccessor.GetBoxedValue(tracked.Original); - Expression eq = - Expression.Equal( - this.GetMemberExpression(serverItem, mm.Member), - Expression.Constant(memberValue, mm.Type) - ); - expr = (expr != null) ? Expression.And(expr, eq) : eq; - } - } - } - return expr; - } - } - - private Expression GetDeleteCommand(TrackedObject tracked) { - MetaType rowType = tracked.Type; - MetaType rowTypeRoot = rowType.InheritanceRoot; - ParameterExpression p = Expression.Parameter(rowTypeRoot.Type, "p"); - Expression pv = p; - if (rowType != rowTypeRoot) { - pv = Expression.Convert(p, rowType.Type); - } - object original = tracked.CreateDataCopy(tracked.Original); - Expression check = this.GetUpdateCheck(pv, tracked); - if (check != null) { - check = Expression.Lambda(check, p); - return Expression.Call(typeof(DataManipulation), "Delete", new Type[] { rowTypeRoot.Type }, Expression.Constant(original), check); - } - else { - return Expression.Call(typeof(DataManipulation), "Delete", new Type[] { rowTypeRoot.Type }, Expression.Constant(original)); - } - } - - private Expression GetDeleteVerificationCommand(TrackedObject tracked) { - ITable table = this.context.GetTable(tracked.Type.InheritanceRoot.Type); - System.Diagnostics.Debug.Assert(table != null); - ParameterExpression p = Expression.Parameter(table.ElementType, "p"); - Expression pred = Expression.Lambda(Expression.Equal(p, Expression.Constant(tracked.Current)), p); - Expression where = Expression.Call(typeof(Queryable), "Where", new Type[] { table.ElementType }, table.Expression, pred); - Expression selector = Expression.Lambda(Expression.Constant(0, typeof(int?)), p); - Expression select = Expression.Call(typeof(Queryable), "Select", new Type[] { table.ElementType, typeof(int?) }, where, selector); - Expression singleOrDefault = Expression.Call(typeof(Queryable), "SingleOrDefault", new Type[] { typeof(int?) }, select); - return singleOrDefault; - } - - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")] - private Expression GetMemberExpression(Expression exp, MemberInfo mi) { - FieldInfo fi = mi as FieldInfo; - if (fi != null) - return Expression.Field(exp, fi); - PropertyInfo pi = (PropertyInfo)mi; - return Expression.Property(exp, pi); - } - } - } -} diff --git a/src/ChangeManagement/ChangeConflictCollection.cs b/src/ChangeManagement/ChangeConflictCollection.cs new file mode 100644 index 0000000..7aedc3b --- /dev/null +++ b/src/ChangeManagement/ChangeConflictCollection.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; +using System.Reflection; +using System.Linq; +using System.Diagnostics; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + using System.Diagnostics.CodeAnalysis; + + public sealed class ChangeConflictCollection : ICollection, ICollection, IEnumerable, IEnumerable + { + private List conflicts; + + internal ChangeConflictCollection() + { + this.conflicts = new List(); + } + + /// + /// The number of conflicts in the collection + /// + public int Count + { + get { return this.conflicts.Count; } + } + + public ObjectChangeConflict this[int index] + { + get { return this.conflicts[index]; } + } + + bool ICollection.IsReadOnly + { + get { return true; } + } + + void ICollection.Add(ObjectChangeConflict item) + { + throw Error.CannotAddChangeConflicts(); + } + + /// + /// Removes the specified conflict from the collection. + /// + /// The conflict to remove + /// + public bool Remove(ObjectChangeConflict item) + { + return this.conflicts.Remove(item); + } + + /// + /// Removes all conflicts from the collection + /// + public void Clear() + { + this.conflicts.Clear(); + } + + /// + /// Returns true if the specified conflict is a member of the collection. + /// + /// + /// + public bool Contains(ObjectChangeConflict item) + { + return this.conflicts.Contains(item); + } + + public void CopyTo(ObjectChangeConflict[] array, int arrayIndex) + { + this.conflicts.CopyTo(array, arrayIndex); + } + + /// + /// Returns the enumerator for the collection. + /// + /// + public IEnumerator GetEnumerator() + { + return this.conflicts.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.conflicts.GetEnumerator(); + } + + bool ICollection.IsSynchronized + { + get { return false; } + } + + object ICollection.SyncRoot + { + get { return null; } + } + + void ICollection.CopyTo(Array array, int index) + { + ((ICollection)this.conflicts).CopyTo(array, index); + } + + /// + /// Resolves all conflicts in the collection using the specified strategy. + /// + /// The strategy to use to resolve the conflicts. + public void ResolveAll(RefreshMode mode) + { + this.ResolveAll(mode, true); + } + + /// + /// Resolves all conflicts in the collection using the specified strategy. + /// + /// The strategy to use to resolve the conflicts. + /// If true conflicts resulting from the modified + /// object no longer existing in the database will be automatically resolved. + public void ResolveAll(RefreshMode mode, bool autoResolveDeletes) + { + foreach(ObjectChangeConflict c in this.conflicts) + { + if(!c.IsResolved) + { + c.Resolve(mode, autoResolveDeletes); + } + } + } + + internal void Fill(List conflictList) + { + this.conflicts = conflictList; + } + } +} + diff --git a/src/ChangeManagement/ChangeConflictSession.cs b/src/ChangeManagement/ChangeConflictSession.cs new file mode 100644 index 0000000..ad1914a --- /dev/null +++ b/src/ChangeManagement/ChangeConflictSession.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; +using System.Reflection; +using System.Linq; +using System.Diagnostics; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + using System.Diagnostics.CodeAnalysis; + + internal sealed class ChangeConflictSession + { + private DataContext context; + private DataContext refreshContext; + + internal ChangeConflictSession(DataContext context) + { + this.context = context; + } + + internal DataContext Context + { + get { return this.context; } + } + + internal DataContext RefreshContext + { + get + { + if(this.refreshContext == null) + { + this.refreshContext = this.context.CreateRefreshContext(); + } + return this.refreshContext; + } + } + } +} + diff --git a/src/ChangeManagement/ChangeDirector.cs b/src/ChangeManagement/ChangeDirector.cs new file mode 100644 index 0000000..481cb8c --- /dev/null +++ b/src/ChangeManagement/ChangeDirector.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq.Expressions; +using System.Text; +using System.Reflection; +using System.Linq; +using System.Security.Permissions; +using System.Security; + +namespace System.Data.Linq { + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + using System.Diagnostics.CodeAnalysis; + + + /// + /// Controls how inserts, updates and deletes are performed. + /// + internal abstract class ChangeDirector { + internal abstract int Insert(TrackedObject item); + internal abstract int DynamicInsert(TrackedObject item); + internal abstract void AppendInsertText(TrackedObject item, StringBuilder appendTo); + + internal abstract int Update(TrackedObject item); + internal abstract int DynamicUpdate(TrackedObject item); + internal abstract void AppendUpdateText(TrackedObject item, StringBuilder appendTo); + + internal abstract int Delete(TrackedObject item); + internal abstract int DynamicDelete(TrackedObject item); + internal abstract void AppendDeleteText(TrackedObject item, StringBuilder appendTo); + + internal abstract void RollbackAutoSync(); + internal abstract void ClearAutoSyncRollback(); + + internal static ChangeDirector CreateChangeDirector(DataContext context) { + return new StandardChangeDirector(context); + } + + } +} diff --git a/src/ChangeManagement/ChangeProcessor.cs b/src/ChangeManagement/ChangeProcessor.cs new file mode 100644 index 0000000..3d77e44 --- /dev/null +++ b/src/ChangeManagement/ChangeProcessor.cs @@ -0,0 +1,919 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Diagnostics; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + + internal class ChangeProcessor + { + CommonDataServices services; + DataContext context; + ChangeTracker tracker; + ChangeDirector changeDirector; + EdgeMap currentParentEdges; + EdgeMap originalChildEdges; + ReferenceMap originalChildReferences; + + #region Private classes + + private class EdgeMap + { + Dictionary> associations; + + internal EdgeMap() + { + this.associations = new Dictionary>(); + } + + internal void Add(MetaAssociation assoc, TrackedObject from, TrackedObject to) + { + Dictionary pairs; + if(!associations.TryGetValue(assoc, out pairs)) + { + pairs = new Dictionary(); + associations.Add(assoc, pairs); + } + pairs.Add(from, to); + } + + internal TrackedObject this[MetaAssociation assoc, TrackedObject from] + { + get + { + Dictionary pairs; + if(associations.TryGetValue(assoc, out pairs)) + { + TrackedObject to; + if(pairs.TryGetValue(from, out to)) + { + return to; + } + } + return null; + } + } + internal void Clear() + { + this.associations.Clear(); + } + } + + private class ReferenceMap + { + Dictionary> references; + + internal ReferenceMap() + { + this.references = new Dictionary>(); + } + + internal void Add(TrackedObject from, TrackedObject to) + { + List refs; + if(!references.TryGetValue(from, out refs)) + { + refs = new List(); + references.Add(from, refs); + } + if(!refs.Contains(to)) + refs.Add(to); + } + + internal IEnumerable this[TrackedObject from] + { + get + { + List refs; + if(references.TryGetValue(from, out refs)) + { + return refs; + } + return Empty; + } + } + + internal void Clear() + { + this.references.Clear(); + } + + private static TrackedObject[] Empty = new TrackedObject[] { }; + } + #endregion + + + internal ChangeProcessor(CommonDataServices services, DataContext context) + { + this.services = services; + this.context = context; + this.tracker = services.ChangeTracker; + this.changeDirector = services.ChangeDirector; + this.currentParentEdges = new EdgeMap(); + this.originalChildEdges = new EdgeMap(); + this.originalChildReferences = new ReferenceMap(); + } + + + internal void SubmitChanges(ConflictMode failureMode) + { + this.TrackUntrackedObjects(); + // Must apply inferred deletions only after any untracked objects + // are tracked + this.ApplyInferredDeletions(); + this.BuildEdgeMaps(); + + var list = this.GetOrderedList(); + + ValidateAll(list); + + int numUpdatesAttempted = 0; + ChangeConflictSession conflictSession = new ChangeConflictSession(this.context); + List conflicts = new List(); + List deletedItems = new List(); + List insertedItems = new List(); + List syncDependentItems = new List(); + + foreach(TrackedObject item in list) + { + try + { + if(item.IsNew) + { + if(item.SynchDependentData()) + { + syncDependentItems.Add(item); + } + changeDirector.Insert(item); + // store all inserted items for post processing + insertedItems.Add(item); + } + else if(item.IsDeleted) + { + // Delete returns 1 if the delete was successfull, 0 if the row exists + // but wasn't deleted due to an OC conflict, or -1 if the row was + // deleted by another context (no OC conflict in this case) + numUpdatesAttempted++; + int ret = changeDirector.Delete(item); + if(ret == 0) + { + conflicts.Add(new ObjectChangeConflict(conflictSession, item, false)); + } + else + { + // store all deleted items for post processing + deletedItems.Add(item); + } + } + else if(item.IsPossiblyModified) + { + if(item.SynchDependentData()) + { + syncDependentItems.Add(item); + } + if(item.IsModified) + { + CheckForInvalidChanges(item); + numUpdatesAttempted++; + if(changeDirector.Update(item) <= 0) + { + conflicts.Add(new ObjectChangeConflict(conflictSession, item)); + } + } + } + } + catch(ChangeConflictException) + { + conflicts.Add(new ObjectChangeConflict(conflictSession, item)); + } + if(conflicts.Count > 0 && failureMode == ConflictMode.FailOnFirstConflict) + { + break; + } + } + + // if we have accumulated any failed updates, throw the exception now + if(conflicts.Count > 0) + { + // First we need to rollback any value that have already been auto-[....]'d, since the values are no longer valid on the server + changeDirector.RollbackAutoSync(); + // Also rollback any dependent items that were [....]'d, since their parent values may have been rolled back + foreach(TrackedObject syncDependentItem in syncDependentItems) + { + Debug.Assert(syncDependentItem.IsNew || syncDependentItem.IsPossiblyModified, "SynchDependent data should only be rolled back for new and modified objects."); + syncDependentItem.SynchDependentData(); + } + this.context.ChangeConflicts.Fill(conflicts); + throw CreateChangeConflictException(numUpdatesAttempted, conflicts.Count); + } + else + { + // No conflicts occurred, so we don't need to save the rollback values anymore + changeDirector.ClearAutoSyncRollback(); + } + + // Only after all updates have been sucessfully processed do we want to make + // post processing modifications to the objects and/or cache state. + PostProcessUpdates(insertedItems, deletedItems); + } + + private void PostProcessUpdates(List insertedItems, List deletedItems) + { + // perform post delete processing + foreach(TrackedObject deletedItem in deletedItems) + { + // remove deleted item from identity cache + this.services.RemoveCachedObjectLike(deletedItem.Type, deletedItem.Original); + ClearForeignKeyReferences(deletedItem); + } + + // perform post insert processing + foreach(TrackedObject insertedItem in insertedItems) + { + object lookup = this.services.InsertLookupCachedObject(insertedItem.Type, insertedItem.Current); + if(lookup != insertedItem.Current) + { + throw new DuplicateKeyException(insertedItem.Current, Strings.DatabaseGeneratedAlreadyExistingKey); + } + insertedItem.InitializeDeferredLoaders(); + } + } + + /// + /// Clears out the foreign key values and parent object references for deleted objects on the child side of a relationship. + /// For bi-directional relationships, also performs the following fixup: + /// - for 1:N we remove the deleted entity from the opposite EntitySet or collection + /// - for 1:1 we null out the back reference + /// + private void ClearForeignKeyReferences(TrackedObject to) + { + Debug.Assert(to.IsDeleted, "Foreign key reference cleanup should only happen on Deleted objects."); + foreach(MetaAssociation assoc in to.Type.Associations) + { + if(assoc.IsForeignKey) + { + // If there is a member on the other side referring back to us (i.e. this is a bi-directional relationship), + // we want to do a cache lookup to find the other side, then will remove ourselves from that collection. + // This cache lookup is only possible if the other key is the primary key, since that is the only way items can be found in the cache. + if(assoc.OtherMember != null && assoc.OtherKeyIsPrimaryKey) + { + Debug.Assert(assoc.OtherMember.IsAssociation, "OtherMember of the association is expected to also be an association."); + // Search the cache for the target of the association, since + // it might not be loaded on the object being deleted, and we + // don't want to force a load. + object[] keyValues = CommonDataServices.GetForeignKeyValues(assoc, to.Current); + object cached = this.services.IdentityManager.Find(assoc.OtherType, keyValues); + + if(cached != null) + { + if(assoc.OtherMember.Association.IsMany) + { + // Note that going through the IList interface handles + // EntitySet as well as POCO collections that implement IList + // and are not FixedSize. + System.Collections.IList collection = assoc.OtherMember.MemberAccessor.GetBoxedValue(cached) as System.Collections.IList; + if(collection != null && !collection.IsFixedSize) + { + collection.Remove(to.Current); + // Explicitly clear the foreign key values and parent object reference + ClearForeignKeysHelper(assoc, to.Current); + } + } + else + { + // Null out the other association. Since this is a 1:1 association, + // we're not concerned here with causing a deferred load, since the + // target is already cached (since we're deleting it). + assoc.OtherMember.MemberAccessor.SetBoxedValue(ref cached, null); + // Explicitly clear the foreign key values and parent object reference + ClearForeignKeysHelper(assoc, to.Current); + } + } + // else the item was not found in the cache, so there is no fixup that has to be done + // We are explicitly not calling ClearForeignKeysHelper because it breaks existing shipped behavior and we want to maintain backward compatibility + } + else + { + // This is a unidirectional relationship or we have no way to look up the other side in the cache, so just clear our own side + ClearForeignKeysHelper(assoc, to.Current); + } + } + // else this is not the 1-side (foreign key) of the relationship, so there is nothing for us to do + } + } + + // Ensure the the member and foreign keys are nulled so that after trackedInstance is deleted, + // the object does not appear to be associated with the other side anymore. This prevents the deleted object + // from referencing objects still in the cache, but also will prevent the related object from being implicitly loaded + private static void ClearForeignKeysHelper(MetaAssociation assoc, object trackedInstance) + { + Debug.Assert(assoc.IsForeignKey, "Foreign key clearing should only happen on foreign key side of the association."); + Debug.Assert(assoc.ThisMember.IsAssociation, "Expected ThisMember of an association to always be an association."); + + // If this member is one of our deferred loaders, and it does not already have a value, explicitly set the deferred source to + // null so that when we set the association member itself to null later, it doesn't trigger an implicit load. + // This is only necessary if the value has not already been assigned or set, because otherwise we won't implicitly load anyway when the member is accessed. + MetaDataMember thisMember = assoc.ThisMember; + + if(thisMember.IsDeferred && + !(thisMember.StorageAccessor.HasAssignedValue(trackedInstance) || thisMember.StorageAccessor.HasLoadedValue(trackedInstance))) + { + // If this is a deferred member, set the value directly in the deferred accessor instead of going + // through the normal member accessor, so that we don't trigger an implicit load. + thisMember.DeferredSourceAccessor.SetBoxedValue(ref trackedInstance, null); + } + + // Notify the object that the relationship should be considered deleted. + // This allows the object to do its own fixup even when we can't do it automatically. + thisMember.MemberAccessor.SetBoxedValue(ref trackedInstance, null); + + // Also set the foreign key values to null if possible + for(int i = 0, n = assoc.ThisKey.Count; i < n; i++) + { + MetaDataMember thisKey = assoc.ThisKey[i]; + if(thisKey.CanBeNull) + { + thisKey.StorageAccessor.SetBoxedValue(ref trackedInstance, null); + } + } + } + + private static void ValidateAll(IEnumerable list) + { + foreach(var item in list) + { + if(item.IsNew) + { + item.SynchDependentData(); + if(item.Type.HasAnyValidateMethod) + { + SendOnValidate(item.Type, item, ChangeAction.Insert); + } + } + else if(item.IsDeleted) + { + if(item.Type.HasAnyValidateMethod) + { + SendOnValidate(item.Type, item, ChangeAction.Delete); + } + } + else if(item.IsPossiblyModified) + { + item.SynchDependentData(); + if(item.IsModified && item.Type.HasAnyValidateMethod) + { + SendOnValidate(item.Type, item, ChangeAction.Update); + } + } + } + } + + private static void SendOnValidate(MetaType type, TrackedObject item, ChangeAction changeAction) + { + if(type != null) + { + SendOnValidate(type.InheritanceBase, item, changeAction); + + if(type.OnValidateMethod != null) + { + try + { + type.OnValidateMethod.Invoke(item.Current, new object[] { changeAction }); + } + catch(TargetInvocationException tie) + { + if(tie.InnerException != null) + { + throw tie.InnerException; + } + + throw; + } + } + } + } + + internal string GetChangeText() + { + this.ObserveUntrackedObjects(); + // Must apply inferred deletions only after any untracked objects + // are tracked + this.ApplyInferredDeletions(); + this.BuildEdgeMaps(); + + // append change text only + StringBuilder changeText = new StringBuilder(); + foreach(TrackedObject item in this.GetOrderedList()) + { + if(item.IsNew) + { + item.SynchDependentData(); + changeDirector.AppendInsertText(item, changeText); + } + else if(item.IsDeleted) + { + changeDirector.AppendDeleteText(item, changeText); + } + else if(item.IsPossiblyModified) + { + item.SynchDependentData(); + if(item.IsModified) + { + changeDirector.AppendUpdateText(item, changeText); + } + } + } + return changeText.ToString(); + } + + internal ChangeSet GetChangeSet() + { + List newEntities = new List(); + List deletedEntities = new List(); + List changedEntities = new List(); + + this.ObserveUntrackedObjects(); + // Must apply inferred deletions only after any untracked objects + // are tracked + this.ApplyInferredDeletions(); + + foreach(TrackedObject item in this.tracker.GetInterestingObjects()) + { + if(item.IsNew) + { + item.SynchDependentData(); + newEntities.Add(item.Current); + } + else if(item.IsDeleted) + { + deletedEntities.Add(item.Current); + } + else if(item.IsPossiblyModified) + { + item.SynchDependentData(); + if(item.IsModified) + { + changedEntities.Add(item.Current); + } + } + } + + return new ChangeSet(newEntities.AsReadOnly(), deletedEntities.AsReadOnly(), changedEntities.AsReadOnly()); + } + + // verify that primary key and db-generated values have not changed + private static void CheckForInvalidChanges(TrackedObject tracked) + { + foreach(MetaDataMember mem in tracked.Type.PersistentDataMembers) + { + if(mem.IsPrimaryKey || mem.IsDbGenerated || mem.IsVersion) + { + if(tracked.HasChangedValue(mem)) + { + if(mem.IsPrimaryKey) + { + throw Error.IdentityChangeNotAllowed(mem.Name, tracked.Type.Name); + } + else + { + throw Error.DbGeneratedChangeNotAllowed(mem.Name, tracked.Type.Name); + } + } + } + } + } + + /// + /// Create an ChangeConflictException with the best message + /// + static private ChangeConflictException CreateChangeConflictException(int totalUpdatesAttempted, int failedUpdates) + { + string msg = Strings.RowNotFoundOrChanged; + if(totalUpdatesAttempted > 1) + { + msg = Strings.UpdatesFailedMessage(failedUpdates, totalUpdatesAttempted); + } + return new ChangeConflictException(msg); + } + + internal void TrackUntrackedObjects() + { + Dictionary visited = new Dictionary(); + + // search for untracked new objects + List items = new List(this.tracker.GetInterestingObjects()); + foreach(TrackedObject item in items) + { + this.TrackUntrackedObjects(item.Type, item.Current, visited); + } + } + + internal void ApplyInferredDeletions() + { + foreach(TrackedObject item in this.tracker.GetInterestingObjects()) + { + if(item.CanInferDelete()) + { + // based on DeleteOnNull specifications on the item's associations, + // a deletion can be inferred for this item. The actual state transition + // is dependent on the current item state. + if(item.IsNew) + { + item.ConvertToRemoved(); + } + else if(item.IsPossiblyModified || item.IsModified) + { + item.ConvertToDeleted(); + } + } + } + } + + private void TrackUntrackedObjects(MetaType type, object item, Dictionary visited) + { + if(!visited.ContainsKey(item)) + { + visited.Add(item, item); + TrackedObject tracked = this.tracker.GetTrackedObject(item); + if(tracked == null) + { + tracked = this.tracker.Track(item); + tracked.ConvertToNew(); + } + else if(tracked.IsDead || tracked.IsRemoved) + { + // ignore + return; + } + + // search parents (objects we are dependent on) + foreach(RelatedItem parent in this.services.GetParents(type, item)) + { + this.TrackUntrackedObjects(parent.Type, parent.Item, visited); + } + + // synch up primary key + if(tracked.IsNew) + { + tracked.InitializeDeferredLoaders(); + + if(!tracked.IsPendingGeneration(tracked.Type.IdentityMembers)) + { + tracked.SynchDependentData(); + object cached = this.services.InsertLookupCachedObject(tracked.Type, item); + if(cached != item) + { + TrackedObject cachedTracked = this.tracker.GetTrackedObject(cached); + Debug.Assert(cachedTracked != null); + if(cachedTracked.IsDeleted || cachedTracked.CanInferDelete()) + { + // adding new object with same ID as object being deleted.. turn into modified + tracked.ConvertToPossiblyModified(cachedTracked.Original); + // turn deleted to dead... + cachedTracked.ConvertToDead(); + + this.services.RemoveCachedObjectLike(tracked.Type, item); + this.services.InsertLookupCachedObject(tracked.Type, item); + } + else if(!cachedTracked.IsDead) + { + throw new DuplicateKeyException(item, Strings.CantAddAlreadyExistingKey); + } + } + } + else + { + // we may have a generated PK, however we set the PK on this new item to + // match a deleted item + object cached = this.services.GetCachedObjectLike(tracked.Type, item); + if(cached != null) + { + TrackedObject cachedTracked = this.tracker.GetTrackedObject(cached); + Debug.Assert(cachedTracked != null); + if(cachedTracked.IsDeleted || cachedTracked.CanInferDelete()) + { + // adding new object with same ID as object being deleted.. turn into modified + tracked.ConvertToPossiblyModified(cachedTracked.Original); + // turn deleted to dead... + cachedTracked.ConvertToDead(); + + this.services.RemoveCachedObjectLike(tracked.Type, item); + this.services.InsertLookupCachedObject(tracked.Type, item); + } + } + } + } + + // search children (objects that are dependent on us) + foreach(RelatedItem child in this.services.GetChildren(type, item)) + { + this.TrackUntrackedObjects(child.Type, child.Item, visited); + } + } + } + + internal void ObserveUntrackedObjects() + { + Dictionary visited = new Dictionary(); + + List items = new List(this.tracker.GetInterestingObjects()); + foreach(TrackedObject item in items) + { + this.ObserveUntrackedObjects(item.Type, item.Current, visited); + } + } + + private void ObserveUntrackedObjects(MetaType type, object item, Dictionary visited) + { + if(!visited.ContainsKey(item)) + { + visited.Add(item, item); + TrackedObject tracked = this.tracker.GetTrackedObject(item); + if(tracked == null) + { + tracked = this.tracker.Track(item); + tracked.ConvertToNew(); + } + else if(tracked.IsDead || tracked.IsRemoved) + { + // ignore + return; + } + + // search parents (objects we are dependent on) + foreach(RelatedItem parent in this.services.GetParents(type, item)) + { + this.ObserveUntrackedObjects(parent.Type, parent.Item, visited); + } + + // synch up primary key unless its generated. + if(tracked.IsNew) + { + if(!tracked.IsPendingGeneration(tracked.Type.IdentityMembers)) + { + tracked.SynchDependentData(); + } + } + + // search children (objects that are dependent on us) + foreach(RelatedItem child in this.services.GetChildren(type, item)) + { + this.ObserveUntrackedObjects(child.Type, child.Item, visited); + } + } + } + + private TrackedObject GetOtherItem(MetaAssociation assoc, object instance) + { + if(instance == null) + return null; + object other = null; + // Don't load unloaded references + if(assoc.ThisMember.StorageAccessor.HasAssignedValue(instance) || + assoc.ThisMember.StorageAccessor.HasLoadedValue(instance) + ) + { + other = assoc.ThisMember.MemberAccessor.GetBoxedValue(instance); + } + else if(assoc.OtherKeyIsPrimaryKey) + { + // Maybe it's in the cache, but not yet attached through reference. + object[] foreignKeys = CommonDataServices.GetForeignKeyValues(assoc, instance); + other = this.services.GetCachedObject(assoc.OtherType, foreignKeys); + } + // else the other key is not the primary key so there is no way to try to look it up + return (other != null) ? this.tracker.GetTrackedObject(other) : null; + } + + private bool HasAssociationChanged(MetaAssociation assoc, TrackedObject item) + { + if(item.Original != null && item.Current != null) + { + if(assoc.ThisMember.StorageAccessor.HasAssignedValue(item.Current) || + assoc.ThisMember.StorageAccessor.HasLoadedValue(item.Current) + ) + { + return this.GetOtherItem(assoc, item.Current) != this.GetOtherItem(assoc, item.Original); + } + else + { + object[] currentFKs = CommonDataServices.GetForeignKeyValues(assoc, item.Current); + object[] originaFKs = CommonDataServices.GetForeignKeyValues(assoc, item.Original); + for(int i = 0, n = currentFKs.Length; i < n; i++) + { + if(!object.Equals(currentFKs[i], originaFKs[i])) + return true; + } + } + } + return false; + } + + private void BuildEdgeMaps() + { + this.currentParentEdges.Clear(); + this.originalChildEdges.Clear(); + this.originalChildReferences.Clear(); + + List list = new List(this.tracker.GetInterestingObjects()); + foreach(TrackedObject item in list) + { + bool isNew = item.IsNew; + MetaType mt = item.Type; + foreach(MetaAssociation assoc in mt.Associations) + { + if(assoc.IsForeignKey) + { + TrackedObject otherItem = this.GetOtherItem(assoc, item.Current); + TrackedObject dbOtherItem = this.GetOtherItem(assoc, item.Original); + bool pointsToDeleted = (otherItem != null && otherItem.IsDeleted) || (dbOtherItem != null && dbOtherItem.IsDeleted); + bool pointsToNew = (otherItem != null && otherItem.IsNew); + + if(isNew || pointsToDeleted || pointsToNew || this.HasAssociationChanged(assoc, item)) + { + if(otherItem != null) + { + this.currentParentEdges.Add(assoc, item, otherItem); + } + if(dbOtherItem != null) + { + if(assoc.IsUnique) + { + this.originalChildEdges.Add(assoc, dbOtherItem, item); + } + this.originalChildReferences.Add(dbOtherItem, item); + } + } + } + } + } + } + + enum VisitState + { + Before, + After + } + + private List GetOrderedList() + { + var objects = this.tracker.GetInterestingObjects().ToList(); + + // give list an initial order (most likely correct order) to avoid deadlocks in server + var range = Enumerable.Range(0, objects.Count).ToList(); + range.Sort((int x, int y) => Compare(objects[x], x, objects[y], y)); + var ordered = range.Select(i => objects[i]).ToList(); + + // permute order if constraint dependencies requires some changes to come before others + var visited = new Dictionary(); + var list = new List(); + foreach(TrackedObject item in ordered) + { + this.BuildDependencyOrderedList(item, list, visited); + } + return list; + } + + private static int Compare(TrackedObject x, int xOrdinal, TrackedObject y, int yOrdinal) + { + // deal with possible nulls + if(x == y) + { + return 0; + } + if(x == null) + { + return -1; + } + else if(y == null) + { + return 1; + } + // first order by action: Inserts first, Updates, Deletes last + int xAction = x.IsNew ? 0 : x.IsDeleted ? 2 : 1; + int yAction = y.IsNew ? 0 : y.IsDeleted ? 2 : 1; + if(xAction < yAction) + { + return -1; + } + else if(xAction > yAction) + { + return 1; + } + // no need to order inserts (PK's may not even exist) + if(x.IsNew) + { + // keep original order + return xOrdinal.CompareTo(yOrdinal); + } + // second order by type + if(x.Type != y.Type) + { + return string.CompareOrdinal(x.Type.Type.FullName, y.Type.Type.FullName); + } + // lastly, order by PK values + int result = 0; + foreach(MetaDataMember mm in x.Type.IdentityMembers) + { + object xValue = mm.StorageAccessor.GetBoxedValue(x.Current); + object yValue = mm.StorageAccessor.GetBoxedValue(y.Current); + if(xValue == null) + { + if(yValue != null) + { + return -1; + } + } + else + { + IComparable xc = xValue as IComparable; + if(xc != null) + { + result = xc.CompareTo(yValue); + if(result != 0) + { + return result; + } + } + } + } + // they are the same? leave in original order + return xOrdinal.CompareTo(yOrdinal); + } + + private void BuildDependencyOrderedList(TrackedObject item, List list, Dictionary visited) + { + VisitState state; + if(visited.TryGetValue(item, out state)) + { + if(state == VisitState.Before) + { + throw Error.CycleDetected(); + } + return; + } + + visited[item] = VisitState.Before; + + if(item.IsInteresting) + { + if(item.IsDeleted) + { + // if 'item' is deleted + // all objects that used to refer to 'item' must be ordered before item + foreach(TrackedObject other in this.originalChildReferences[item]) + { + if(other != item) + { + this.BuildDependencyOrderedList(other, list, visited); + } + } + } + else + { + // if 'item' is new or changed + // for all objects 'other' that 'item' refers to along association 'assoc' + // if 'other' is new then 'other' must be ordered before 'item' + // if 'assoc' is pure one-to-one and some other item 'prevItem' used to refer to 'other' + // then 'prevItem' must be ordered before 'item' + foreach(MetaAssociation assoc in item.Type.Associations) + { + if(assoc.IsForeignKey) + { + TrackedObject other = this.currentParentEdges[assoc, item]; + if(other != null) + { + if(other.IsNew) + { + // if other is new, visit other first (since item's FK depends on it) + if(other != item || item.Type.DBGeneratedIdentityMember != null) + { + this.BuildDependencyOrderedList(other, list, visited); + } + } + else if((assoc.IsUnique || assoc.ThisKeyIsPrimaryKey)) + { + TrackedObject prevItem = this.originalChildEdges[assoc, other]; + if(prevItem != null && other != item) + { + this.BuildDependencyOrderedList(prevItem, list, visited); + } + } + } + } + } + } + + list.Add(item); + } + + visited[item] = VisitState.After; + } + + } +} diff --git a/src/ChangeManagement/ChangeSet.cs b/src/ChangeManagement/ChangeSet.cs new file mode 100644 index 0000000..98ab835 --- /dev/null +++ b/src/ChangeManagement/ChangeSet.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Configuration; +using System.Data; +using System.Data.Common; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Transactions; +using System.Xml; +using System.Runtime.CompilerServices; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + using System.Diagnostics.CodeAnalysis; + using System.Data.Linq.BindingLists; + + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "ChangeSet", Justification = "The capitalization was deliberately chosen.")] + public sealed class ChangeSet + { + ReadOnlyCollection inserts; + ReadOnlyCollection deletes; + ReadOnlyCollection updates; + + internal ChangeSet( + ReadOnlyCollection inserts, + ReadOnlyCollection deletes, + ReadOnlyCollection updates + ) + { + this.inserts = inserts; + this.deletes = deletes; + this.updates = updates; + } + + public IList Inserts + { + get { return this.inserts; } + } + + public IList Deletes + { + get { return this.deletes; } + } + + public IList Updates + { + get { return this.updates; } + } + + public override string ToString() + { + return "{" + + string.Format( + Globalization.CultureInfo.InvariantCulture, + "Inserts: {0}, Deletes: {1}, Updates: {2}", + this.Inserts.Count, + this.Deletes.Count, + this.Updates.Count + ) + "}"; + } + } +} + diff --git a/src/ChangeManagement/ChangeTracker.cs b/src/ChangeManagement/ChangeTracker.cs new file mode 100644 index 0000000..78636dc --- /dev/null +++ b/src/ChangeManagement/ChangeTracker.cs @@ -0,0 +1,1049 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + + internal abstract class ChangeTracker + { + /// + /// Starts tracking an object as 'unchanged' + /// + /// + /// + internal abstract TrackedObject Track(object obj); + /// + /// Starts tracking an object as 'unchanged', and optionally + /// 'weakly' tracks all other referenced objects recursively. + /// + /// + /// True if all untracked objects in the graph + /// should be tracked recursively. + /// + internal abstract TrackedObject Track(object obj, bool recurse); + /// + /// Fast-tracks an object that is already in identity cache + /// + /// + internal abstract void FastTrack(object obj); + internal abstract bool IsTracked(object obj); + internal abstract TrackedObject GetTrackedObject(object obj); + internal abstract void StopTracking(object obj); + internal abstract void AcceptChanges(); + internal abstract IEnumerable GetInterestingObjects(); + + + #region Private classes + /// + /// This is the implementation used when change tracking is disabled. + /// + private class ReadOnlyChangeTracker : ChangeTracker + { + internal override TrackedObject Track(object obj) { return null; } + internal override TrackedObject Track(object obj, bool recurse) { return null; } + internal override void FastTrack(object obj) { } + internal override bool IsTracked(object obj) { return false; } + internal override TrackedObject GetTrackedObject(object obj) { return null; } + internal override void StopTracking(object obj) { } + internal override void AcceptChanges() { } + internal override IEnumerable GetInterestingObjects() { return new TrackedObject[0]; } + } + #endregion + + internal static ChangeTracker CreateChangeTracker(CommonDataServices dataServices, bool asReadOnly) + { + if(asReadOnly) + { + return new ReadOnlyChangeTracker(); + } + else + { + return new StandardChangeTracker(dataServices); + } + } + + class StandardChangeTracker : ChangeTracker + { + Dictionary items; + PropertyChangingEventHandler onPropertyChanging; + CommonDataServices services; + + internal StandardChangeTracker(CommonDataServices services) + { + this.services = services; + this.items = new Dictionary(); + this.onPropertyChanging = new PropertyChangingEventHandler(this.OnPropertyChanging); + } + + /// + /// Given a type root and a discriminator, return the type that would be instantiated. + /// + private static MetaType TypeFromDiscriminator(MetaType root, object discriminator) + { + foreach(MetaType type in root.InheritanceTypes) + { + if(IsSameDiscriminator(discriminator, type.InheritanceCode)) + return type; + } + return root.InheritanceDefault; + } + + private static bool IsSameDiscriminator(object discriminator1, object discriminator2) + { + if(discriminator1 == discriminator2) + { + return true; + } + if(discriminator1 == null || discriminator2 == null) + { + return false; + } + return discriminator1.Equals(discriminator2); + } + + internal override TrackedObject Track(object obj) + { + return Track(obj, false); + } + + internal override TrackedObject Track(object obj, bool recurse) + { + MetaType type = this.services.Model.GetMetaType(obj.GetType()); + Dictionary visited = new Dictionary(); + return Track(type, obj, visited, recurse, 1); + } + + private TrackedObject Track(MetaType mt, object obj, Dictionary visited, bool recurse, int level) + { + StandardTrackedObject tracked = (StandardTrackedObject)this.GetTrackedObject(obj); + if(tracked != null || visited.ContainsKey(obj)) + { + return tracked; + } + + // The root object tracked is tracked normally - all other objects + // in the reference graph are weakly tracked. + bool weaklyTrack = level > 1; + tracked = new StandardTrackedObject(this, mt, obj, obj, weaklyTrack); + if(tracked.HasDeferredLoaders) + { + throw Error.CannotAttachAddNonNewEntities(); + } + this.items.Add(obj, tracked); + this.Attach(obj); + visited.Add(obj, obj); + + if(recurse) + { + // track parents (objects we are dependent on) + foreach(RelatedItem parent in this.services.GetParents(mt, obj)) + { + this.Track(parent.Type, parent.Item, visited, recurse, level + 1); + } + + // track children (objects that are dependent on us) + foreach(RelatedItem child in this.services.GetChildren(mt, obj)) + { + this.Track(child.Type, child.Item, visited, recurse, level + 1); + } + } + + return tracked; + } + + internal override void FastTrack(object obj) + { + // assumes object is already in identity cache + this.Attach(obj); + } + + internal override void StopTracking(object obj) + { + this.Detach(obj); + this.items.Remove(obj); + } + + internal override bool IsTracked(object obj) + { + return this.items.ContainsKey(obj) || this.IsFastTracked(obj); + } + + private bool IsFastTracked(object obj) + { + MetaType type = this.services.Model.GetTable(obj.GetType()).RowType; + return this.services.IsCachedObject(type, obj); + } + + internal override TrackedObject GetTrackedObject(object obj) + { + StandardTrackedObject ti; + if(!this.items.TryGetValue(obj, out ti)) + { + if(this.IsFastTracked(obj)) + { + return this.PromoteFastTrackedObject(obj); + } + } + return ti; + } + + private StandardTrackedObject PromoteFastTrackedObject(object obj) + { + Type type = obj.GetType(); + MetaType metaType = this.services.Model.GetTable(type).RowType.GetInheritanceType(type); + return this.PromoteFastTrackedObject(metaType, obj); + } + + private StandardTrackedObject PromoteFastTrackedObject(MetaType type, object obj) + { + StandardTrackedObject ti = new StandardTrackedObject(this, type, obj, obj); + this.items.Add(obj, ti); + return ti; + } + + private void Attach(object obj) + { + INotifyPropertyChanging notifier = obj as INotifyPropertyChanging; + if(notifier != null) + { + notifier.PropertyChanging += this.onPropertyChanging; + } + else + { + // if has no notifier, consider it modified already + this.OnPropertyChanging(obj, null); + } + } + + private void Detach(object obj) + { + INotifyPropertyChanging notifier = obj as INotifyPropertyChanging; + if(notifier != null) + { + notifier.PropertyChanging -= this.onPropertyChanging; + } + } + + private void OnPropertyChanging(object sender, PropertyChangingEventArgs args) + { + StandardTrackedObject ti; + if(this.items.TryGetValue(sender, out ti)) + { + ti.StartTracking(); + } + else if(this.IsFastTracked(sender)) + { + ti = this.PromoteFastTrackedObject(sender); + ti.StartTracking(); + } + } + + internal override void AcceptChanges() + { + List list = new List((IEnumerable)this.items.Values); + foreach(TrackedObject item in list) + { + item.AcceptChanges(); + } + } + + internal override IEnumerable GetInterestingObjects() + { + foreach(StandardTrackedObject ti in this.items.Values) + { + if(ti.IsInteresting) + { + yield return ti; + } + } + } + + class StandardTrackedObject : TrackedObject + { + private StandardChangeTracker tracker; + private MetaType type; + private object current; + private object original; + private State state; + private BitArray dirtyMemberCache; + private bool haveInitializedDeferredLoaders; + private bool isWeaklyTracked; + + enum State + { + New, + Deleted, + PossiblyModified, + Modified, + Removed, + Dead + } + + public override string ToString() + { + return type.Name + ":" + GetState(); + } + + private string GetState() + { + switch(this.state) + { + case State.New: + case State.Deleted: + case State.Dead: + case State.Removed: + return this.state.ToString(); + default: + if(this.IsModified) + { + return "Modified"; + } + else + { + return "Unmodified"; + } + } + } + + internal StandardTrackedObject(StandardChangeTracker tracker, MetaType type, object current, object original) + { + if(current == null) + { + throw Error.ArgumentNull("current"); + } + this.tracker = tracker; + this.type = type.GetInheritanceType(current.GetType()); + this.current = current; + this.original = original; + this.state = State.PossiblyModified; + dirtyMemberCache = new BitArray(this.type.DataMembers.Count); + } + + internal StandardTrackedObject(StandardChangeTracker tracker, MetaType type, object current, object original, bool isWeaklyTracked) + : this(tracker, type, current, original) + { + this.isWeaklyTracked = isWeaklyTracked; + } + + internal override bool IsWeaklyTracked + { + get { return isWeaklyTracked; } + } + + internal override MetaType Type + { + get { return this.type; } + } + + internal override object Current + { + get { return this.current; } + } + + internal override object Original + { + get { return this.original; } + } + + internal override bool IsNew + { + get { return this.state == State.New; } + } + + internal override bool IsDeleted + { + get { return this.state == State.Deleted; } + } + + internal override bool IsRemoved + { + get { return this.state == State.Removed; } + } + + internal override bool IsDead + { + get { return this.state == State.Dead; } + } + + internal override bool IsModified + { + get { return this.state == State.Modified || (this.state == State.PossiblyModified && this.current != this.original && this.HasChangedValues()); } + } + + internal override bool IsUnmodified + { + get { return this.state == State.PossiblyModified && (this.current == this.original || !this.HasChangedValues()); } + } + + internal override bool IsPossiblyModified + { + get { return this.state == State.Modified || this.state == State.PossiblyModified; } + } + + internal override bool CanInferDelete() + { + // A delete can be inferred iff there is a non-nullable singleton association that has + // been set to null, and the association has DeleteOnNull = true. + if(this.state == State.Modified || this.state == State.PossiblyModified) + { + foreach(MetaAssociation assoc in Type.Associations) + { + if(assoc.DeleteOnNull && assoc.IsForeignKey && !assoc.IsNullable && !assoc.IsMany && + assoc.ThisMember.StorageAccessor.HasAssignedValue(Current) && + assoc.ThisMember.StorageAccessor.GetBoxedValue(Current) == null) + { + return true; + } + } + } + return false; + } + + internal override bool IsInteresting + { + get + { + return this.state == State.New || + this.state == State.Deleted || + this.state == State.Modified || + (this.state == State.PossiblyModified && this.current != this.original) || + CanInferDelete(); + } + } + + internal override void ConvertToNew() + { + // must be new or unmodified or removed to convert to new + System.Diagnostics.Debug.Assert(this.IsNew || this.IsRemoved || this.IsUnmodified); + this.original = null; + this.state = State.New; + } + + internal override void ConvertToPossiblyModified() + { + System.Diagnostics.Debug.Assert(this.IsPossiblyModified || this.IsDeleted); + this.state = State.PossiblyModified; + this.isWeaklyTracked = false; + } + + internal override void ConvertToModified() + { + System.Diagnostics.Debug.Assert(this.IsPossiblyModified); + System.Diagnostics.Debug.Assert(this.type.VersionMember != null || !this.type.HasUpdateCheck); + this.state = State.Modified; + this.isWeaklyTracked = false; + } + + internal override void ConvertToPossiblyModified(object originalState) + { + // must be modified or unmodified to convert to modified + System.Diagnostics.Debug.Assert(this.IsNew || this.IsPossiblyModified); + System.Diagnostics.Debug.Assert(originalState != null); + System.Diagnostics.Debug.Assert(originalState.GetType() == this.type.Type); + this.state = State.PossiblyModified; + this.original = this.CreateDataCopy(originalState); + this.isWeaklyTracked = false; + } + + internal override void ConvertToDeleted() + { + // must be modified or unmodified to be deleted + System.Diagnostics.Debug.Assert(this.IsDeleted || this.IsPossiblyModified); + this.state = State.Deleted; + this.isWeaklyTracked = false; + } + + internal override void ConvertToDead() + { + System.Diagnostics.Debug.Assert(this.IsDead || this.IsDeleted); + this.state = State.Dead; + this.isWeaklyTracked = false; + } + + internal override void ConvertToRemoved() + { + System.Diagnostics.Debug.Assert(this.IsRemoved || this.IsNew); + this.state = State.Removed; + this.isWeaklyTracked = false; + } + + internal override void ConvertToUnmodified() + { + System.Diagnostics.Debug.Assert(this.IsNew || this.IsPossiblyModified); + // reset to unmodified + this.state = State.PossiblyModified; + if(this.current is INotifyPropertyChanging) + { + this.original = this.current; + } + else + { + this.original = this.CreateDataCopy(this.current); + } + this.ResetDirtyMemberTracking(); + this.isWeaklyTracked = false; + } + + internal override void AcceptChanges() + { + if(IsWeaklyTracked) + { + InitializeDeferredLoaders(); + isWeaklyTracked = false; + } + if(this.IsDeleted) + { + this.ConvertToDead(); + } + else if(this.IsNew) + { + this.InitializeDeferredLoaders(); + this.ConvertToUnmodified(); + } + else if(this.IsPossiblyModified) + { + this.ConvertToUnmodified(); + } + } + + private void AssignMember(object instance, MetaDataMember mm, object value) + { + // In the unnotified case, directly use the storage accessor + // for everything because there are not events to be fired. + if(!(this.current is INotifyPropertyChanging)) + { + mm.StorageAccessor.SetBoxedValue(ref instance, value); + } + else + { + // Go through the member accessor to fire events. + mm.MemberAccessor.SetBoxedValue(ref instance, value); + } + } + + /// + /// Certain state is saved during change tracking to enable modifications + /// to be detected taking refresh operations into account. When changes + /// are reverted or accepted, this state must be reset. + /// + private void ResetDirtyMemberTracking() + { + this.dirtyMemberCache.SetAll(false); + } + + /// + /// Refresh internal tracking state using the original value and mode + /// specified. + /// + internal override void Refresh(RefreshMode mode, object freshInstance) + { + this.SynchDependentData(); + + // This must be done prior to updating original values + this.UpdateDirtyMemberCache(); + + // Apply the refresh strategy to each data member + Type instanceType = freshInstance.GetType(); + foreach(MetaDataMember mm in type.PersistentDataMembers) + { + var memberMode = mm.IsDbGenerated ? RefreshMode.OverwriteCurrentValues : mode; + if(memberMode != RefreshMode.KeepCurrentValues) + { + if(!mm.IsAssociation && (this.Type.Type == instanceType || mm.DeclaringType.Type.IsAssignableFrom(instanceType))) + { + object freshValue = mm.StorageAccessor.GetBoxedValue(freshInstance); + this.RefreshMember(mm, memberMode, freshValue); + } + } + } + + // Make the new data the current original value + this.original = this.CreateDataCopy(freshInstance); + + if(mode == RefreshMode.OverwriteCurrentValues) + { + this.ResetDirtyMemberTracking(); + } + } + + /// + /// Using the last saved comparison baseline, figure out which members have + /// changed since the last refresh, and save that information. This must be + /// done BEFORE any merge operations modify the current values. + /// + private void UpdateDirtyMemberCache() + { + // iterate over all members, and if they differ from + // last read values, mark as dirty + foreach(MetaDataMember mm in type.PersistentDataMembers) + { + if(mm.IsAssociation && mm.Association.IsMany) + { + continue; + } + if(!this.dirtyMemberCache.Get(mm.Ordinal) && this.HasChangedValue(mm)) + { + this.dirtyMemberCache.Set(mm.Ordinal, true); + } + } + } + + internal override void RefreshMember(MetaDataMember mm, RefreshMode mode, object freshValue) + { + System.Diagnostics.Debug.Assert(!mm.IsAssociation); + + if(mode == RefreshMode.KeepCurrentValues) + { + return; + } + + bool hasUserChange = this.HasChangedValue(mm); + + // we don't want to overwrite any modified values, unless + // the mode is original wins + if(hasUserChange && mode != RefreshMode.OverwriteCurrentValues) + return; + + object currentValue = mm.StorageAccessor.GetBoxedValue(this.current); + if(!object.Equals(freshValue, currentValue)) + { + mm.StorageAccessor.SetBoxedValue(ref this.current, freshValue); + + // update all singleton associations that are affected by a change to this member + foreach(MetaDataMember am in this.GetAssociationsForKey(mm)) + { + if(!am.Association.IsMany) + { + IEnumerable ds = this.tracker.services.GetDeferredSourceFactory(am).CreateDeferredSource(this.current); + if(am.StorageAccessor.HasValue(this.current)) + { + this.AssignMember(this.current, am, ds.Cast().SingleOrDefault()); + } + } + } + } + } + + private IEnumerable GetAssociationsForKey(MetaDataMember key) + { + foreach(MetaDataMember mm in this.type.PersistentDataMembers) + { + if(mm.IsAssociation && mm.Association.ThisKey.Contains(key)) + { + yield return mm; + } + } + } + + internal override object CreateDataCopy(object instance) + { + System.Diagnostics.Debug.Assert(instance != null); + Type instanceType = instance.GetType(); + System.Diagnostics.Debug.Assert(instance.GetType() == this.type.Type); + + object copy = Activator.CreateInstance(this.Type.Type); + + MetaType rootMetaType = this.tracker.services.Model.GetTable(instanceType).RowType.InheritanceRoot; + foreach(MetaDataMember mm in rootMetaType.GetInheritanceType(instanceType).PersistentDataMembers) + { + if(this.Type.Type != instanceType && !mm.DeclaringType.Type.IsAssignableFrom(instanceType)) + { + continue; + } + if(mm.IsDeferred) + { + // do not copy associations + if(!mm.IsAssociation) + { + if(mm.StorageAccessor.HasValue(instance)) + { + object value = mm.DeferredValueAccessor.GetBoxedValue(instance); + mm.DeferredValueAccessor.SetBoxedValue(ref copy, value); + } + else + { + IEnumerable ds = this.tracker.services.GetDeferredSourceFactory(mm).CreateDeferredSource(copy); + mm.DeferredSourceAccessor.SetBoxedValue(ref copy, ds); + } + } + } + else + { + // otherwise assign the value as-is to the backup instance + object value = mm.StorageAccessor.GetBoxedValue(instance); + // assumes member values are immutable or will communicate changes to entity + // note: byte[] and char[] don't do this. + mm.StorageAccessor.SetBoxedValue(ref copy, value); + } + } + return copy; + } + + internal void StartTracking() + { + if(this.original == this.current) + { + this.original = this.CreateDataCopy(this.current); + } + } + + // Return value indicates whether or not any data was actually [....]'d + internal override bool SynchDependentData() + { + bool valueWasSet = false; + + // set foreign key fields + foreach(MetaAssociation assoc in this.Type.Associations) + { + MetaDataMember mm = assoc.ThisMember; + if(assoc.IsForeignKey) + { + bool hasAssigned = mm.StorageAccessor.HasAssignedValue(this.current); + bool hasLoaded = mm.StorageAccessor.HasLoadedValue(this.current); + if(hasAssigned || hasLoaded) + { + object parent = mm.StorageAccessor.GetBoxedValue(this.current); + if(parent != null) + { + // copy parent's current primary key into this instance's foreign key fields + for(int i = 0, n = assoc.ThisKey.Count; i < n; i++) + { + MetaDataMember accThis = assoc.ThisKey[i]; + MetaDataMember accParent = assoc.OtherKey[i]; + object parentValue = accParent.StorageAccessor.GetBoxedValue(parent); + accThis.StorageAccessor.SetBoxedValue(ref this.current, parentValue); + valueWasSet = true; + } + } + else if(assoc.IsNullable) + { + if(mm.IsDeferred || (this.original != null && mm.MemberAccessor.GetBoxedValue(this.original) != null)) + { + // no known parent? set to null + for(int i = 0, n = assoc.ThisKey.Count; i < n; i++) + { + MetaDataMember accThis = assoc.ThisKey[i]; + if(accThis.CanBeNull) + { + if(this.original != null && this.HasChangedValue(accThis)) + { + if(accThis.StorageAccessor.GetBoxedValue(this.current) != null) + { + throw Error.InconsistentAssociationAndKeyChange(accThis.Member.Name, mm.Member.Name); + } + } + else + { + accThis.StorageAccessor.SetBoxedValue(ref this.current, null); + valueWasSet = true; + } + } + } + } + } + else if(!hasLoaded) + { + //Else the parent association has been set to null; but the ID is not nullable so + //the value can not be set + StringBuilder keys = new StringBuilder(); + foreach(MetaDataMember key in assoc.ThisKey) + { + if(keys.Length > 0) + { + keys.Append(", "); + } + keys.AppendFormat("{0}.{1}", this.Type.Name.ToString(), key.Name); + } + throw Error.CouldNotRemoveRelationshipBecauseOneSideCannotBeNull(assoc.OtherType.Name, this.Type.Name, keys); + } + } + } + } + + /// Explicitly set any inheritance discriminator for item. + if(this.type.HasInheritance) + { + if(this.original != null) + { + object currentDiscriminator = type.Discriminator.MemberAccessor.GetBoxedValue(this.current); + MetaType currentTypeFromDiscriminator = TypeFromDiscriminator(this.type, currentDiscriminator); + object dbDiscriminator = type.Discriminator.MemberAccessor.GetBoxedValue(this.original); + MetaType dbTypeFromDiscriminator = TypeFromDiscriminator(this.type, dbDiscriminator); + + // Would the discriminator change also change the type? If so, its not allowed. + if(currentTypeFromDiscriminator != dbTypeFromDiscriminator) + { + throw Error.CannotChangeInheritanceType(dbDiscriminator, + currentDiscriminator, original.GetType().Name, currentTypeFromDiscriminator); + } + } + else + { + // No db value means this is an 'Add'. Set the discriminator. + MetaType currentType = type.GetInheritanceType(this.current.GetType()); + if(currentType.HasInheritanceCode) + { + object code = currentType.InheritanceCode; + this.type.Discriminator.MemberAccessor.SetBoxedValue(ref current, code); + valueWasSet = true; + } + } + } + return valueWasSet; + } + + internal override bool HasChangedValue(MetaDataMember mm) + { + if(this.current == this.original) + { + return false; + } + if(mm.IsAssociation && mm.Association.IsMany) + { + return mm.StorageAccessor.HasAssignedValue(this.original); + } + if(mm.StorageAccessor.HasValue(this.current)) + { + if(this.original != null && mm.StorageAccessor.HasValue(this.original)) + { + // If the member has ever been in a modified state + // in the past, it is considered modified + if(dirtyMemberCache.Get(mm.Ordinal)) + { + return true; + } + object baseline = mm.MemberAccessor.GetBoxedValue(this.original); + object currentValue = mm.MemberAccessor.GetBoxedValue(this.current); + if(!object.Equals(currentValue, baseline)) + { + return true; + } + return false; + } + else if(mm.IsDeferred && mm.StorageAccessor.HasAssignedValue(this.current)) + { + return true; + } + } + return false; + } + + internal override bool HasChangedValues() + { + if(this.current == this.original) + { + return false; + } + if(this.IsNew) + { + return true; + } + foreach(MetaDataMember mm in this.type.PersistentDataMembers) + { + if(!mm.IsAssociation && this.HasChangedValue(mm)) + { + return true; + } + } + return false; + } + + internal override IEnumerable GetModifiedMembers() + { + foreach(MetaDataMember mm in this.type.PersistentDataMembers) + { + if(this.IsModifiedMember(mm)) + { + object currentValue = mm.MemberAccessor.GetBoxedValue(this.current); + if(this.original != null && mm.StorageAccessor.HasValue(this.original)) + { + object originalValue = mm.MemberAccessor.GetBoxedValue(this.original); + yield return new ModifiedMemberInfo(mm.Member, currentValue, originalValue); + } + else if(this.original == null || (mm.IsDeferred && !mm.StorageAccessor.HasLoadedValue(this.current))) + { + yield return new ModifiedMemberInfo(mm.Member, currentValue, null); + } + } + } + } + + private bool IsModifiedMember(MetaDataMember member) + { + return !member.IsAssociation && + !member.IsPrimaryKey && + !member.IsVersion && + !member.IsDbGenerated && + member.StorageAccessor.HasAssignedValue(this.current) && + (this.state == State.Modified || + (this.state == State.PossiblyModified && this.HasChangedValue(member))); + } + + internal override bool HasDeferredLoaders + { + get + { + foreach(MetaAssociation assoc in this.Type.Associations) + { + if(HasDeferredLoader(assoc.ThisMember)) + { + return true; + } + } + IEnumerable deferredMembers = this.Type.PersistentDataMembers.Where(p => p.IsDeferred && !p.IsAssociation); + foreach(MetaDataMember deferredMember in deferredMembers) + { + if(HasDeferredLoader(deferredMember)) + { + return true; + } + } + return false; + } + } + + private bool HasDeferredLoader(MetaDataMember deferredMember) + { + if(!deferredMember.IsDeferred) + { + return false; + } + + MetaAccessor acc = deferredMember.StorageAccessor; + if(acc.HasAssignedValue(this.current) || acc.HasLoadedValue(this.current)) + { + return false; + } + MetaAccessor dsacc = deferredMember.DeferredSourceAccessor; + IEnumerable loader = (IEnumerable)dsacc.GetBoxedValue(this.current); + + return loader != null; + } + + /// + /// Called to initialize deferred loaders for New or Attached entities. + /// + internal override void InitializeDeferredLoaders() + { + if(this.tracker.services.Context.DeferredLoadingEnabled) + { + foreach(MetaAssociation assoc in this.Type.Associations) + { + // don't set loader on association that is dependent on unrealized generated values + if(!this.IsPendingGeneration(assoc.ThisKey)) + { + InitializeDeferredLoader(assoc.ThisMember); + } + } + IEnumerable deferredMembers = this.Type.PersistentDataMembers.Where(p => p.IsDeferred && !p.IsAssociation); + foreach(MetaDataMember deferredMember in deferredMembers) + { + // don't set loader on member that is dependent on unrealized generated values + if(!this.IsPendingGeneration(Type.IdentityMembers)) + { + InitializeDeferredLoader(deferredMember); + } + } + haveInitializedDeferredLoaders = true; + } + } + + private void InitializeDeferredLoader(MetaDataMember deferredMember) + { + MetaAccessor acc = deferredMember.StorageAccessor; + if(!acc.HasAssignedValue(this.current) && !acc.HasLoadedValue(this.current)) + { + MetaAccessor dsacc = deferredMember.DeferredSourceAccessor; + IEnumerable loader = (IEnumerable)dsacc.GetBoxedValue(this.current); + // don't reset loader on any deferred member that already has one + if(loader == null) + { + IDeferredSourceFactory factory = this.tracker.services.GetDeferredSourceFactory(deferredMember); + loader = factory.CreateDeferredSource(this.current); + dsacc.SetBoxedValue(ref this.current, loader); + + } + else if(loader != null && !haveInitializedDeferredLoaders) + { + // If loader is present but wasn't generated by us, then + // an attempt to Attach or Add an entity from another context + // has been made, which is not supported. + throw Error.CannotAttachAddNonNewEntities(); + } + } + } + + internal override bool IsPendingGeneration(IEnumerable key) + { + if(this.IsNew) + { + foreach(MetaDataMember member in key) + { + if(IsMemberPendingGeneration(member)) + { + return true; + } + } + } + return false; + } + + internal override bool IsMemberPendingGeneration(MetaDataMember keyMember) + { + if(this.IsNew && keyMember.IsDbGenerated) + { + return true; + } + // look for any FK association that has this key member (should only be one) + foreach(MetaAssociation assoc in type.Associations) + { + if(assoc.IsForeignKey) + { + int index = assoc.ThisKey.IndexOf(keyMember); + if(index > -1) + { + // we must have a reference to this other object to know if its side of + // the association is generated or not + object otherItem = null; + if(assoc.ThisMember.IsDeferred) + { + otherItem = assoc.ThisMember.DeferredValueAccessor.GetBoxedValue(this.current); + } + else + { + otherItem = assoc.ThisMember.StorageAccessor.GetBoxedValue(this.current); + } + if(otherItem != null) + { + if(assoc.IsMany) + { + // Can't be pending generation for a value that would have to be the same + // across many rows. + continue; + } + else + { + StandardTrackedObject trackedOther = (StandardTrackedObject)this.tracker.GetTrackedObject(otherItem); + if(trackedOther != null) + { + MetaDataMember otherMember = assoc.OtherKey[index]; + return trackedOther.IsMemberPendingGeneration(otherMember); + } + } + } + } + } + } + return false; + } + } + } + } +} \ No newline at end of file diff --git a/src/ChangeManagement/DataManipulation.cs b/src/ChangeManagement/DataManipulation.cs new file mode 100644 index 0000000..6208116 --- /dev/null +++ b/src/ChangeManagement/DataManipulation.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.Data.Linq; +using System.Data.Common; +using System.Linq.Expressions; +using System.IO; +using System.Linq; +using System.Text; +using System.Transactions; +using System.Reflection; +using System.Diagnostics.CodeAnalysis; + +namespace System.Data.Linq +{ + /// + /// Class with placeholder methods which are used to encode DML statements using linq and thus are only used in Expression trees with MethodCall expressions. All + /// methods are throwing NotImplementedExceptions as they're not supposed to be called directly. Used in the StandardChangeDirector class to create MethodCall expressions easily. + /// + internal static class DMLMethodPlaceholders + { + /// + /// The method signature used to encode an Insert command. + /// The method will throw a NotImplementedException if called directly. + /// + /// + /// + /// + /// + /// + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "item", Justification = "[....]: The method is being used to represent a method signature")] + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "resultSelector", Justification = "[....]: The method is being used to represent a method signature")] + public static TResult Insert(TEntity item, Func resultSelector) + { + throw new NotImplementedException(); + } + /// + /// The method signature used to encode an Insert command. + /// The method will throw a NotImplementedException if called directly. + /// + /// + /// + /// + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "item", Justification = "[....]: The method is being used to represent a method signature")] + public static int Insert(TEntity item) + { + throw new NotImplementedException(); + } + /// + /// The method signature used to encode an Update command. + /// The method will throw a NotImplementedException if called directly. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "item", Justification = "[....]: The method is being used to represent a method signature")] + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "check", Justification = "[....]: The method is being used to represent a method signature")] + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "resultSelector", Justification = "[....]: The method is being used to represent a method signature")] + public static TResult Update(TEntity item, Func check, Func resultSelector) + { + throw new NotImplementedException(); + } + /// + /// The method signature used to encode an Update command. + /// The method will throw a NotImplementedException if called directly. + /// + /// + /// + /// + /// + /// + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "item", Justification = "[....]: The method is being used to represent a method signature")] + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "resultSelector", Justification = "[....]: The method is being used to represent a method signature")] + public static TResult Update(TEntity item, Func resultSelector) + { + throw new NotImplementedException(); + } + /// + /// The method signature used to encode an Update command. + /// The method will throw a NotImplementedException if called directly. + /// + /// + /// + /// + /// + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "item", Justification = "[....]: The method is being used to represent a method signature")] + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "check", Justification = "[....]: The method is being used to represent a method signature")] + public static int Update(TEntity item, Func check) + { + throw new NotImplementedException(); + } + /// + /// The method signature used to encode an Update command. + /// The method will throw a NotImplementedException if called directly. + /// + /// + /// + /// + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "item", Justification = "[....]: The method is being used to represent a method signature")] + public static int Update(TEntity item) + { + throw new NotImplementedException(); + } + /// + /// The method signature used to encode a Delete command. + /// The method will throw a NotImplementedException if called directly. + /// + /// + /// + /// + /// + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "item", Justification = "[....]: The method is being used to represent a method signature")] + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "check", Justification = "[....]: The method is being used to represent a method signature")] + public static int Delete(TEntity item, Func check) + { + throw new NotImplementedException(); + } + /// + /// The method signature used to encode a Delete command. + /// The method will throw a NotImplementedException if called directly. + /// + /// + /// + /// + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "item", Justification = "[....]: The method is being used to represent a method signature")] + public static int Delete(TEntity item) + { + throw new NotImplementedException(); + } + } +} + diff --git a/src/ChangeManagement/MemberChangeConflict.cs b/src/ChangeManagement/MemberChangeConflict.cs new file mode 100644 index 0000000..e1d6c93 --- /dev/null +++ b/src/ChangeManagement/MemberChangeConflict.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; +using System.Reflection; +using System.Linq; +using System.Diagnostics; + +namespace System.Data.Linq { + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + using System.Diagnostics.CodeAnalysis; + + /// + /// Represents a single optimistic concurrency member conflict. + /// + public sealed class MemberChangeConflict { + private ObjectChangeConflict conflict; + private MetaDataMember metaMember; + private object originalValue; + private object databaseValue; + private object currentValue; + bool isResolved; + + internal MemberChangeConflict(ObjectChangeConflict conflict, MetaDataMember metaMember) { + this.conflict = conflict; + this.metaMember = metaMember; + this.originalValue = metaMember.StorageAccessor.GetBoxedValue(conflict.Original); + this.databaseValue = metaMember.StorageAccessor.GetBoxedValue(conflict.Database); + this.currentValue = metaMember.StorageAccessor.GetBoxedValue(conflict.TrackedObject.Current); + } + + /// + /// The previous client value. + /// + public object OriginalValue { + get { return this.originalValue; } + } + + /// + /// The current database value. + /// + public object DatabaseValue { + get { return this.databaseValue; } + } + + /// + /// The current client value. + /// + public object CurrentValue { + get { return this.currentValue; } + } + + /// + /// MemberInfo for the member in conflict. + /// + public MemberInfo Member { + get { return this.metaMember.Member; } + } + + /// + /// Updates the current value to the specified value. + /// + public void Resolve(object value) { + this.conflict.TrackedObject.RefreshMember(this.metaMember, RefreshMode.OverwriteCurrentValues, value); + this.isResolved = true; + this.conflict.OnMemberResolved(); + } + + /// + /// Updates the current value using the specified strategy. + /// + public void Resolve(RefreshMode refreshMode) { + this.conflict.TrackedObject.RefreshMember(this.metaMember, refreshMode, this.databaseValue); + this.isResolved = true; + this.conflict.OnMemberResolved(); + } + + /// + /// True if the value was modified by the client. + /// + public bool IsModified { + get { return this.conflict.TrackedObject.HasChangedValue(this.metaMember); } + } + + /// + /// True if the member conflict has been resolved. + /// + public bool IsResolved { + get { return this.isResolved; } + } + } +} diff --git a/src/ChangeManagement/ModifiedMemberInfo.cs b/src/ChangeManagement/ModifiedMemberInfo.cs new file mode 100644 index 0000000..5f24337 --- /dev/null +++ b/src/ChangeManagement/ModifiedMemberInfo.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Configuration; +using System.Data; +using System.Data.Common; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Transactions; +using System.Xml; +using System.Runtime.CompilerServices; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + using System.Diagnostics.CodeAnalysis; + using System.Data.Linq.BindingLists; + + [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "[....]: Types are never compared to each other. When comparisons happen it is against the entities that are represented by these constructs.")] + public struct ModifiedMemberInfo + { + MemberInfo member; + object current; + object original; + + internal ModifiedMemberInfo(MemberInfo member, object current, object original) + { + this.member = member; + this.current = current; + this.original = original; + } + + public MemberInfo Member + { + get { return this.member; } + } + + public object CurrentValue + { + get { return this.current; } + } + + public object OriginalValue + { + get { return this.original; } + } + } +} + diff --git a/src/ChangeManagement/ObjectChangeConflict.cs b/src/ChangeManagement/ObjectChangeConflict.cs new file mode 100644 index 0000000..8d263f6 --- /dev/null +++ b/src/ChangeManagement/ObjectChangeConflict.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; +using System.Reflection; +using System.Linq; +using System.Diagnostics; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + using System.Diagnostics.CodeAnalysis; + + /// + /// Represents an update with one or more optimistic concurrency conflicts. + /// + public sealed class ObjectChangeConflict + { + private ChangeConflictSession session; + private TrackedObject trackedObject; + private bool isResolved; + private ReadOnlyCollection memberConflicts; + private object database; + private object original; + private bool? isDeleted; + + /// + /// Constructor. + /// + /// The session in which the conflicts occurred. + /// The tracked item in conflict. + internal ObjectChangeConflict(ChangeConflictSession session, TrackedObject trackedObject) + { + this.session = session; + this.trackedObject = trackedObject; + this.original = trackedObject.CreateDataCopy(trackedObject.Original); + } + + /// + /// Constructor. + /// + /// The session in which the conflicts occurred. + /// The tracked item in conflict. + /// True if the item in conflict no longer exists in the database. + internal ObjectChangeConflict(ChangeConflictSession session, TrackedObject trackedObject, bool isDeleted) + : this(session, trackedObject) + { + this.isDeleted = isDeleted; + } + + internal ChangeConflictSession Session + { + get { return this.session; } + } + + internal TrackedObject TrackedObject + { + get { return this.trackedObject; } + } + + /// + /// The object in conflict. + /// + public object Object + { + get { return this.trackedObject.Current; } + } + + /// + /// An instance containing the baseline original values used to perform the concurrency check. + /// + internal object Original + { + get { return this.original; } + } + + /// + /// True if the conflicts for this object have already been resovled. + /// + public bool IsResolved + { + get { return this.isResolved; } + } + + /// + /// True if the object in conflict has been deleted from the database. + /// + public bool IsDeleted + { + get + { + if(this.isDeleted.HasValue) + { + return this.isDeleted.Value; + } + return (this.Database == null); + } + } + + /// + /// An instance containing the most recent values from the database + /// + internal object Database + { + get + { + if(this.database == null) + { + // use the 'refresh' context to retrieve the current database state + DataContext ctxt = this.session.RefreshContext; + object[] keyValues = CommonDataServices.GetKeyValues(this.trackedObject.Type, this.original); + this.database = ctxt.Services.GetObjectByKey(this.trackedObject.Type, keyValues); + } + return this.database; + } + } + + /// + /// Resolve member conflicts keeping current values and resetting the baseline 'Original' values + /// to match the more recent 'Database' values. + /// + public void Resolve() + { + this.Resolve(RefreshMode.KeepCurrentValues, true); + } + + /// + /// Resolve member conflicts using the mode specified and resetting the baseline 'Original' values + /// to match the more recent 'Database' values. + /// + /// The mode that determines how the current values are + /// changed in order to resolve the conflict + public void Resolve(RefreshMode refreshMode) + { + this.Resolve(refreshMode, false); + } + + /// + /// Resolve member conflicts using the mode specified and resetting the baseline 'Original' values + /// to match the more recent 'Database' values. + /// + /// The mode that determines how the current values are + /// changed in order to resolve the conflict + /// If true conflicts resulting from the modified + /// object no longer existing in the database will be automatically resolved. + public void Resolve(RefreshMode refreshMode, bool autoResolveDeletes) + { + if(autoResolveDeletes && this.IsDeleted) + { + this.ResolveDelete(); + } + else + { + // We make these calls explicity rather than simply calling + // DataContext.Refresh (which does virtually the same thing) + // since we want to cache the database value read. + if(this.Database == null) + { + throw Error.RefreshOfDeletedObject(); + } + trackedObject.Refresh(refreshMode, this.Database); + this.isResolved = true; + } + } + + /// + /// Resolve a conflict where we have updated an entity that no longer exists + /// in the database. + /// + private void ResolveDelete() + { + Debug.Assert(this.IsDeleted); + // If the user is attempting to update an entity that no longer exists + // in the database, we first need to [....] the delete into the local cache. + if(!trackedObject.IsDeleted) + { + trackedObject.ConvertToDeleted(); + } + + // As the object have been deleted, it needs to leave the cache + this.Session.Context.Services.RemoveCachedObjectLike(trackedObject.Type, trackedObject.Original); + + // Now that our cache is in [....], we accept the changes + this.trackedObject.AcceptChanges(); + this.isResolved = true; + } + + /// + /// Returns a collection of all member conflicts that caused the update to fail. + /// + public ReadOnlyCollection MemberConflicts + { + get + { + if(this.memberConflicts == null) + { + var list = new List(); + if(this.Database != null) + { + // determine which members are in conflict + foreach(MetaDataMember metaMember in trackedObject.Type.PersistentDataMembers) + { + if(!metaMember.IsAssociation && this.HasMemberConflict(metaMember)) + { + list.Add(new MemberChangeConflict(this, metaMember)); + } + } + } + this.memberConflicts = list.AsReadOnly(); + } + return this.memberConflicts; + } + } + + private bool HasMemberConflict(MetaDataMember member) + { + object oValue = member.StorageAccessor.GetBoxedValue(this.original); + if(!member.DeclaringType.Type.IsAssignableFrom(this.database.GetType())) + { + return false; + } + object dValue = member.StorageAccessor.GetBoxedValue(this.database); + return !this.AreEqual(member, oValue, dValue); + } + + private bool AreEqual(MetaDataMember member, object v1, object v2) + { + if(v1 == null && v2 == null) + return true; + if(v1 == null || v2 == null) + return false; + if(member.Type == typeof(char[])) + { + return this.AreEqual((char[])v1, (char[])v2); + } + else if(member.Type == typeof(byte[])) + { + return this.AreEqual((byte[])v1, (byte[])v2); + } + else + { + return object.Equals(v1, v2); + } + } + + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Unknown reason.")] + private bool AreEqual(char[] a1, char[] a2) + { + if(a1.Length != a2.Length) + return false; + for(int i = 0, n = a1.Length; i < n; i++) + { + if(a1[i] != a2[i]) + return false; + } + return true; + } + + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Unknown reason.")] + private bool AreEqual(byte[] a1, byte[] a2) + { + if(a1.Length != a2.Length) + return false; + for(int i = 0, n = a1.Length; i < n; i++) + { + if(a1[i] != a2[i]) + return false; + } + return true; + } + + internal void OnMemberResolved() + { + if(!this.IsResolved) + { + int nResolved = this.memberConflicts.AsEnumerable().Count(m => m.IsResolved); + if(nResolved == this.memberConflicts.Count) + { + this.Resolve(RefreshMode.KeepCurrentValues, false); + } + } + } + } +} + diff --git a/src/ChangeManagement/RelatedItem.cs b/src/ChangeManagement/RelatedItem.cs new file mode 100644 index 0000000..f5fa875 --- /dev/null +++ b/src/ChangeManagement/RelatedItem.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + + internal struct RelatedItem + { + internal MetaType Type; + internal object Item; + internal RelatedItem(MetaType type, object item) + { + this.Type = type; + this.Item = item; + } + } +} + diff --git a/src/ChangeManagement/StandardChangeDirector.cs b/src/ChangeManagement/StandardChangeDirector.cs new file mode 100644 index 0000000..93856c9 --- /dev/null +++ b/src/ChangeManagement/StandardChangeDirector.cs @@ -0,0 +1,482 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq.Expressions; +using System.Text; +using System.Reflection; +using System.Linq; +using System.Security.Permissions; +using System.Security; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + using System.Diagnostics.CodeAnalysis; + + /// + /// Implementation of ChangeDirector which calls user code if possible + /// and othewise falls back to creating SQL for 'INSERT', 'UPDATE' and 'DELETE'. + /// + internal class StandardChangeDirector : ChangeDirector + { + #region Enums + private enum UpdateType { Insert, Update, Delete }; + private enum AutoSyncBehavior { ApplyNewAutoSync, RollbackSavedValues } + #endregion + + DataContext context; + [SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification = "[....]: FxCop bug Dev10:423110 -- List> is not supposed to be flagged as a violation.")] + List> syncRollbackItems; + + internal StandardChangeDirector(DataContext context) + { + this.context = context; + } + + [SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification = "[....]: FxCop bug Dev10:423110 -- List> is not supposed to be flagged as a violation.")] + private List> SyncRollbackItems + { + get + { + if(syncRollbackItems == null) + { + syncRollbackItems = new List>(); + } + return syncRollbackItems; + } + } + + internal override int Insert(TrackedObject item) + { + if(item.Type.Table.InsertMethod != null) + { + try + { + item.Type.Table.InsertMethod.Invoke(this.context, new object[] { item.Current }); + } + catch(TargetInvocationException tie) + { + if(tie.InnerException != null) + { + throw tie.InnerException; + } + throw; + } + return 1; + } + else + { + return DynamicInsert(item); + } + } + + internal override int DynamicInsert(TrackedObject item) + { + Expression cmd = this.GetInsertCommand(item); + if(cmd.Type == typeof(int)) + { + return (int)this.context.Provider.Execute(cmd).ReturnValue; + } + else + { + IEnumerable facts = (IEnumerable)this.context.Provider.Execute(cmd).ReturnValue; + object[] syncResults = (object[])facts.FirstOrDefault(); + if(syncResults != null) + { + // [....] any auto gen or computed members + AutoSyncMembers(syncResults, item, UpdateType.Insert, AutoSyncBehavior.ApplyNewAutoSync); + return 1; + } + else + { + throw Error.InsertAutoSyncFailure(); + } + } + } + + internal override void AppendInsertText(TrackedObject item, StringBuilder appendTo) + { + if(item.Type.Table.InsertMethod != null) + { + appendTo.Append(Strings.InsertCallbackComment); + } + else + { + Expression cmd = this.GetInsertCommand(item); + appendTo.Append(this.context.Provider.GetQueryText(cmd)); + appendTo.AppendLine(); + } + } + + /// + /// Update the item, returning 0 if the update fails, 1 if it succeeds. + /// + internal override int Update(TrackedObject item) + { + if(item.Type.Table.UpdateMethod != null) + { + // create a copy - don't allow the override to modify our + // internal original values + try + { + item.Type.Table.UpdateMethod.Invoke(this.context, new object[] { item.Current }); + } + catch(TargetInvocationException tie) + { + if(tie.InnerException != null) + { + throw tie.InnerException; + } + throw; + } + return 1; + } + else + { + return DynamicUpdate(item); + } + } + + internal override int DynamicUpdate(TrackedObject item) + { + Expression cmd = this.GetUpdateCommand(item); + if(cmd.Type == typeof(int)) + { + return (int)this.context.Provider.Execute(cmd).ReturnValue; + } + else + { + IEnumerable facts = (IEnumerable)this.context.Provider.Execute(cmd).ReturnValue; + object[] syncResults = (object[])facts.FirstOrDefault(); + if(syncResults != null) + { + // [....] any auto gen or computed members + AutoSyncMembers(syncResults, item, UpdateType.Update, AutoSyncBehavior.ApplyNewAutoSync); + return 1; + } + else + { + return 0; + } + } + } + + internal override void AppendUpdateText(TrackedObject item, StringBuilder appendTo) + { + if(item.Type.Table.UpdateMethod != null) + { + appendTo.Append(Strings.UpdateCallbackComment); + } + else + { + Expression cmd = this.GetUpdateCommand(item); + appendTo.Append(this.context.Provider.GetQueryText(cmd)); + appendTo.AppendLine(); + } + } + + internal override int Delete(TrackedObject item) + { + if(item.Type.Table.DeleteMethod != null) + { + try + { + item.Type.Table.DeleteMethod.Invoke(this.context, new object[] { item.Current }); + } + catch(TargetInvocationException tie) + { + if(tie.InnerException != null) + { + throw tie.InnerException; + } + throw; + } + return 1; + } + else + { + return DynamicDelete(item); + } + } + + internal override int DynamicDelete(TrackedObject item) + { + Expression cmd = this.GetDeleteCommand(item); + int ret = (int)this.context.Provider.Execute(cmd).ReturnValue; + if(ret == 0) + { + // we don't yet know if the delete failed because the check constaint did not match + // or item was already deleted. Verify the item exists + cmd = this.GetDeleteVerificationCommand(item); + ret = ((int?)this.context.Provider.Execute(cmd).ReturnValue) ?? -1; + } + return ret; + } + + internal override void AppendDeleteText(TrackedObject item, StringBuilder appendTo) + { + if(item.Type.Table.DeleteMethod != null) + { + appendTo.Append(Strings.DeleteCallbackComment); + } + else + { + Expression cmd = this.GetDeleteCommand(item); + appendTo.Append(this.context.Provider.GetQueryText(cmd)); + appendTo.AppendLine(); + } + } + + [SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification = "[....]: FxCop bug Dev10:423110 -- List> is not supposed to be flagged as a violation.")] + internal override void RollbackAutoSync() + { + // Rolls back any AutoSync values that may have been set already + // Those values are no longer valid since the transaction will be rolled back on the server + if(this.syncRollbackItems != null) + { + foreach(KeyValuePair rollbackItemPair in this.SyncRollbackItems) + { + TrackedObject rollbackItem = rollbackItemPair.Key; + object[] rollbackValues = rollbackItemPair.Value; + + AutoSyncMembers( + rollbackValues, + rollbackItem, + rollbackItem.IsNew ? UpdateType.Insert : UpdateType.Update, + AutoSyncBehavior.RollbackSavedValues); + } + } + } + + [SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification = "[....]: FxCop bug Dev10:423110 -- List> is not supposed to be flagged as a violation.")] + internal override void ClearAutoSyncRollback() + { + this.syncRollbackItems = null; + } + + private Expression GetInsertCommand(TrackedObject item) + { + MetaType mt = item.Type; + + // bind to InsertFacts if there are any members to syncronize + List membersToSync = GetAutoSyncMembers(mt, UpdateType.Insert); + ParameterExpression p = Expression.Parameter(item.Type.Table.RowType.Type, "p"); + if(membersToSync.Count > 0) + { + Expression autoSync = this.CreateAutoSync(membersToSync, p); + LambdaExpression resultSelector = Expression.Lambda(autoSync, p); + return Expression.Call(typeof(DMLMethodPlaceholders), "Insert", new Type[] { item.Type.InheritanceRoot.Type, resultSelector.Body.Type }, Expression.Constant(item.Current), resultSelector); + } + else + { + return Expression.Call(typeof(DMLMethodPlaceholders), "Insert", new Type[] { item.Type.InheritanceRoot.Type }, Expression.Constant(item.Current)); + } + } + + /// + /// For the meta members specified, create an array initializer for each and bind to + /// an output array. + /// + private Expression CreateAutoSync(List membersToSync, Expression source) + { + System.Diagnostics.Debug.Assert(membersToSync.Count > 0); + int i = 0; + Expression[] initializers = new Expression[membersToSync.Count]; + foreach(MetaDataMember mm in membersToSync) + { + initializers[i++] = Expression.Convert(this.GetMemberExpression(source, mm.Member), typeof(object)); + } + return Expression.NewArrayInit(typeof(object), initializers); + } + + private static List GetAutoSyncMembers(MetaType metaType, UpdateType updateType) + { + List membersToSync = new List(); + foreach(MetaDataMember metaMember in metaType.PersistentDataMembers.OrderBy(m => m.Ordinal)) + { + // add all auto generated members for the specified update type to the auto-[....] list + if((updateType == UpdateType.Insert && metaMember.AutoSync == AutoSync.OnInsert) || + (updateType == UpdateType.Update && metaMember.AutoSync == AutoSync.OnUpdate) || + metaMember.AutoSync == AutoSync.Always) + { + membersToSync.Add(metaMember); + } + } + return membersToSync; + } + + /// + /// Synchronize the specified item by copying in data from the specified results. + /// Used to [....] members after successful insert or update, but also used to rollback to previous values if a failure + /// occurs on other entities in the same SubmitChanges batch. + /// + /// + /// If AutoSyncBehavior.ApplyNewAutoSync, the current value of the property is saved before the [....] occurs. This is used for normal synchronization after a successful update/insert. + /// Otherwise, the current value is not saved. This is used for rollback operations when something in the SubmitChanges batch failed, rendering the previously-[....]'d values invalid. + /// + [SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification = "[....]: FxCop bug Dev10:423110 -- List> is not supposed to be flagged as a violation.")] + private void AutoSyncMembers(object[] syncResults, TrackedObject item, UpdateType updateType, AutoSyncBehavior autoSyncBehavior) + { + System.Diagnostics.Debug.Assert(item != null); + System.Diagnostics.Debug.Assert(item.IsNew || item.IsPossiblyModified, "AutoSyncMembers should only be called for new and modified objects."); + object[] syncRollbackValues = null; + if(syncResults != null) + { + int idx = 0; + List membersToSync = GetAutoSyncMembers(item.Type, updateType); + System.Diagnostics.Debug.Assert(syncResults.Length == membersToSync.Count); + if(autoSyncBehavior == AutoSyncBehavior.ApplyNewAutoSync) + { + syncRollbackValues = new object[syncResults.Length]; + } + foreach(MetaDataMember mm in membersToSync) + { + object value = syncResults[idx]; + object current = item.Current; + MetaAccessor accessor = + (mm.Member is PropertyInfo && ((PropertyInfo)mm.Member).CanWrite) + ? mm.MemberAccessor + : mm.StorageAccessor; + + if(syncRollbackValues != null) + { + syncRollbackValues[idx] = accessor.GetBoxedValue(current); + } + accessor.SetBoxedValue(ref current, DBConvert.ChangeType(value, mm.Type)); + idx++; + } + } + if(syncRollbackValues != null) + { + this.SyncRollbackItems.Add(new KeyValuePair(item, syncRollbackValues)); + } + } + + private Expression GetUpdateCommand(TrackedObject tracked) + { + object database = tracked.Original; + MetaType rowType = tracked.Type.GetInheritanceType(database.GetType()); + MetaType rowTypeRoot = rowType.InheritanceRoot; + + ParameterExpression p = Expression.Parameter(rowTypeRoot.Type, "p"); + Expression pv = p; + if(rowType != rowTypeRoot) + { + pv = Expression.Convert(p, rowType.Type); + } + + Expression check = this.GetUpdateCheck(pv, tracked); + if(check != null) + { + check = Expression.Lambda(check, p); + } + + // bind to out array if there are any members to synchronize + List membersToSync = GetAutoSyncMembers(rowType, UpdateType.Update); + if(membersToSync.Count > 0) + { + Expression autoSync = this.CreateAutoSync(membersToSync, pv); + LambdaExpression resultSelector = Expression.Lambda(autoSync, p); + if(check != null) + { + return Expression.Call(typeof(DMLMethodPlaceholders), "Update", new Type[] { rowTypeRoot.Type, resultSelector.Body.Type }, Expression.Constant(tracked.Current), check, resultSelector); + } + else + { + return Expression.Call(typeof(DMLMethodPlaceholders), "Update", new Type[] { rowTypeRoot.Type, resultSelector.Body.Type }, Expression.Constant(tracked.Current), resultSelector); + } + } + else if(check != null) + { + return Expression.Call(typeof(DMLMethodPlaceholders), "Update", new Type[] { rowTypeRoot.Type }, Expression.Constant(tracked.Current), check); + } + else + { + return Expression.Call(typeof(DMLMethodPlaceholders), "Update", new Type[] { rowTypeRoot.Type }, Expression.Constant(tracked.Current)); + } + } + + private Expression GetUpdateCheck(Expression serverItem, TrackedObject tracked) + { + MetaType mt = tracked.Type; + if(mt.VersionMember != null) + { + return Expression.Equal( + this.GetMemberExpression(serverItem, mt.VersionMember.Member), + this.GetMemberExpression(Expression.Constant(tracked.Current), mt.VersionMember.Member) + ); + } + else + { + Expression expr = null; + foreach(MetaDataMember mm in mt.PersistentDataMembers) + { + if(!mm.IsPrimaryKey) + { + UpdateCheck check = mm.UpdateCheck; + if(check == UpdateCheck.Always || + (check == UpdateCheck.WhenChanged && tracked.HasChangedValue(mm))) + { + object memberValue = mm.MemberAccessor.GetBoxedValue(tracked.Original); + Expression eq = + Expression.Equal( + this.GetMemberExpression(serverItem, mm.Member), + Expression.Constant(memberValue, mm.Type) + ); + expr = (expr != null) ? Expression.And(expr, eq) : eq; + } + } + } + return expr; + } + } + + private Expression GetDeleteCommand(TrackedObject tracked) + { + MetaType rowType = tracked.Type; + MetaType rowTypeRoot = rowType.InheritanceRoot; + ParameterExpression p = Expression.Parameter(rowTypeRoot.Type, "p"); + Expression pv = p; + if(rowType != rowTypeRoot) + { + pv = Expression.Convert(p, rowType.Type); + } + object original = tracked.CreateDataCopy(tracked.Original); + Expression check = this.GetUpdateCheck(pv, tracked); + if(check != null) + { + check = Expression.Lambda(check, p); + return Expression.Call(typeof(DMLMethodPlaceholders), "Delete", new Type[] { rowTypeRoot.Type }, Expression.Constant(original), check); + } + else + { + return Expression.Call(typeof(DMLMethodPlaceholders), "Delete", new Type[] { rowTypeRoot.Type }, Expression.Constant(original)); + } + } + + private Expression GetDeleteVerificationCommand(TrackedObject tracked) + { + ITable table = this.context.GetTable(tracked.Type.InheritanceRoot.Type); + System.Diagnostics.Debug.Assert(table != null); + ParameterExpression p = Expression.Parameter(table.ElementType, "p"); + Expression pred = Expression.Lambda(Expression.Equal(p, Expression.Constant(tracked.Current)), p); + Expression where = Expression.Call(typeof(Queryable), "Where", new Type[] { table.ElementType }, table.Expression, pred); + Expression selector = Expression.Lambda(Expression.Constant(0, typeof(int?)), p); + Expression select = Expression.Call(typeof(Queryable), "Select", new Type[] { table.ElementType, typeof(int?) }, where, selector); + Expression singleOrDefault = Expression.Call(typeof(Queryable), "SingleOrDefault", new Type[] { typeof(int?) }, select); + return singleOrDefault; + } + + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Unknown reason.")] + private Expression GetMemberExpression(Expression exp, MemberInfo mi) + { + FieldInfo fi = mi as FieldInfo; + if(fi != null) + return Expression.Field(exp, fi); + PropertyInfo pi = (PropertyInfo)mi; + return Expression.Property(exp, pi); + } + } +} + diff --git a/src/ChangeManagement/TrackedObject.cs b/src/ChangeManagement/TrackedObject.cs new file mode 100644 index 0000000..2a16d07 --- /dev/null +++ b/src/ChangeManagement/TrackedObject.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + + internal abstract class TrackedObject + { + internal abstract MetaType Type { get; } + /// + /// The current client value. + /// + internal abstract object Current { get; } + /// + /// The last read database value. This is updated whenever the + /// item is refreshed. + /// + internal abstract object Original { get; } + internal abstract bool IsInteresting { get; } // new, deleted or possibly changed + internal abstract bool IsNew { get; } + internal abstract bool IsDeleted { get; } + internal abstract bool IsModified { get; } + internal abstract bool IsUnmodified { get; } + internal abstract bool IsPossiblyModified { get; } + internal abstract bool IsRemoved { get; } + internal abstract bool IsDead { get; } + /// + /// True if the object is being tracked (perhaps during a recursive + /// attach operation) but can be transitioned to other states. + /// + internal abstract bool IsWeaklyTracked { get; } + internal abstract bool HasDeferredLoaders { get; } + internal abstract bool HasChangedValues(); + internal abstract IEnumerable GetModifiedMembers(); + internal abstract bool HasChangedValue(MetaDataMember mm); + internal abstract bool CanInferDelete(); + internal abstract void AcceptChanges(); + internal abstract void ConvertToNew(); + internal abstract void ConvertToPossiblyModified(); + internal abstract void ConvertToPossiblyModified(object original); + internal abstract void ConvertToUnmodified(); + internal abstract void ConvertToModified(); + internal abstract void ConvertToDeleted(); + internal abstract void ConvertToRemoved(); + internal abstract void ConvertToDead(); + /// + /// Refresh the item by making the value passed in the current + /// Database value, and refreshing the current values using the + /// mode specified. + /// + internal abstract void Refresh(RefreshMode mode, object freshInstance); + /// + /// Does the refresh operation for a single member. This method does not + /// update the baseline 'original' value. You must call + /// Refresh(RefreshMode.KeepCurrentValues, freshInstance) to finish the refresh + /// after refreshing individual members. + /// + /// + /// + /// + internal abstract void RefreshMember(MetaDataMember member, RefreshMode mode, object freshValue); + /// + /// Create a data-member only copy of the instance (no associations) + /// + /// + internal abstract object CreateDataCopy(object instance); + + internal abstract bool SynchDependentData(); + + internal abstract bool IsPendingGeneration(IEnumerable keyMembers); + internal abstract bool IsMemberPendingGeneration(MetaDataMember keyMember); + + internal abstract void InitializeDeferredLoaders(); + } +} + diff --git a/src/ChangeProcessor.cs b/src/ChangeProcessor.cs deleted file mode 100644 index 4eaa79b..0000000 --- a/src/ChangeProcessor.cs +++ /dev/null @@ -1,765 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Diagnostics; - -namespace System.Data.Linq { - using System.Data.Linq.Mapping; - using System.Data.Linq.Provider; - - /// - /// Describes the type of change the entity will undergo when submitted to the database. - /// - public enum ChangeAction { - /// - /// The entity will not be submitted. - /// - None = 0, - /// - /// The entity will be deleted. - /// - Delete, - /// - /// The entity will be inserted. - /// - Insert, - /// - /// The entity will be updated. - /// - Update - } - - internal class ChangeProcessor { - CommonDataServices services; - DataContext context; - ChangeTracker tracker; - ChangeDirector changeDirector; - EdgeMap currentParentEdges; - EdgeMap originalChildEdges; - ReferenceMap originalChildReferences; - - internal ChangeProcessor(CommonDataServices services, DataContext context) { - this.services = services; - this.context = context; - this.tracker = services.ChangeTracker; - this.changeDirector = services.ChangeDirector; - this.currentParentEdges = new EdgeMap(); - this.originalChildEdges = new EdgeMap(); - this.originalChildReferences = new ReferenceMap(); - } - - internal void SubmitChanges(ConflictMode failureMode) { - this.TrackUntrackedObjects(); - // Must apply inferred deletions only after any untracked objects - // are tracked - this.ApplyInferredDeletions(); - this.BuildEdgeMaps(); - - var list = this.GetOrderedList(); - - ValidateAll(list); - - int numUpdatesAttempted = 0; - ChangeConflictSession conflictSession = new ChangeConflictSession(this.context); - List conflicts = new List(); - List deletedItems = new List(); - List insertedItems = new List(); - List syncDependentItems = new List(); - - foreach (TrackedObject item in list) { - try { - if (item.IsNew) { - if (item.SynchDependentData()) { - syncDependentItems.Add(item); - } - changeDirector.Insert(item); - // store all inserted items for post processing - insertedItems.Add(item); - } - else if (item.IsDeleted) { - // Delete returns 1 if the delete was successfull, 0 if the row exists - // but wasn't deleted due to an OC conflict, or -1 if the row was - // deleted by another context (no OC conflict in this case) - numUpdatesAttempted++; - int ret = changeDirector.Delete(item); - if (ret == 0) { - conflicts.Add(new ObjectChangeConflict(conflictSession, item, false)); - } - else { - // store all deleted items for post processing - deletedItems.Add(item); - } - } - else if (item.IsPossiblyModified) { - if (item.SynchDependentData()) { - syncDependentItems.Add(item); - } - if (item.IsModified) { - CheckForInvalidChanges(item); - numUpdatesAttempted++; - if (changeDirector.Update(item) <= 0) { - conflicts.Add(new ObjectChangeConflict(conflictSession, item)); - } - } - } - } - catch (ChangeConflictException) { - conflicts.Add(new ObjectChangeConflict(conflictSession, item)); - } - if (conflicts.Count > 0 && failureMode == ConflictMode.FailOnFirstConflict) { - break; - } - } - - // if we have accumulated any failed updates, throw the exception now - if (conflicts.Count > 0) { - // First we need to rollback any value that have already been auto-[....]'d, since the values are no longer valid on the server - changeDirector.RollbackAutoSync(); - // Also rollback any dependent items that were [....]'d, since their parent values may have been rolled back - foreach (TrackedObject syncDependentItem in syncDependentItems) { - Debug.Assert(syncDependentItem.IsNew || syncDependentItem.IsPossiblyModified, "SynchDependent data should only be rolled back for new and modified objects."); - syncDependentItem.SynchDependentData(); - } - this.context.ChangeConflicts.Fill(conflicts); - throw CreateChangeConflictException(numUpdatesAttempted, conflicts.Count); - } - else { - // No conflicts occurred, so we don't need to save the rollback values anymore - changeDirector.ClearAutoSyncRollback(); - } - - // Only after all updates have been sucessfully processed do we want to make - // post processing modifications to the objects and/or cache state. - PostProcessUpdates(insertedItems, deletedItems); - } - - private void PostProcessUpdates(List insertedItems, List deletedItems) { - // perform post delete processing - foreach (TrackedObject deletedItem in deletedItems) { - // remove deleted item from identity cache - this.services.RemoveCachedObjectLike(deletedItem.Type, deletedItem.Original); - ClearForeignKeyReferences(deletedItem); - } - - // perform post insert processing - foreach (TrackedObject insertedItem in insertedItems) { - object lookup = this.services.InsertLookupCachedObject(insertedItem.Type, insertedItem.Current); - if (lookup != insertedItem.Current) { - throw new DuplicateKeyException(insertedItem.Current, Strings.DatabaseGeneratedAlreadyExistingKey); - } - insertedItem.InitializeDeferredLoaders(); - } - } - - /// - /// Clears out the foreign key values and parent object references for deleted objects on the child side of a relationship. - /// For bi-directional relationships, also performs the following fixup: - /// - for 1:N we remove the deleted entity from the opposite EntitySet or collection - /// - for 1:1 we null out the back reference - /// - private void ClearForeignKeyReferences(TrackedObject to) { - Debug.Assert(to.IsDeleted, "Foreign key reference cleanup should only happen on Deleted objects."); - foreach (MetaAssociation assoc in to.Type.Associations) { - if (assoc.IsForeignKey) { - // If there is a member on the other side referring back to us (i.e. this is a bi-directional relationship), - // we want to do a cache lookup to find the other side, then will remove ourselves from that collection. - // This cache lookup is only possible if the other key is the primary key, since that is the only way items can be found in the cache. - if (assoc.OtherMember != null && assoc.OtherKeyIsPrimaryKey) { - Debug.Assert(assoc.OtherMember.IsAssociation, "OtherMember of the association is expected to also be an association."); - // Search the cache for the target of the association, since - // it might not be loaded on the object being deleted, and we - // don't want to force a load. - object[] keyValues = CommonDataServices.GetForeignKeyValues(assoc, to.Current); - object cached = this.services.IdentityManager.Find(assoc.OtherType, keyValues); - - if (cached != null) { - if (assoc.OtherMember.Association.IsMany) { - // Note that going through the IList interface handles - // EntitySet as well as POCO collections that implement IList - // and are not FixedSize. - System.Collections.IList collection = assoc.OtherMember.MemberAccessor.GetBoxedValue(cached) as System.Collections.IList; - if (collection != null && !collection.IsFixedSize) { - collection.Remove(to.Current); - // Explicitly clear the foreign key values and parent object reference - ClearForeignKeysHelper(assoc, to.Current); - } - } - else { - // Null out the other association. Since this is a 1:1 association, - // we're not concerned here with causing a deferred load, since the - // target is already cached (since we're deleting it). - assoc.OtherMember.MemberAccessor.SetBoxedValue(ref cached, null); - // Explicitly clear the foreign key values and parent object reference - ClearForeignKeysHelper(assoc, to.Current); - } - } - // else the item was not found in the cache, so there is no fixup that has to be done - // We are explicitly not calling ClearForeignKeysHelper because it breaks existing shipped behavior and we want to maintain backward compatibility - } - else { - // This is a unidirectional relationship or we have no way to look up the other side in the cache, so just clear our own side - ClearForeignKeysHelper(assoc, to.Current); - } - } - // else this is not the 1-side (foreign key) of the relationship, so there is nothing for us to do - } - } - - // Ensure the the member and foreign keys are nulled so that after trackedInstance is deleted, - // the object does not appear to be associated with the other side anymore. This prevents the deleted object - // from referencing objects still in the cache, but also will prevent the related object from being implicitly loaded - private static void ClearForeignKeysHelper(MetaAssociation assoc, object trackedInstance) { - Debug.Assert(assoc.IsForeignKey, "Foreign key clearing should only happen on foreign key side of the association."); - Debug.Assert(assoc.ThisMember.IsAssociation, "Expected ThisMember of an association to always be an association."); - - // If this member is one of our deferred loaders, and it does not already have a value, explicitly set the deferred source to - // null so that when we set the association member itself to null later, it doesn't trigger an implicit load. - // This is only necessary if the value has not already been assigned or set, because otherwise we won't implicitly load anyway when the member is accessed. - MetaDataMember thisMember = assoc.ThisMember; - - if (thisMember.IsDeferred && - !(thisMember.StorageAccessor.HasAssignedValue(trackedInstance) || thisMember.StorageAccessor.HasLoadedValue(trackedInstance))) - { - // If this is a deferred member, set the value directly in the deferred accessor instead of going - // through the normal member accessor, so that we don't trigger an implicit load. - thisMember.DeferredSourceAccessor.SetBoxedValue(ref trackedInstance, null); - } - - // Notify the object that the relationship should be considered deleted. - // This allows the object to do its own fixup even when we can't do it automatically. - thisMember.MemberAccessor.SetBoxedValue(ref trackedInstance, null); - - // Also set the foreign key values to null if possible - for (int i = 0, n = assoc.ThisKey.Count; i < n; i++) { - MetaDataMember thisKey = assoc.ThisKey[i]; - if (thisKey.CanBeNull) { - thisKey.StorageAccessor.SetBoxedValue(ref trackedInstance, null); - } - } - } - - private static void ValidateAll(IEnumerable list) { - foreach (var item in list) { - if (item.IsNew) { - item.SynchDependentData(); - if (item.Type.HasAnyValidateMethod) { - SendOnValidate(item.Type, item, ChangeAction.Insert); - } - } else if (item.IsDeleted) { - if (item.Type.HasAnyValidateMethod) { - SendOnValidate(item.Type, item, ChangeAction.Delete); - } - } else if (item.IsPossiblyModified) { - item.SynchDependentData(); - if (item.IsModified && item.Type.HasAnyValidateMethod) { - SendOnValidate(item.Type, item, ChangeAction.Update); - } - } - } - } - - private static void SendOnValidate(MetaType type, TrackedObject item, ChangeAction changeAction) { - if (type != null) { - SendOnValidate(type.InheritanceBase, item, changeAction); - - if (type.OnValidateMethod != null) { - try { - type.OnValidateMethod.Invoke(item.Current, new object[] { changeAction }); - } catch (TargetInvocationException tie) { - if (tie.InnerException != null) { - throw tie.InnerException; - } - - throw; - } - } - } - } - - internal string GetChangeText() { - this.ObserveUntrackedObjects(); - // Must apply inferred deletions only after any untracked objects - // are tracked - this.ApplyInferredDeletions(); - this.BuildEdgeMaps(); - - // append change text only - StringBuilder changeText = new StringBuilder(); - foreach (TrackedObject item in this.GetOrderedList()) { - if (item.IsNew) { - item.SynchDependentData(); - changeDirector.AppendInsertText(item, changeText); - } - else if (item.IsDeleted) { - changeDirector.AppendDeleteText(item, changeText); - } - else if (item.IsPossiblyModified) { - item.SynchDependentData(); - if (item.IsModified) { - changeDirector.AppendUpdateText(item, changeText); - } - } - } - return changeText.ToString(); - } - - internal ChangeSet GetChangeSet() { - List newEntities = new List(); - List deletedEntities = new List(); - List changedEntities = new List(); - - this.ObserveUntrackedObjects(); - // Must apply inferred deletions only after any untracked objects - // are tracked - this.ApplyInferredDeletions(); - - foreach (TrackedObject item in this.tracker.GetInterestingObjects()) { - if (item.IsNew) { - item.SynchDependentData(); - newEntities.Add(item.Current); - } - else if (item.IsDeleted) { - deletedEntities.Add(item.Current); - } - else if (item.IsPossiblyModified) { - item.SynchDependentData(); - if (item.IsModified) { - changedEntities.Add(item.Current); - } - } - } - - return new ChangeSet(newEntities.AsReadOnly(), deletedEntities.AsReadOnly(), changedEntities.AsReadOnly()); - } - - // verify that primary key and db-generated values have not changed - private static void CheckForInvalidChanges(TrackedObject tracked) { - foreach (MetaDataMember mem in tracked.Type.PersistentDataMembers) { - if (mem.IsPrimaryKey || mem.IsDbGenerated || mem.IsVersion) { - if (tracked.HasChangedValue(mem)) { - if (mem.IsPrimaryKey) { - throw Error.IdentityChangeNotAllowed(mem.Name, tracked.Type.Name); - } - else { - throw Error.DbGeneratedChangeNotAllowed(mem.Name, tracked.Type.Name); - } - } - } - } - } - - /// - /// Create an ChangeConflictException with the best message - /// - static private ChangeConflictException CreateChangeConflictException(int totalUpdatesAttempted, int failedUpdates) { - string msg = Strings.RowNotFoundOrChanged; - if (totalUpdatesAttempted > 1) { - msg = Strings.UpdatesFailedMessage(failedUpdates, totalUpdatesAttempted); - } - return new ChangeConflictException(msg); - } - - internal void TrackUntrackedObjects() { - Dictionary visited = new Dictionary(); - - // search for untracked new objects - List items = new List(this.tracker.GetInterestingObjects()); - foreach (TrackedObject item in items) { - this.TrackUntrackedObjects(item.Type, item.Current, visited); - } - } - - internal void ApplyInferredDeletions() { - foreach (TrackedObject item in this.tracker.GetInterestingObjects()) { - if (item.CanInferDelete()) { - // based on DeleteOnNull specifications on the item's associations, - // a deletion can be inferred for this item. The actual state transition - // is dependent on the current item state. - if (item.IsNew) { - item.ConvertToRemoved(); - } - else if (item.IsPossiblyModified || item.IsModified) { - item.ConvertToDeleted(); - } - } - } - } - - private void TrackUntrackedObjects(MetaType type, object item, Dictionary visited) { - if (!visited.ContainsKey(item)) { - visited.Add(item, item); - TrackedObject tracked = this.tracker.GetTrackedObject(item); - if (tracked == null) { - tracked = this.tracker.Track(item); - tracked.ConvertToNew(); - } - else if (tracked.IsDead || tracked.IsRemoved) { - // ignore - return; - } - - // search parents (objects we are dependent on) - foreach (RelatedItem parent in this.services.GetParents(type, item)) { - this.TrackUntrackedObjects(parent.Type, parent.Item, visited); - } - - // synch up primary key - if (tracked.IsNew) { - tracked.InitializeDeferredLoaders(); - - if (!tracked.IsPendingGeneration(tracked.Type.IdentityMembers)) { - tracked.SynchDependentData(); - object cached = this.services.InsertLookupCachedObject(tracked.Type, item); - if (cached != item) { - TrackedObject cachedTracked = this.tracker.GetTrackedObject(cached); - Debug.Assert(cachedTracked != null); - if (cachedTracked.IsDeleted || cachedTracked.CanInferDelete()) { - // adding new object with same ID as object being deleted.. turn into modified - tracked.ConvertToPossiblyModified(cachedTracked.Original); - // turn deleted to dead... - cachedTracked.ConvertToDead(); - - this.services.RemoveCachedObjectLike(tracked.Type, item); - this.services.InsertLookupCachedObject(tracked.Type, item); - } - else if (!cachedTracked.IsDead) { - throw new DuplicateKeyException(item, Strings.CantAddAlreadyExistingKey); - } - } - } - else { - // we may have a generated PK, however we set the PK on this new item to - // match a deleted item - object cached = this.services.GetCachedObjectLike(tracked.Type, item); - if (cached != null) { - TrackedObject cachedTracked = this.tracker.GetTrackedObject(cached); - Debug.Assert(cachedTracked != null); - if (cachedTracked.IsDeleted || cachedTracked.CanInferDelete()) { - // adding new object with same ID as object being deleted.. turn into modified - tracked.ConvertToPossiblyModified(cachedTracked.Original); - // turn deleted to dead... - cachedTracked.ConvertToDead(); - - this.services.RemoveCachedObjectLike(tracked.Type, item); - this.services.InsertLookupCachedObject(tracked.Type, item); - } - } - } - } - - // search children (objects that are dependent on us) - foreach (RelatedItem child in this.services.GetChildren(type, item)) { - this.TrackUntrackedObjects(child.Type, child.Item, visited); - } - } - } - - internal void ObserveUntrackedObjects() { - Dictionary visited = new Dictionary(); - - List items = new List(this.tracker.GetInterestingObjects()); - foreach (TrackedObject item in items) { - this.ObserveUntrackedObjects(item.Type, item.Current, visited); - } - } - - private void ObserveUntrackedObjects(MetaType type, object item, Dictionary visited) { - if (!visited.ContainsKey(item)) { - visited.Add(item, item); - TrackedObject tracked = this.tracker.GetTrackedObject(item); - if (tracked == null) { - tracked = this.tracker.Track(item); - tracked.ConvertToNew(); - } else if (tracked.IsDead || tracked.IsRemoved) { - // ignore - return; - } - - // search parents (objects we are dependent on) - foreach (RelatedItem parent in this.services.GetParents(type, item)) { - this.ObserveUntrackedObjects(parent.Type, parent.Item, visited); - } - - // synch up primary key unless its generated. - if (tracked.IsNew) { - if (!tracked.IsPendingGeneration(tracked.Type.IdentityMembers)) { - tracked.SynchDependentData(); - } - } - - // search children (objects that are dependent on us) - foreach (RelatedItem child in this.services.GetChildren(type, item)) { - this.ObserveUntrackedObjects(child.Type, child.Item, visited); - } - } - } - - private TrackedObject GetOtherItem(MetaAssociation assoc, object instance) { - if (instance == null) - return null; - object other = null; - // Don't load unloaded references - if (assoc.ThisMember.StorageAccessor.HasAssignedValue(instance) || - assoc.ThisMember.StorageAccessor.HasLoadedValue(instance) - ) { - other = assoc.ThisMember.MemberAccessor.GetBoxedValue(instance); - } - else if (assoc.OtherKeyIsPrimaryKey) { - // Maybe it's in the cache, but not yet attached through reference. - object[] foreignKeys = CommonDataServices.GetForeignKeyValues(assoc, instance); - other = this.services.GetCachedObject(assoc.OtherType, foreignKeys); - } - // else the other key is not the primary key so there is no way to try to look it up - return (other != null) ? this.tracker.GetTrackedObject(other) : null; - } - - private bool HasAssociationChanged(MetaAssociation assoc, TrackedObject item) { - if (item.Original != null && item.Current != null) { - if (assoc.ThisMember.StorageAccessor.HasAssignedValue(item.Current) || - assoc.ThisMember.StorageAccessor.HasLoadedValue(item.Current) - ) { - return this.GetOtherItem(assoc, item.Current) != this.GetOtherItem(assoc, item.Original); - } - else { - object[] currentFKs = CommonDataServices.GetForeignKeyValues(assoc, item.Current); - object[] originaFKs = CommonDataServices.GetForeignKeyValues(assoc, item.Original); - for (int i = 0, n = currentFKs.Length; i < n; i++) { - if (!object.Equals(currentFKs[i], originaFKs[i])) - return true; - } - } - } - return false; - } - - private void BuildEdgeMaps() { - this.currentParentEdges.Clear(); - this.originalChildEdges.Clear(); - this.originalChildReferences.Clear(); - - List list = new List(this.tracker.GetInterestingObjects()); - foreach (TrackedObject item in list) { - bool isNew = item.IsNew; - MetaType mt = item.Type; - foreach (MetaAssociation assoc in mt.Associations) { - if (assoc.IsForeignKey) { - TrackedObject otherItem = this.GetOtherItem(assoc, item.Current); - TrackedObject dbOtherItem = this.GetOtherItem(assoc, item.Original); - bool pointsToDeleted = (otherItem != null && otherItem.IsDeleted) || (dbOtherItem != null && dbOtherItem.IsDeleted); - bool pointsToNew = (otherItem != null && otherItem.IsNew); - - if (isNew || pointsToDeleted || pointsToNew || this.HasAssociationChanged(assoc, item)) { - if (otherItem != null) { - this.currentParentEdges.Add(assoc, item, otherItem); - } - if (dbOtherItem != null) { - if (assoc.IsUnique) { - this.originalChildEdges.Add(assoc, dbOtherItem, item); - } - this.originalChildReferences.Add(dbOtherItem, item); - } - } - } - } - } - } - - enum VisitState { - Before, - After - } - - private List GetOrderedList() { - var objects = this.tracker.GetInterestingObjects().ToList(); - - // give list an initial order (most likely correct order) to avoid deadlocks in server - var range = Enumerable.Range(0, objects.Count).ToList(); - range.Sort((int x, int y) => Compare(objects[x], x, objects[y], y)); - var ordered = range.Select(i => objects[i]).ToList(); - - // permute order if constraint dependencies requires some changes to come before others - var visited = new Dictionary(); - var list = new List(); - foreach (TrackedObject item in ordered) { - this.BuildDependencyOrderedList(item, list, visited); - } - return list; - } - - private static int Compare(TrackedObject x, int xOrdinal, TrackedObject y, int yOrdinal) { - // deal with possible nulls - if (x == y) { - return 0; - } - if (x == null) { - return -1; - } - else if (y == null) { - return 1; - } - // first order by action: Inserts first, Updates, Deletes last - int xAction = x.IsNew ? 0 : x.IsDeleted ? 2 : 1; - int yAction = y.IsNew ? 0 : y.IsDeleted ? 2 : 1; - if (xAction < yAction) { - return -1; - } - else if (xAction > yAction) { - return 1; - } - // no need to order inserts (PK's may not even exist) - if (x.IsNew) { - // keep original order - return xOrdinal.CompareTo(yOrdinal); - } - // second order by type - if (x.Type != y.Type) { - return string.CompareOrdinal(x.Type.Type.FullName, y.Type.Type.FullName); - } - // lastly, order by PK values - int result = 0; - foreach (MetaDataMember mm in x.Type.IdentityMembers) { - object xValue = mm.StorageAccessor.GetBoxedValue(x.Current); - object yValue = mm.StorageAccessor.GetBoxedValue(y.Current); - if (xValue == null) { - if (yValue != null) { - return -1; - } - } - else { - IComparable xc = xValue as IComparable; - if (xc != null) { - result = xc.CompareTo(yValue); - if (result != 0) { - return result; - } - } - } - } - // they are the same? leave in original order - return xOrdinal.CompareTo(yOrdinal); - } - - private void BuildDependencyOrderedList(TrackedObject item, List list, Dictionary visited) { - VisitState state; - if (visited.TryGetValue(item, out state)) { - if (state == VisitState.Before) { - throw Error.CycleDetected(); - } - return; - } - - visited[item] = VisitState.Before; - - if (item.IsInteresting) { - if (item.IsDeleted) { - // if 'item' is deleted - // all objects that used to refer to 'item' must be ordered before item - foreach (TrackedObject other in this.originalChildReferences[item]) { - if (other != item) { - this.BuildDependencyOrderedList(other, list, visited); - } - } - } - else { - // if 'item' is new or changed - // for all objects 'other' that 'item' refers to along association 'assoc' - // if 'other' is new then 'other' must be ordered before 'item' - // if 'assoc' is pure one-to-one and some other item 'prevItem' used to refer to 'other' - // then 'prevItem' must be ordered before 'item' - foreach (MetaAssociation assoc in item.Type.Associations) { - if (assoc.IsForeignKey) { - TrackedObject other = this.currentParentEdges[assoc, item]; - if (other != null) { - if (other.IsNew) { - // if other is new, visit other first (since item's FK depends on it) - if (other != item || item.Type.DBGeneratedIdentityMember != null) { - this.BuildDependencyOrderedList(other, list, visited); - } - } - else if ((assoc.IsUnique || assoc.ThisKeyIsPrimaryKey)) { - TrackedObject prevItem = this.originalChildEdges[assoc, other]; - if (prevItem != null && other != item) { - this.BuildDependencyOrderedList(prevItem, list, visited); - } - } - } - } - } - } - - list.Add(item); - } - - visited[item] = VisitState.After; - } - - class EdgeMap { - Dictionary> associations; - - internal EdgeMap() { - this.associations = new Dictionary>(); - } - - internal void Add(MetaAssociation assoc, TrackedObject from, TrackedObject to) { - Dictionary pairs; - if (!associations.TryGetValue(assoc, out pairs)) { - pairs = new Dictionary(); - associations.Add(assoc, pairs); - } - pairs.Add(from, to); - } - - internal TrackedObject this[MetaAssociation assoc, TrackedObject from] { - get { - Dictionary pairs; - if (associations.TryGetValue(assoc, out pairs)) { - TrackedObject to; - if (pairs.TryGetValue(from, out to)) { - return to; - } - } - return null; - } - } - internal void Clear() { - this.associations.Clear(); - } - } - - class ReferenceMap { - Dictionary> references; - - internal ReferenceMap() { - this.references = new Dictionary>(); - } - - internal void Add(TrackedObject from, TrackedObject to) { - List refs; - if (!references.TryGetValue(from, out refs)) { - refs = new List(); - references.Add(from, refs); - } - if (!refs.Contains(to)) - refs.Add(to); - } - - internal IEnumerable this[TrackedObject from] { - get { - List refs; - if (references.TryGetValue(from, out refs)) { - return refs; - } - return Empty; - } - } - - internal void Clear() { - this.references.Clear(); - } - - private static TrackedObject[] Empty = new TrackedObject[] { }; - } - } -} diff --git a/src/ChangeTracker.cs b/src/ChangeTracker.cs deleted file mode 100644 index 76d8042..0000000 --- a/src/ChangeTracker.cs +++ /dev/null @@ -1,923 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Reflection; -using System.Text; - -namespace System.Data.Linq { - using System.Data.Linq.Mapping; - using System.Data.Linq.Provider; - - internal abstract class ChangeTracker { - /// - /// Starts tracking an object as 'unchanged' - /// - /// - /// - internal abstract TrackedObject Track(object obj); - /// - /// Starts tracking an object as 'unchanged', and optionally - /// 'weakly' tracks all other referenced objects recursively. - /// - /// - /// True if all untracked objects in the graph - /// should be tracked recursively. - /// - internal abstract TrackedObject Track(object obj, bool recurse); - /// - /// Fast-tracks an object that is already in identity cache - /// - /// - internal abstract void FastTrack(object obj); - internal abstract bool IsTracked(object obj); - internal abstract TrackedObject GetTrackedObject(object obj); - internal abstract void StopTracking(object obj); - internal abstract void AcceptChanges(); - internal abstract IEnumerable GetInterestingObjects(); - - internal static ChangeTracker CreateChangeTracker(CommonDataServices dataServices, bool asReadOnly) { - if (asReadOnly) { - return new ReadOnlyChangeTracker(); - } - else { - return new StandardChangeTracker(dataServices); - } - } - - class StandardChangeTracker : ChangeTracker { - Dictionary items; - PropertyChangingEventHandler onPropertyChanging; - CommonDataServices services; - - internal StandardChangeTracker(CommonDataServices services) { - this.services = services; - this.items = new Dictionary(); - this.onPropertyChanging = new PropertyChangingEventHandler(this.OnPropertyChanging); - } - - /// - /// Given a type root and a discriminator, return the type that would be instantiated. - /// - private static MetaType TypeFromDiscriminator(MetaType root, object discriminator) { - foreach (MetaType type in root.InheritanceTypes) { - if (IsSameDiscriminator(discriminator, type.InheritanceCode)) - return type; - } - return root.InheritanceDefault; - } - - private static bool IsSameDiscriminator(object discriminator1, object discriminator2) { - if (discriminator1 == discriminator2) { - return true; - } - if (discriminator1 == null || discriminator2 == null) { - return false; - } - return discriminator1.Equals(discriminator2); - } - - internal override TrackedObject Track(object obj) { - return Track(obj, false); - } - - internal override TrackedObject Track(object obj, bool recurse) { - MetaType type = this.services.Model.GetMetaType(obj.GetType()); - Dictionary visited = new Dictionary(); - return Track(type, obj, visited, recurse, 1); - } - - private TrackedObject Track(MetaType mt, object obj, Dictionary visited, bool recurse, int level) { - StandardTrackedObject tracked = (StandardTrackedObject)this.GetTrackedObject(obj); - if (tracked != null || visited.ContainsKey(obj)) { - return tracked; - } - - // The root object tracked is tracked normally - all other objects - // in the reference graph are weakly tracked. - bool weaklyTrack = level > 1; - tracked = new StandardTrackedObject(this, mt, obj, obj, weaklyTrack); - if (tracked.HasDeferredLoaders) { - throw Error.CannotAttachAddNonNewEntities(); - } - this.items.Add(obj, tracked); - this.Attach(obj); - visited.Add(obj, obj); - - if (recurse) { - // track parents (objects we are dependent on) - foreach (RelatedItem parent in this.services.GetParents(mt, obj)) { - this.Track(parent.Type, parent.Item, visited, recurse, level + 1); - } - - // track children (objects that are dependent on us) - foreach (RelatedItem child in this.services.GetChildren(mt, obj)) { - this.Track(child.Type, child.Item, visited, recurse, level + 1); - } - } - - return tracked; - } - - internal override void FastTrack(object obj) { - // assumes object is already in identity cache - this.Attach(obj); - } - - internal override void StopTracking(object obj) { - this.Detach(obj); - this.items.Remove(obj); - } - - internal override bool IsTracked(object obj) { - return this.items.ContainsKey(obj) || this.IsFastTracked(obj); - } - - private bool IsFastTracked(object obj) { - MetaType type = this.services.Model.GetTable(obj.GetType()).RowType; - return this.services.IsCachedObject(type, obj); - } - - internal override TrackedObject GetTrackedObject(object obj) { - StandardTrackedObject ti; - if (!this.items.TryGetValue(obj, out ti)) { - if (this.IsFastTracked(obj)) { - return this.PromoteFastTrackedObject(obj); - } - } - return ti; - } - - private StandardTrackedObject PromoteFastTrackedObject(object obj) { - Type type = obj.GetType(); - MetaType metaType = this.services.Model.GetTable(type).RowType.GetInheritanceType(type); - return this.PromoteFastTrackedObject(metaType, obj); - } - - private StandardTrackedObject PromoteFastTrackedObject(MetaType type, object obj) { - StandardTrackedObject ti = new StandardTrackedObject(this, type, obj, obj); - this.items.Add(obj, ti); - return ti; - } - - private void Attach(object obj) { - INotifyPropertyChanging notifier = obj as INotifyPropertyChanging; - if (notifier != null) { - notifier.PropertyChanging += this.onPropertyChanging; - } - else { - // if has no notifier, consider it modified already - this.OnPropertyChanging(obj, null); - } - } - - private void Detach(object obj) { - INotifyPropertyChanging notifier = obj as INotifyPropertyChanging; - if (notifier != null) { - notifier.PropertyChanging -= this.onPropertyChanging; - } - } - - private void OnPropertyChanging(object sender, PropertyChangingEventArgs args) { - StandardTrackedObject ti; - if (this.items.TryGetValue(sender, out ti)) { - ti.StartTracking(); - } - else if (this.IsFastTracked(sender)) { - ti = this.PromoteFastTrackedObject(sender); - ti.StartTracking(); - } - } - - internal override void AcceptChanges() { - List list = new List((IEnumerable)this.items.Values); - foreach (TrackedObject item in list) { - item.AcceptChanges(); - } - } - - internal override IEnumerable GetInterestingObjects() { - foreach (StandardTrackedObject ti in this.items.Values) { - if (ti.IsInteresting) { - yield return ti; - } - } - } - - class StandardTrackedObject : TrackedObject { - private StandardChangeTracker tracker; - private MetaType type; - private object current; - private object original; - private State state; - private BitArray dirtyMemberCache; - private bool haveInitializedDeferredLoaders; - private bool isWeaklyTracked; - - enum State { - New, - Deleted, - PossiblyModified, - Modified, - Removed, - Dead - } - - public override string ToString() { - return type.Name + ":" + GetState(); - } - - private string GetState() { - switch (this.state) { - case State.New: - case State.Deleted: - case State.Dead: - case State.Removed: - return this.state.ToString(); - default: - if (this.IsModified) { - return "Modified"; - } - else { - return "Unmodified"; - } - } - } - - internal StandardTrackedObject(StandardChangeTracker tracker, MetaType type, object current, object original) { - if (current == null) { - throw Error.ArgumentNull("current"); - } - this.tracker = tracker; - this.type = type.GetInheritanceType(current.GetType()); - this.current = current; - this.original = original; - this.state = State.PossiblyModified; - dirtyMemberCache = new BitArray(this.type.DataMembers.Count); - } - - internal StandardTrackedObject(StandardChangeTracker tracker, MetaType type, object current, object original, bool isWeaklyTracked) - : this(tracker, type, current, original) { - this.isWeaklyTracked = isWeaklyTracked; - } - - internal override bool IsWeaklyTracked { - get { return isWeaklyTracked; } - } - - internal override MetaType Type { - get { return this.type; } - } - - internal override object Current { - get { return this.current; } - } - - internal override object Original { - get { return this.original; } - } - - internal override bool IsNew { - get { return this.state == State.New; } - } - - internal override bool IsDeleted { - get { return this.state == State.Deleted; } - } - - internal override bool IsRemoved { - get { return this.state == State.Removed; } - } - - internal override bool IsDead { - get { return this.state == State.Dead; } - } - - internal override bool IsModified { - get { return this.state == State.Modified || (this.state == State.PossiblyModified && this.current != this.original && this.HasChangedValues()); } - } - - internal override bool IsUnmodified { - get { return this.state == State.PossiblyModified && (this.current == this.original || !this.HasChangedValues()); } - } - - internal override bool IsPossiblyModified { - get { return this.state == State.Modified || this.state == State.PossiblyModified; } - } - - internal override bool CanInferDelete() { - // A delete can be inferred iff there is a non-nullable singleton association that has - // been set to null, and the association has DeleteOnNull = true. - if (this.state == State.Modified || this.state == State.PossiblyModified) { - foreach (MetaAssociation assoc in Type.Associations) { - if (assoc.DeleteOnNull && assoc.IsForeignKey && !assoc.IsNullable && !assoc.IsMany && - assoc.ThisMember.StorageAccessor.HasAssignedValue(Current) && - assoc.ThisMember.StorageAccessor.GetBoxedValue(Current) == null) { - return true; - } - } - } - return false; - } - - internal override bool IsInteresting { - get { - return this.state == State.New || - this.state == State.Deleted || - this.state == State.Modified || - (this.state == State.PossiblyModified && this.current != this.original) || - CanInferDelete(); - } - } - - internal override void ConvertToNew() { - // must be new or unmodified or removed to convert to new - System.Diagnostics.Debug.Assert(this.IsNew || this.IsRemoved || this.IsUnmodified); - this.original = null; - this.state = State.New; - } - - internal override void ConvertToPossiblyModified() { - System.Diagnostics.Debug.Assert(this.IsPossiblyModified || this.IsDeleted); - this.state = State.PossiblyModified; - this.isWeaklyTracked = false; - } - - internal override void ConvertToModified() { - System.Diagnostics.Debug.Assert(this.IsPossiblyModified); - System.Diagnostics.Debug.Assert(this.type.VersionMember != null || !this.type.HasUpdateCheck); - this.state = State.Modified; - this.isWeaklyTracked = false; - } - - internal override void ConvertToPossiblyModified(object originalState) { - // must be modified or unmodified to convert to modified - System.Diagnostics.Debug.Assert(this.IsNew || this.IsPossiblyModified); - System.Diagnostics.Debug.Assert(originalState != null); - System.Diagnostics.Debug.Assert(originalState.GetType() == this.type.Type); - this.state = State.PossiblyModified; - this.original = this.CreateDataCopy(originalState); - this.isWeaklyTracked = false; - } - - internal override void ConvertToDeleted() { - // must be modified or unmodified to be deleted - System.Diagnostics.Debug.Assert(this.IsDeleted || this.IsPossiblyModified); - this.state = State.Deleted; - this.isWeaklyTracked = false; - } - - internal override void ConvertToDead() { - System.Diagnostics.Debug.Assert(this.IsDead || this.IsDeleted); - this.state = State.Dead; - this.isWeaklyTracked = false; - } - - internal override void ConvertToRemoved() { - System.Diagnostics.Debug.Assert(this.IsRemoved || this.IsNew); - this.state = State.Removed; - this.isWeaklyTracked = false; - } - - internal override void ConvertToUnmodified() { - System.Diagnostics.Debug.Assert(this.IsNew || this.IsPossiblyModified); - // reset to unmodified - this.state = State.PossiblyModified; - if (this.current is INotifyPropertyChanging) { - this.original = this.current; - } - else { - this.original = this.CreateDataCopy(this.current); - } - this.ResetDirtyMemberTracking(); - this.isWeaklyTracked = false; - } - - internal override void AcceptChanges() { - if (IsWeaklyTracked) { - InitializeDeferredLoaders(); - isWeaklyTracked = false; - } - if (this.IsDeleted) { - this.ConvertToDead(); - } - else if (this.IsNew) { - this.InitializeDeferredLoaders(); - this.ConvertToUnmodified(); - } - else if (this.IsPossiblyModified) { - this.ConvertToUnmodified(); - } - } - - private void AssignMember(object instance, MetaDataMember mm, object value) { - // In the unnotified case, directly use the storage accessor - // for everything because there are not events to be fired. - if (!(this.current is INotifyPropertyChanging)) { - mm.StorageAccessor.SetBoxedValue(ref instance, value); - } - else { - // Go through the member accessor to fire events. - mm.MemberAccessor.SetBoxedValue(ref instance, value); - } - } - - /// - /// Certain state is saved during change tracking to enable modifications - /// to be detected taking refresh operations into account. When changes - /// are reverted or accepted, this state must be reset. - /// - private void ResetDirtyMemberTracking() { - this.dirtyMemberCache.SetAll(false); - } - - /// - /// Refresh internal tracking state using the original value and mode - /// specified. - /// - internal override void Refresh(RefreshMode mode, object freshInstance) { - this.SynchDependentData(); - - // This must be done prior to updating original values - this.UpdateDirtyMemberCache(); - - // Apply the refresh strategy to each data member - Type instanceType = freshInstance.GetType(); - foreach (MetaDataMember mm in type.PersistentDataMembers) { - var memberMode = mm.IsDbGenerated ? RefreshMode.OverwriteCurrentValues : mode; - if (memberMode != RefreshMode.KeepCurrentValues) { - if (!mm.IsAssociation && (this.Type.Type == instanceType || mm.DeclaringType.Type.IsAssignableFrom(instanceType))) { - object freshValue = mm.StorageAccessor.GetBoxedValue(freshInstance); - this.RefreshMember(mm, memberMode, freshValue); - } - } - } - - // Make the new data the current original value - this.original = this.CreateDataCopy(freshInstance); - - if (mode == RefreshMode.OverwriteCurrentValues) { - this.ResetDirtyMemberTracking(); - } - } - - /// - /// Using the last saved comparison baseline, figure out which members have - /// changed since the last refresh, and save that information. This must be - /// done BEFORE any merge operations modify the current values. - /// - private void UpdateDirtyMemberCache() { - // iterate over all members, and if they differ from - // last read values, mark as dirty - foreach (MetaDataMember mm in type.PersistentDataMembers) { - if (mm.IsAssociation && mm.Association.IsMany) { - continue; - } - if (!this.dirtyMemberCache.Get(mm.Ordinal) && this.HasChangedValue(mm)) { - this.dirtyMemberCache.Set(mm.Ordinal, true); - } - } - } - - internal override void RefreshMember(MetaDataMember mm, RefreshMode mode, object freshValue) { - System.Diagnostics.Debug.Assert(!mm.IsAssociation); - - if (mode == RefreshMode.KeepCurrentValues) { - return; - } - - bool hasUserChange = this.HasChangedValue(mm); - - // we don't want to overwrite any modified values, unless - // the mode is original wins - if (hasUserChange && mode != RefreshMode.OverwriteCurrentValues) - return; - - object currentValue = mm.StorageAccessor.GetBoxedValue(this.current); - if (!object.Equals(freshValue, currentValue)) { - mm.StorageAccessor.SetBoxedValue(ref this.current, freshValue); - - // update all singleton associations that are affected by a change to this member - foreach (MetaDataMember am in this.GetAssociationsForKey(mm)) { - if (!am.Association.IsMany) { - IEnumerable ds = this.tracker.services.GetDeferredSourceFactory(am).CreateDeferredSource(this.current); - if (am.StorageAccessor.HasValue(this.current)) { - this.AssignMember(this.current, am, ds.Cast().SingleOrDefault()); - } - } - } - } - } - - private IEnumerable GetAssociationsForKey(MetaDataMember key) { - foreach (MetaDataMember mm in this.type.PersistentDataMembers) { - if (mm.IsAssociation && mm.Association.ThisKey.Contains(key)) { - yield return mm; - } - } - } - - internal override object CreateDataCopy(object instance) { - System.Diagnostics.Debug.Assert(instance != null); - Type instanceType = instance.GetType(); - System.Diagnostics.Debug.Assert(instance.GetType() == this.type.Type); - - object copy = Activator.CreateInstance(this.Type.Type); - - MetaType rootMetaType = this.tracker.services.Model.GetTable(instanceType).RowType.InheritanceRoot; - foreach (MetaDataMember mm in rootMetaType.GetInheritanceType(instanceType).PersistentDataMembers) { - if (this.Type.Type != instanceType && !mm.DeclaringType.Type.IsAssignableFrom(instanceType)) { - continue; - } - if (mm.IsDeferred) { - // do not copy associations - if (!mm.IsAssociation) { - if (mm.StorageAccessor.HasValue(instance)) { - object value = mm.DeferredValueAccessor.GetBoxedValue(instance); - mm.DeferredValueAccessor.SetBoxedValue(ref copy, value); - } - else { - IEnumerable ds = this.tracker.services.GetDeferredSourceFactory(mm).CreateDeferredSource(copy); - mm.DeferredSourceAccessor.SetBoxedValue(ref copy, ds); - } - } - } - else { - // otherwise assign the value as-is to the backup instance - object value = mm.StorageAccessor.GetBoxedValue(instance); - // assumes member values are immutable or will communicate changes to entity - // note: byte[] and char[] don't do this. - mm.StorageAccessor.SetBoxedValue(ref copy, value); - } - } - return copy; - } - - internal void StartTracking() { - if (this.original == this.current) { - this.original = this.CreateDataCopy(this.current); - } - } - - // Return value indicates whether or not any data was actually [....]'d - internal override bool SynchDependentData() { - bool valueWasSet = false; - - // set foreign key fields - foreach (MetaAssociation assoc in this.Type.Associations) { - MetaDataMember mm = assoc.ThisMember; - if (assoc.IsForeignKey) { - bool hasAssigned = mm.StorageAccessor.HasAssignedValue(this.current); - bool hasLoaded = mm.StorageAccessor.HasLoadedValue(this.current); - if (hasAssigned || hasLoaded) { - object parent = mm.StorageAccessor.GetBoxedValue(this.current); - if (parent != null) { - // copy parent's current primary key into this instance's foreign key fields - for (int i = 0, n = assoc.ThisKey.Count; i < n; i++) { - MetaDataMember accThis = assoc.ThisKey[i]; - MetaDataMember accParent = assoc.OtherKey[i]; - object parentValue = accParent.StorageAccessor.GetBoxedValue(parent); - accThis.StorageAccessor.SetBoxedValue(ref this.current, parentValue); - valueWasSet = true; - } - } - else if (assoc.IsNullable) { - if (mm.IsDeferred || (this.original != null && mm.MemberAccessor.GetBoxedValue(this.original) != null)) { - // no known parent? set to null - for (int i = 0, n = assoc.ThisKey.Count; i < n; i++) { - MetaDataMember accThis = assoc.ThisKey[i]; - if (accThis.CanBeNull) { - if (this.original != null && this.HasChangedValue(accThis)) { - if (accThis.StorageAccessor.GetBoxedValue(this.current) != null) { - throw Error.InconsistentAssociationAndKeyChange(accThis.Member.Name, mm.Member.Name); - } - } - else { - accThis.StorageAccessor.SetBoxedValue(ref this.current, null); - valueWasSet = true; - } - } - } - } - } - else if (!hasLoaded) { - //Else the parent association has been set to null; but the ID is not nullable so - //the value can not be set - StringBuilder keys = new StringBuilder(); - foreach (MetaDataMember key in assoc.ThisKey) { - if (keys.Length > 0) { - keys.Append(", "); - } - keys.AppendFormat("{0}.{1}", this.Type.Name.ToString(), key.Name); - } - throw Error.CouldNotRemoveRelationshipBecauseOneSideCannotBeNull(assoc.OtherType.Name, this.Type.Name, keys); - } - } - } - } - - /// Explicitly set any inheritance discriminator for item. - if (this.type.HasInheritance) { - if (this.original != null) { - object currentDiscriminator = type.Discriminator.MemberAccessor.GetBoxedValue(this.current); - MetaType currentTypeFromDiscriminator = TypeFromDiscriminator(this.type, currentDiscriminator); - object dbDiscriminator = type.Discriminator.MemberAccessor.GetBoxedValue(this.original); - MetaType dbTypeFromDiscriminator = TypeFromDiscriminator(this.type, dbDiscriminator); - - // Would the discriminator change also change the type? If so, its not allowed. - if (currentTypeFromDiscriminator != dbTypeFromDiscriminator) { - throw Error.CannotChangeInheritanceType(dbDiscriminator, - currentDiscriminator, original.GetType().Name, currentTypeFromDiscriminator); - } - } - else { - // No db value means this is an 'Add'. Set the discriminator. - MetaType currentType = type.GetInheritanceType(this.current.GetType()); - if (currentType.HasInheritanceCode) { - object code = currentType.InheritanceCode; - this.type.Discriminator.MemberAccessor.SetBoxedValue(ref current, code); - valueWasSet = true; - } - } - } - return valueWasSet; - } - - internal override bool HasChangedValue(MetaDataMember mm) { - if (this.current == this.original) { - return false; - } - if (mm.IsAssociation && mm.Association.IsMany) { - return mm.StorageAccessor.HasAssignedValue(this.original); - } - if (mm.StorageAccessor.HasValue(this.current)) { - if (this.original != null && mm.StorageAccessor.HasValue(this.original)) { - // If the member has ever been in a modified state - // in the past, it is considered modified - if (dirtyMemberCache.Get(mm.Ordinal)) { - return true; - } - object baseline = mm.MemberAccessor.GetBoxedValue(this.original); - object currentValue = mm.MemberAccessor.GetBoxedValue(this.current); - if (!object.Equals(currentValue, baseline)) { - return true; - } - return false; - } - else if (mm.IsDeferred && mm.StorageAccessor.HasAssignedValue(this.current)) { - return true; - } - } - return false; - } - - internal override bool HasChangedValues() { - if (this.current == this.original) { - return false; - } - if (this.IsNew) { - return true; - } - foreach (MetaDataMember mm in this.type.PersistentDataMembers) { - if (!mm.IsAssociation && this.HasChangedValue(mm)) { - return true; - } - } - return false; - } - - internal override IEnumerable GetModifiedMembers() { - foreach (MetaDataMember mm in this.type.PersistentDataMembers) { - if (this.IsModifiedMember(mm)) { - object currentValue = mm.MemberAccessor.GetBoxedValue(this.current); - if (this.original != null && mm.StorageAccessor.HasValue(this.original)) { - object originalValue = mm.MemberAccessor.GetBoxedValue(this.original); - yield return new ModifiedMemberInfo(mm.Member, currentValue, originalValue); - } - else if (this.original == null || (mm.IsDeferred && !mm.StorageAccessor.HasLoadedValue(this.current))) { - yield return new ModifiedMemberInfo(mm.Member, currentValue, null); - } - } - } - } - - private bool IsModifiedMember(MetaDataMember member) { - return !member.IsAssociation && - !member.IsPrimaryKey && - !member.IsVersion && - !member.IsDbGenerated && - member.StorageAccessor.HasAssignedValue(this.current) && - (this.state == State.Modified || - (this.state == State.PossiblyModified && this.HasChangedValue(member))); - } - - internal override bool HasDeferredLoaders { - get { - foreach (MetaAssociation assoc in this.Type.Associations) { - if (HasDeferredLoader(assoc.ThisMember)) { - return true; - } - } - IEnumerable deferredMembers = this.Type.PersistentDataMembers.Where(p => p.IsDeferred && !p.IsAssociation); - foreach (MetaDataMember deferredMember in deferredMembers) { - if (HasDeferredLoader(deferredMember)) { - return true; - } - } - return false; - } - } - - private bool HasDeferredLoader(MetaDataMember deferredMember) { - if (!deferredMember.IsDeferred) { - return false; - } - - MetaAccessor acc = deferredMember.StorageAccessor; - if (acc.HasAssignedValue(this.current) || acc.HasLoadedValue(this.current)) { - return false; - } - MetaAccessor dsacc = deferredMember.DeferredSourceAccessor; - IEnumerable loader = (IEnumerable)dsacc.GetBoxedValue(this.current); - - return loader != null; - } - - /// - /// Called to initialize deferred loaders for New or Attached entities. - /// - internal override void InitializeDeferredLoaders() { - if (this.tracker.services.Context.DeferredLoadingEnabled) { - foreach (MetaAssociation assoc in this.Type.Associations) { - // don't set loader on association that is dependent on unrealized generated values - if (!this.IsPendingGeneration(assoc.ThisKey)) { - InitializeDeferredLoader(assoc.ThisMember); - } - } - IEnumerable deferredMembers = this.Type.PersistentDataMembers.Where(p => p.IsDeferred && !p.IsAssociation); - foreach (MetaDataMember deferredMember in deferredMembers) { - // don't set loader on member that is dependent on unrealized generated values - if (!this.IsPendingGeneration(Type.IdentityMembers)) { - InitializeDeferredLoader(deferredMember); - } - } - haveInitializedDeferredLoaders = true; - } - } - - private void InitializeDeferredLoader(MetaDataMember deferredMember) { - MetaAccessor acc = deferredMember.StorageAccessor; - if (!acc.HasAssignedValue(this.current) && !acc.HasLoadedValue(this.current)) { - MetaAccessor dsacc = deferredMember.DeferredSourceAccessor; - IEnumerable loader = (IEnumerable)dsacc.GetBoxedValue(this.current); - // don't reset loader on any deferred member that already has one - if (loader == null) { - IDeferredSourceFactory factory = this.tracker.services.GetDeferredSourceFactory(deferredMember); - loader = factory.CreateDeferredSource(this.current); - dsacc.SetBoxedValue(ref this.current, loader); - - } - else if (loader != null && !haveInitializedDeferredLoaders) { - // If loader is present but wasn't generated by us, then - // an attempt to Attach or Add an entity from another context - // has been made, which is not supported. - throw Error.CannotAttachAddNonNewEntities(); - } - } - } - - internal override bool IsPendingGeneration(IEnumerable key) { - if (this.IsNew) { - foreach (MetaDataMember member in key) { - if (IsMemberPendingGeneration(member)) { - return true; - } - } - } - return false; - } - - internal override bool IsMemberPendingGeneration(MetaDataMember keyMember) { - if (this.IsNew && keyMember.IsDbGenerated) { - return true; - } - // look for any FK association that has this key member (should only be one) - foreach (MetaAssociation assoc in type.Associations) { - if (assoc.IsForeignKey) { - int index = assoc.ThisKey.IndexOf(keyMember); - if (index > -1) { - // we must have a reference to this other object to know if its side of - // the association is generated or not - object otherItem = null; - if (assoc.ThisMember.IsDeferred) { - otherItem = assoc.ThisMember.DeferredValueAccessor.GetBoxedValue(this.current); - } - else { - otherItem = assoc.ThisMember.StorageAccessor.GetBoxedValue(this.current); - } - if (otherItem != null) { - if (assoc.IsMany) { - // Can't be pending generation for a value that would have to be the same - // across many rows. - continue; - } - else { - StandardTrackedObject trackedOther = (StandardTrackedObject)this.tracker.GetTrackedObject(otherItem); - if (trackedOther != null) { - MetaDataMember otherMember = assoc.OtherKey[index]; - return trackedOther.IsMemberPendingGeneration(otherMember); - } - } - } - } - } - } - return false; - } - } - } - - /// - /// This is the implementation used when change tracking is disabled. - /// - class ReadOnlyChangeTracker : ChangeTracker { - internal override TrackedObject Track(object obj) { return null; } - internal override TrackedObject Track(object obj, bool recurse) { return null; } - internal override void FastTrack(object obj) { } - internal override bool IsTracked(object obj) { return false; } - internal override TrackedObject GetTrackedObject(object obj) { return null; } - internal override void StopTracking(object obj) { } - internal override void AcceptChanges() { } - internal override IEnumerable GetInterestingObjects() { return new TrackedObject[0]; } - } - } - - internal abstract class TrackedObject { - internal abstract MetaType Type { get; } - /// - /// The current client value. - /// - internal abstract object Current { get; } - /// - /// The last read database value. This is updated whenever the - /// item is refreshed. - /// - internal abstract object Original { get; } - internal abstract bool IsInteresting { get; } // new, deleted or possibly changed - internal abstract bool IsNew { get; } - internal abstract bool IsDeleted { get; } - internal abstract bool IsModified { get; } - internal abstract bool IsUnmodified { get; } - internal abstract bool IsPossiblyModified { get; } - internal abstract bool IsRemoved { get; } - internal abstract bool IsDead { get; } - /// - /// True if the object is being tracked (perhaps during a recursive - /// attach operation) but can be transitioned to other states. - /// - internal abstract bool IsWeaklyTracked { get; } - internal abstract bool HasDeferredLoaders { get; } - internal abstract bool HasChangedValues(); - internal abstract IEnumerable GetModifiedMembers(); - internal abstract bool HasChangedValue(MetaDataMember mm); - internal abstract bool CanInferDelete(); - internal abstract void AcceptChanges(); - internal abstract void ConvertToNew(); - internal abstract void ConvertToPossiblyModified(); - internal abstract void ConvertToPossiblyModified(object original); - internal abstract void ConvertToUnmodified(); - internal abstract void ConvertToModified(); - internal abstract void ConvertToDeleted(); - internal abstract void ConvertToRemoved(); - internal abstract void ConvertToDead(); - /// - /// Refresh the item by making the value passed in the current - /// Database value, and refreshing the current values using the - /// mode specified. - /// - internal abstract void Refresh(RefreshMode mode, object freshInstance); - /// - /// Does the refresh operation for a single member. This method does not - /// update the baseline 'original' value. You must call - /// Refresh(RefreshMode.KeepCurrentValues, freshInstance) to finish the refresh - /// after refreshing individual members. - /// - /// - /// - /// - internal abstract void RefreshMember(MetaDataMember member, RefreshMode mode, object freshValue); - /// - /// Create a data-member only copy of the instance (no associations) - /// - /// - internal abstract object CreateDataCopy(object instance); - - internal abstract bool SynchDependentData(); - - internal abstract bool IsPendingGeneration(IEnumerable keyMembers); - internal abstract bool IsMemberPendingGeneration(MetaDataMember keyMember); - - internal abstract void InitializeDeferredLoaders(); - } -} diff --git a/src/ConstantsEnums.cs b/src/ConstantsEnums.cs new file mode 100644 index 0000000..21f4ec0 --- /dev/null +++ b/src/ConstantsEnums.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace System.Data.Linq +{ + /// + /// Describes the type of change the entity will undergo when submitted to the database. + /// + public enum ChangeAction + { + /// + /// The entity will not be submitted. + /// + None = 0, + /// + /// The entity will be deleted. + /// + Delete, + /// + /// The entity will be inserted. + /// + Insert, + /// + /// The entity will be updated. + /// + Update + } + + + /// + /// Used to specify how a submit should behave when one + /// or more updates fail due to optimistic concurrency + /// conflicts. + /// + public enum ConflictMode + { + /// + /// Fail immediately when the first change conflict is encountered. + /// + FailOnFirstConflict, + /// + /// Only fail after all changes have been attempted. + /// + ContinueOnConflict + } + + /// + /// Used to specify a value synchronization strategy. + /// + public enum RefreshMode + { + /// + /// Keep the current values. + /// + KeepCurrentValues, + /// + /// Current values that have been changed are not modified, but + /// any unchanged values are updated with the current database + /// values. No changes are lost in this merge. + /// + KeepChanges, + /// + /// All current values are overwritten with current database values, + /// regardless of whether they have been changed. + /// + OverwriteCurrentValues + } +} diff --git a/src/Exceptions.cs b/src/Exceptions.cs deleted file mode 100644 index 90ebd88..0000000 --- a/src/Exceptions.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.Linq.Provider; -using System.Linq; -using System.Diagnostics.CodeAnalysis; - -namespace System.Data.Linq { - /// - /// DLinq-specific custom exception factory. - /// - [SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable", Justification = "Unknown reason.")] - [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "Unknown reason.")] - public class ChangeConflictException : Exception { - public ChangeConflictException() { } - public ChangeConflictException(string message) : base(message) { } - public ChangeConflictException(string message, Exception innerException) : base(message, innerException) { } - } - /// - /// An attempt was made to add an object to the identity cache with a key that is already in use - /// - [SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable", Justification = "Unknown reason.")] - [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "Unknown reason.")] - public class DuplicateKeyException : InvalidOperationException { - private object duplicate; - public DuplicateKeyException(object duplicate) { - this.duplicate = duplicate; - } - public DuplicateKeyException(object duplicate, string message) - : base(message) { - this.duplicate = duplicate; - } - public DuplicateKeyException(object duplicate, string message, Exception innerException) - : base(message, innerException) { - this.duplicate = duplicate; - } - - /// - /// The object whose duplicate key caused the exception. - /// - public object Object { - get { - return duplicate; - } - } - } - - /// - /// An attempt was made to change an FK but the Entity is Loaded - /// - [SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable", Justification = "Unknown reason.")] - [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "Unknown reason.")] - public class ForeignKeyReferenceAlreadyHasValueException : InvalidOperationException { - public ForeignKeyReferenceAlreadyHasValueException() { } - public ForeignKeyReferenceAlreadyHasValueException(string message) : base(message) { } - public ForeignKeyReferenceAlreadyHasValueException(string message, Exception innerException) : base(message, innerException) { } - } -} diff --git a/src/Exceptions/ChangeConflictException.cs b/src/Exceptions/ChangeConflictException.cs new file mode 100644 index 0000000..8b08127 --- /dev/null +++ b/src/Exceptions/ChangeConflictException.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Data.Linq.Provider; +using System.Linq; +using System.Diagnostics.CodeAnalysis; + +namespace System.Data.Linq { + /// + /// DLinq-specific custom exception factory. + /// + [SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable", Justification = "Unknown reason.")] + [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "Unknown reason.")] + public class ChangeConflictException : Exception { + public ChangeConflictException() { } + public ChangeConflictException(string message) : base(message) { } + public ChangeConflictException(string message, Exception innerException) : base(message, innerException) { } + } +} diff --git a/src/Exceptions/DuplicateKeyException.cs b/src/Exceptions/DuplicateKeyException.cs new file mode 100644 index 0000000..a3c9ae1 --- /dev/null +++ b/src/Exceptions/DuplicateKeyException.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Data.Linq.Provider; +using System.Linq; +using System.Diagnostics.CodeAnalysis; + +namespace System.Data.Linq +{ + /// + /// An attempt was made to add an object to the identity cache with a key that is already in use + /// + [SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable", Justification = "Unknown reason.")] + [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "Unknown reason.")] + public class DuplicateKeyException : InvalidOperationException + { + private object duplicate; + public DuplicateKeyException(object duplicate) + { + this.duplicate = duplicate; + } + public DuplicateKeyException(object duplicate, string message) + : base(message) + { + this.duplicate = duplicate; + } + public DuplicateKeyException(object duplicate, string message, Exception innerException) + : base(message, innerException) + { + this.duplicate = duplicate; + } + + /// + /// The object whose duplicate key caused the exception. + /// + public object Object + { + get + { + return duplicate; + } + } + } +} + diff --git a/src/Exceptions/ForeignKeyReferenceAlreadyHasValueException.cs b/src/Exceptions/ForeignKeyReferenceAlreadyHasValueException.cs new file mode 100644 index 0000000..adede09 --- /dev/null +++ b/src/Exceptions/ForeignKeyReferenceAlreadyHasValueException.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Data.Linq.Provider; +using System.Linq; +using System.Diagnostics.CodeAnalysis; + +namespace System.Data.Linq +{ + /// + /// An attempt was made to change an FK but the Entity is Loaded + /// + [SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable", Justification = "Unknown reason.")] + [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "Unknown reason.")] + public class ForeignKeyReferenceAlreadyHasValueException : InvalidOperationException + { + public ForeignKeyReferenceAlreadyHasValueException() { } + public ForeignKeyReferenceAlreadyHasValueException(string message) : base(message) { } + public ForeignKeyReferenceAlreadyHasValueException(string message, Exception innerException) : base(message, innerException) { } + } +} + diff --git a/src/FunctionResults.cs b/src/FunctionResults.cs deleted file mode 100644 index 2a8050f..0000000 --- a/src/FunctionResults.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -namespace System.Data.Linq { - /// - /// The result of executing a query. - /// - public interface IExecuteResult : IDisposable { - /// - /// The return value or result of the executed query. This object has the same type as the - /// query expression's Type property. - /// - object ReturnValue { get; } - - /// - /// Retrieves the nth output parameter. This method is normally used when the query is a mapped - /// function with output parameters. - /// - /// - /// - object GetParameterValue(int parameterIndex); - } - - /// - /// Interface providing access to a function return value. - /// - public interface IFunctionResult { - /// - /// The value. - /// - object ReturnValue { get; } - } - - /// - /// An interface for representing the result of a mapped function with a single return sequence. - /// - [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification="[....]: Meant to represent a database table which is delayed loaded and doesn't provide collection semantics.")] - public interface ISingleResult : IEnumerable, IFunctionResult, IDisposable { } - - /// - /// An interface for representing results of mapped functions or queries with variable return sequences. - /// - public interface IMultipleResults : IFunctionResult, IDisposable { - /// - /// Retrieves the next result as a sequence of Type 'TElement'. - /// - /// - /// - [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "[....]: Generic parameters are required for strong-typing of the return type.")] - IEnumerable GetResult(); - } -} diff --git a/src/IdentityManager.cs b/src/IdentityManager.cs deleted file mode 100644 index 414c193..0000000 --- a/src/IdentityManager.cs +++ /dev/null @@ -1,377 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Security; -using System.Security.Permissions; -using System.Runtime.CompilerServices; - -namespace System.Data.Linq { - using System.Data.Linq.Mapping; - using System.Data.Linq.Provider; - - internal abstract class IdentityManager { - internal abstract object InsertLookup(MetaType type, object instance); - internal abstract bool RemoveLike(MetaType type, object instance); - internal abstract object Find(MetaType type, object[] keyValues); - internal abstract object FindLike(MetaType type, object instance); - - internal static IdentityManager CreateIdentityManager(bool asReadOnly) { - if (asReadOnly) { - return new ReadOnlyIdentityManager(); - } - else { - return new StandardIdentityManager(); - } - } - - class StandardIdentityManager : IdentityManager { - Dictionary caches; - IdentityCache currentCache; - MetaType currentType; - - internal StandardIdentityManager() { - this.caches = new Dictionary(); - } - - internal override object InsertLookup(MetaType type, object instance) { - this.SetCurrent(type); - return this.currentCache.InsertLookup(instance); - } - - internal override bool RemoveLike(MetaType type, object instance) { - this.SetCurrent(type); - return this.currentCache.RemoveLike(instance); - } - - internal override object Find(MetaType type, object[] keyValues) { - this.SetCurrent(type); - return this.currentCache.Find(keyValues); - } - - internal override object FindLike(MetaType type, object instance) { - this.SetCurrent(type); - return this.currentCache.FindLike(instance); - } - - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] - private void SetCurrent(MetaType type) { - type = type.InheritanceRoot; - if (this.currentType != type) { - if (!this.caches.TryGetValue(type, out this.currentCache)) { - KeyManager km = GetKeyManager(type); - this.currentCache = (IdentityCache)Activator.CreateInstance( - typeof(IdentityCache<,>).MakeGenericType(type.Type, km.KeyType), - BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, - new object[] { km }, null - ); - this.caches.Add(type, this.currentCache); - } - this.currentType = type; - } - } - - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] - static KeyManager GetKeyManager(MetaType type) { - int n = type.IdentityMembers.Count; - MetaDataMember mm = type.IdentityMembers[0]; - - KeyManager km = (KeyManager)Activator.CreateInstance( - typeof(SingleKeyManager<,>).MakeGenericType(type.Type, mm.Type), - BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, - new object[] { mm.StorageAccessor, 0 }, null - ); - for (int i = 1; i < n; i++) { - mm = type.IdentityMembers[i]; - km = (KeyManager) - Activator.CreateInstance( - typeof(MultiKeyManager<,,>).MakeGenericType(type.Type, mm.Type, km.KeyType), - BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, - new object[] { mm.StorageAccessor, i, km }, null - ); - } - - return km; - } - - #region Nested type definitions - // These types are internal rather than private to work around - // CLR bug #117419 related to type visibility under partial trust - // in nested class scenarios. - - internal abstract class KeyManager { - internal abstract Type KeyType { get; } - } - - internal abstract class KeyManager : KeyManager { - internal abstract K CreateKeyFromInstance(T instance); - internal abstract bool TryCreateKeyFromValues(object[] values, out K k); - internal abstract IEqualityComparer Comparer { get; } - } - - internal class SingleKeyManager : KeyManager { - bool isKeyNullAssignable; - MetaAccessor accessor; - int offset; - IEqualityComparer comparer; - - internal SingleKeyManager(MetaAccessor accessor, int offset) { - this.accessor = accessor; - this.offset = offset; - this.isKeyNullAssignable = System.Data.Linq.SqlClient.TypeSystem.IsNullAssignable(typeof(V)); - } - - internal override V CreateKeyFromInstance(T instance) { - return this.accessor.GetValue(instance); - } - - internal override bool TryCreateKeyFromValues(object[] values, out V v) { - object o = values[this.offset]; - if (o == null && !this.isKeyNullAssignable) { - v = default(V); - return false; - } - v = (V)o; - return true; - } - - internal override Type KeyType { - get { return typeof(V); } - } - - internal override IEqualityComparer Comparer { - get { - if (this.comparer == null) { - this.comparer = EqualityComparer.Default; - } - return this.comparer; - } - } - } - - internal class MultiKeyManager : KeyManager> { - MetaAccessor accessor; - int offset; - KeyManager next; - IEqualityComparer> comparer; - - internal MultiKeyManager(MetaAccessor accessor, int offset, KeyManager next) { - this.accessor = accessor; - this.next = next; - this.offset = offset; - } - - internal override MultiKey CreateKeyFromInstance(T instance) { - return new MultiKey( - this.accessor.GetValue(instance), - this.next.CreateKeyFromInstance(instance) - ); - } - - internal override bool TryCreateKeyFromValues(object[] values, out MultiKey k) { - System.Diagnostics.Debug.Assert(this.offset < values.Length, "offset is outside the bounds of the values array"); - - object o = values[this.offset]; - if (o == null && typeof(V1).IsValueType) { - k = default(MultiKey); - return false; - } - V2 v2; - if (!this.next.TryCreateKeyFromValues(values, out v2)) { - k = default(MultiKey); - return false; - } - k = new MultiKey((V1)o, v2); - return true; - } - - internal override Type KeyType { - get { return typeof(MultiKey); } - } - - internal override IEqualityComparer> Comparer { - get { - if (this.comparer == null) { - this.comparer = new MultiKey.Comparer(EqualityComparer.Default, next.Comparer); - } - return this.comparer; - } - } - } - - internal struct MultiKey { - T1 value1; - T2 value2; - - internal MultiKey(T1 value1, T2 value2) { - this.value1 = value1; - this.value2 = value2; - } - - internal class Comparer : IEqualityComparer>, IEqualityComparer { - IEqualityComparer comparer1; - IEqualityComparer comparer2; - - internal Comparer(IEqualityComparer comparer1, IEqualityComparer comparer2) { - this.comparer1 = comparer1; - this.comparer2 = comparer2; - } - - public bool Equals(MultiKey x, MultiKey y) { - return this.comparer1.Equals(x.value1, y.value1) && - this.comparer2.Equals(x.value2, y.value2); - } - - public int GetHashCode(MultiKey x) { - return this.comparer1.GetHashCode(x.value1) ^ this.comparer2.GetHashCode(x.value2); - } - - bool IEqualityComparer.Equals(object x, object y) { - return this.Equals((MultiKey)x, (MultiKey)y); - } - - int IEqualityComparer.GetHashCode(object x) { - return this.GetHashCode((MultiKey)x); - } - } - } - - internal abstract class IdentityCache { - internal abstract object Find(object[] keyValues); - internal abstract object FindLike(object instance); - internal abstract object InsertLookup(object instance); - internal abstract bool RemoveLike(object instance); - } - - internal class IdentityCache : IdentityCache { - int[] buckets; - Slot[] slots; - int count; - int freeList; - KeyManager keyManager; - IEqualityComparer comparer; - - public IdentityCache(KeyManager keyManager) { - this.keyManager = keyManager; - this.comparer = keyManager.Comparer; - buckets = new int[7]; - slots = new Slot[7]; - freeList = -1; - } - - internal override object InsertLookup(object instance) { - T value = (T)instance; - K key = this.keyManager.CreateKeyFromInstance(value); - Find(key, ref value, true); - return value; - } - - internal override bool RemoveLike(object instance) { - T value = (T)instance; - K key = this.keyManager.CreateKeyFromInstance(value); - - int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; - int bucket = hashCode % buckets.Length; - int last = -1; - for (int i = buckets[bucket] - 1; i >= 0; last = i, i = slots[i].next) { - if (slots[i].hashCode == hashCode && comparer.Equals(slots[i].key, key)) { - if (last < 0) { - buckets[bucket] = slots[i].next + 1; - } - else { - slots[last].next = slots[i].next; - } - slots[i].hashCode = -1; - slots[i].value = default(T); - slots[i].next = freeList; - freeList = i; - return true; - } - } - return false; - } - - internal override object Find(object[] keyValues) { - K key; - if (this.keyManager.TryCreateKeyFromValues(keyValues, out key)) { - T value = default(T); - if (Find(key, ref value, false)) - return value; - } - return null; - } - - internal override object FindLike(object instance) { - T value = (T)instance; - K key = this.keyManager.CreateKeyFromInstance(value); - if (Find(key, ref value, false)) - return value; - return null; - } - - bool Find(K key, ref T value, bool add) { - int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; - for (int i = buckets[hashCode % buckets.Length] - 1; i >= 0; i = slots[i].next) { - if (slots[i].hashCode == hashCode && comparer.Equals(slots[i].key, key)) { - value = slots[i].value; - return true; - } - } - if (add) { - int index; - if (freeList >= 0) { - index = freeList; - freeList = slots[index].next; - } - else { - if (count == slots.Length) Resize(); - index = count; - count++; - } - int bucket = hashCode % buckets.Length; - slots[index].hashCode = hashCode; - slots[index].key = key; - slots[index].value = value; - slots[index].next = buckets[bucket] - 1; - buckets[bucket] = index + 1; - } - return false; - } - - void Resize() { - int newSize = checked(count * 2 + 1); - int[] newBuckets = new int[newSize]; - Slot[] newSlots = new Slot[newSize]; - Array.Copy(slots, 0, newSlots, 0, count); - for (int i = 0; i < count; i++) { - int bucket = newSlots[i].hashCode % newSize; - newSlots[i].next = newBuckets[bucket] - 1; - newBuckets[bucket] = i + 1; - } - buckets = newBuckets; - slots = newSlots; - } - - internal struct Slot { - internal int hashCode; - internal K key; - internal T value; - internal int next; - } - } - #endregion - } - - /// - /// This is the noop implementation used when object tracking is disabled. - /// - class ReadOnlyIdentityManager : IdentityManager { - internal ReadOnlyIdentityManager() { } - internal override object InsertLookup(MetaType type, object instance) { return instance; } - internal override bool RemoveLike(MetaType type, object instance) { return false; } - internal override object Find(MetaType type, object[] keyValues) { return null; } - internal override object FindLike(MetaType type, object instance) { return null; } - } - } -} diff --git a/src/IdentityMangement/IdentityCache.cs b/src/IdentityMangement/IdentityCache.cs new file mode 100644 index 0000000..71baf4f --- /dev/null +++ b/src/IdentityMangement/IdentityCache.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Security; +using System.Security.Permissions; +using System.Runtime.CompilerServices; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + + internal abstract class IdentityCache + { + internal abstract object Find(object[] keyValues); + internal abstract object FindLike(object instance); + internal abstract object InsertLookup(object instance); + internal abstract bool RemoveLike(object instance); + } +} + diff --git a/src/IdentityMangement/IdentityCacheOfTK.cs b/src/IdentityMangement/IdentityCacheOfTK.cs new file mode 100644 index 0000000..b54d3a2 --- /dev/null +++ b/src/IdentityMangement/IdentityCacheOfTK.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Security; +using System.Security.Permissions; +using System.Runtime.CompilerServices; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + + internal class IdentityCache : IdentityCache + { + int[] buckets; + Slot[] slots; + int count; + int freeList; + KeyManager keyManager; + IEqualityComparer comparer; + + #region Private classes + internal struct Slot + { + internal int hashCode; + internal K key; + internal T value; + internal int next; + } + #endregion + + + public IdentityCache(KeyManager keyManager) + { + this.keyManager = keyManager; + this.comparer = keyManager.Comparer; + buckets = new int[7]; + slots = new Slot[7]; + freeList = -1; + } + + internal override object InsertLookup(object instance) + { + T value = (T)instance; + K key = this.keyManager.CreateKeyFromInstance(value); + Find(key, ref value, true); + return value; + } + + internal override bool RemoveLike(object instance) + { + T value = (T)instance; + K key = this.keyManager.CreateKeyFromInstance(value); + + int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; + int bucket = hashCode % buckets.Length; + int last = -1; + for(int i = buckets[bucket] - 1; i >= 0; last = i, i = slots[i].next) + { + if(slots[i].hashCode == hashCode && comparer.Equals(slots[i].key, key)) + { + if(last < 0) + { + buckets[bucket] = slots[i].next + 1; + } + else + { + slots[last].next = slots[i].next; + } + slots[i].hashCode = -1; + slots[i].value = default(T); + slots[i].next = freeList; + freeList = i; + return true; + } + } + return false; + } + + internal override object Find(object[] keyValues) + { + K key; + if(this.keyManager.TryCreateKeyFromValues(keyValues, out key)) + { + T value = default(T); + if(Find(key, ref value, false)) + return value; + } + return null; + } + + internal override object FindLike(object instance) + { + T value = (T)instance; + K key = this.keyManager.CreateKeyFromInstance(value); + if(Find(key, ref value, false)) + return value; + return null; + } + + bool Find(K key, ref T value, bool add) + { + int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; + for(int i = buckets[hashCode % buckets.Length] - 1; i >= 0; i = slots[i].next) + { + if(slots[i].hashCode == hashCode && comparer.Equals(slots[i].key, key)) + { + value = slots[i].value; + return true; + } + } + if(add) + { + int index; + if(freeList >= 0) + { + index = freeList; + freeList = slots[index].next; + } + else + { + if(count == slots.Length) Resize(); + index = count; + count++; + } + int bucket = hashCode % buckets.Length; + slots[index].hashCode = hashCode; + slots[index].key = key; + slots[index].value = value; + slots[index].next = buckets[bucket] - 1; + buckets[bucket] = index + 1; + } + return false; + } + + void Resize() + { + int newSize = checked(count * 2 + 1); + int[] newBuckets = new int[newSize]; + Slot[] newSlots = new Slot[newSize]; + Array.Copy(slots, 0, newSlots, 0, count); + for(int i = 0; i < count; i++) + { + int bucket = newSlots[i].hashCode % newSize; + newSlots[i].next = newBuckets[bucket] - 1; + newBuckets[bucket] = i + 1; + } + buckets = newBuckets; + slots = newSlots; + } + } +} + diff --git a/src/IdentityMangement/IdentityManager.cs b/src/IdentityMangement/IdentityManager.cs new file mode 100644 index 0000000..6632791 --- /dev/null +++ b/src/IdentityMangement/IdentityManager.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Security; +using System.Security.Permissions; +using System.Runtime.CompilerServices; + +namespace System.Data.Linq { + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + + internal abstract class IdentityManager { + internal abstract object InsertLookup(MetaType type, object instance); + internal abstract bool RemoveLike(MetaType type, object instance); + internal abstract object Find(MetaType type, object[] keyValues); + internal abstract object FindLike(MetaType type, object instance); + + internal static IdentityManager CreateIdentityManager(bool asReadOnly) { + if (asReadOnly) { + return new ReadOnlyIdentityManager(); + } + else { + return new StandardIdentityManager(); + } + } + } + + /// + /// This is the noop implementation used when object tracking is disabled. + /// + internal class ReadOnlyIdentityManager : IdentityManager + { + internal ReadOnlyIdentityManager() { } + internal override object InsertLookup(MetaType type, object instance) { return instance; } + internal override bool RemoveLike(MetaType type, object instance) { return false; } + internal override object Find(MetaType type, object[] keyValues) { return null; } + internal override object FindLike(MetaType type, object instance) { return null; } + } +} diff --git a/src/IdentityMangement/KeyManager.cs b/src/IdentityMangement/KeyManager.cs new file mode 100644 index 0000000..021d636 --- /dev/null +++ b/src/IdentityMangement/KeyManager.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Security; +using System.Security.Permissions; +using System.Runtime.CompilerServices; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + + internal abstract class KeyManager + { + internal abstract Type KeyType { get; } + } +} + diff --git a/src/IdentityMangement/KeyManagerOfTK.cs b/src/IdentityMangement/KeyManagerOfTK.cs new file mode 100644 index 0000000..d34f760 --- /dev/null +++ b/src/IdentityMangement/KeyManagerOfTK.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Security; +using System.Security.Permissions; +using System.Runtime.CompilerServices; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + + internal abstract class KeyManager : KeyManager + { + internal abstract K CreateKeyFromInstance(T instance); + internal abstract bool TryCreateKeyFromValues(object[] values, out K k); + internal abstract IEqualityComparer Comparer { get; } + } +} + diff --git a/src/IdentityMangement/MultiKey.cs b/src/IdentityMangement/MultiKey.cs new file mode 100644 index 0000000..afd89fc --- /dev/null +++ b/src/IdentityMangement/MultiKey.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Security; +using System.Security.Permissions; +using System.Runtime.CompilerServices; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + + internal struct MultiKey + { + T1 value1; + T2 value2; + + internal MultiKey(T1 value1, T2 value2) + { + this.value1 = value1; + this.value2 = value2; + } + + internal class Comparer : IEqualityComparer>, IEqualityComparer + { + IEqualityComparer comparer1; + IEqualityComparer comparer2; + + internal Comparer(IEqualityComparer comparer1, IEqualityComparer comparer2) + { + this.comparer1 = comparer1; + this.comparer2 = comparer2; + } + + public bool Equals(MultiKey x, MultiKey y) + { + return this.comparer1.Equals(x.value1, y.value1) && + this.comparer2.Equals(x.value2, y.value2); + } + + public int GetHashCode(MultiKey x) + { + return this.comparer1.GetHashCode(x.value1) ^ this.comparer2.GetHashCode(x.value2); + } + + bool IEqualityComparer.Equals(object x, object y) + { + return this.Equals((MultiKey)x, (MultiKey)y); + } + + int IEqualityComparer.GetHashCode(object x) + { + return this.GetHashCode((MultiKey)x); + } + } + } +} + diff --git a/src/IdentityMangement/MultiKeyManager.cs b/src/IdentityMangement/MultiKeyManager.cs new file mode 100644 index 0000000..7c5a600 --- /dev/null +++ b/src/IdentityMangement/MultiKeyManager.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Security; +using System.Security.Permissions; +using System.Runtime.CompilerServices; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + + internal class MultiKeyManager : KeyManager> + { + MetaAccessor accessor; + int offset; + KeyManager next; + IEqualityComparer> comparer; + + internal MultiKeyManager(MetaAccessor accessor, int offset, KeyManager next) + { + this.accessor = accessor; + this.next = next; + this.offset = offset; + } + + internal override MultiKey CreateKeyFromInstance(T instance) + { + return new MultiKey( + this.accessor.GetValue(instance), + this.next.CreateKeyFromInstance(instance) + ); + } + + internal override bool TryCreateKeyFromValues(object[] values, out MultiKey k) + { + System.Diagnostics.Debug.Assert(this.offset < values.Length, "offset is outside the bounds of the values array"); + + object o = values[this.offset]; + if(o == null && typeof(V1).IsValueType) + { + k = default(MultiKey); + return false; + } + V2 v2; + if(!this.next.TryCreateKeyFromValues(values, out v2)) + { + k = default(MultiKey); + return false; + } + k = new MultiKey((V1)o, v2); + return true; + } + + internal override Type KeyType + { + get { return typeof(MultiKey); } + } + + internal override IEqualityComparer> Comparer + { + get + { + if(this.comparer == null) + { + this.comparer = new MultiKey.Comparer(EqualityComparer.Default, next.Comparer); + } + return this.comparer; + } + } + } +} + diff --git a/src/IdentityMangement/SingleKeyManager.cs b/src/IdentityMangement/SingleKeyManager.cs new file mode 100644 index 0000000..b930ed6 --- /dev/null +++ b/src/IdentityMangement/SingleKeyManager.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Security; +using System.Security.Permissions; +using System.Runtime.CompilerServices; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + + internal class SingleKeyManager : KeyManager + { + bool isKeyNullAssignable; + MetaAccessor accessor; + int offset; + IEqualityComparer comparer; + + internal SingleKeyManager(MetaAccessor accessor, int offset) + { + this.accessor = accessor; + this.offset = offset; + this.isKeyNullAssignable = System.Data.Linq.SqlClient.TypeSystem.IsNullAssignable(typeof(V)); + } + + internal override V CreateKeyFromInstance(T instance) + { + return this.accessor.GetValue(instance); + } + + internal override bool TryCreateKeyFromValues(object[] values, out V v) + { + object o = values[this.offset]; + if(o == null && !this.isKeyNullAssignable) + { + v = default(V); + return false; + } + v = (V)o; + return true; + } + + internal override Type KeyType + { + get { return typeof(V); } + } + + internal override IEqualityComparer Comparer + { + get + { + if(this.comparer == null) + { + this.comparer = EqualityComparer.Default; + } + return this.comparer; + } + } + } +} + diff --git a/src/IdentityMangement/StandardIdentityManager.cs b/src/IdentityMangement/StandardIdentityManager.cs new file mode 100644 index 0000000..5cf6f59 --- /dev/null +++ b/src/IdentityMangement/StandardIdentityManager.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Security; +using System.Security.Permissions; +using System.Runtime.CompilerServices; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + + internal class StandardIdentityManager : IdentityManager + { + Dictionary caches; + IdentityCache currentCache; + MetaType currentType; + + internal StandardIdentityManager() + { + this.caches = new Dictionary(); + } + + internal override object InsertLookup(MetaType type, object instance) + { + this.SetCurrent(type); + return this.currentCache.InsertLookup(instance); + } + + internal override bool RemoveLike(MetaType type, object instance) + { + this.SetCurrent(type); + return this.currentCache.RemoveLike(instance); + } + + internal override object Find(MetaType type, object[] keyValues) + { + this.SetCurrent(type); + return this.currentCache.Find(keyValues); + } + + internal override object FindLike(MetaType type, object instance) + { + this.SetCurrent(type); + return this.currentCache.FindLike(instance); + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + private void SetCurrent(MetaType type) + { + type = type.InheritanceRoot; + if(this.currentType != type) + { + if(!this.caches.TryGetValue(type, out this.currentCache)) + { + KeyManager km = GetKeyManager(type); + this.currentCache = (IdentityCache)Activator.CreateInstance( + typeof(IdentityCache<,>).MakeGenericType(type.Type, km.KeyType), + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, + new object[] { km }, null + ); + this.caches.Add(type, this.currentCache); + } + this.currentType = type; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + static KeyManager GetKeyManager(MetaType type) + { + int n = type.IdentityMembers.Count; + MetaDataMember mm = type.IdentityMembers[0]; + + KeyManager km = (KeyManager)Activator.CreateInstance( + typeof(SingleKeyManager<,>).MakeGenericType(type.Type, mm.Type), + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, + new object[] { mm.StorageAccessor, 0 }, null + ); + for(int i = 1; i < n; i++) + { + mm = type.IdentityMembers[i]; + km = (KeyManager) + Activator.CreateInstance( + typeof(MultiKeyManager<,,>).MakeGenericType(type.Type, mm.Type, km.KeyType), + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, + new object[] { mm.StorageAccessor, i, km }, null + ); + } + + return km; + } + } +} + diff --git a/src/Interfaces/ICompiledQuery.cs b/src/Interfaces/ICompiledQuery.cs new file mode 100644 index 0000000..9a2fecf --- /dev/null +++ b/src/Interfaces/ICompiledQuery.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.Data.Linq; +using System.Data.Common; +using System.Linq.Expressions; +using System.IO; +using System.Linq; +using System.Text; +using System.Transactions; +using System.Reflection; +using System.Diagnostics.CodeAnalysis; + +namespace System.Data.Linq +{ + using System.Data.Linq.Provider; + + /// + /// A compiled query. + /// + internal interface ICompiledQuery + { + /// + /// Executes the compiled query using the specified provider and a set of arguments. + /// + /// The provider that will execute the compiled query. + /// Argument values to supply to the parameters of the compiled query, + /// when the query is specified as a LambdaExpression. + /// + IExecuteResult Execute(IProvider provider, object[] arguments); + } +} + diff --git a/src/Provider/IDataServices.cs b/src/Interfaces/IDataServices.cs similarity index 74% rename from src/Provider/IDataServices.cs rename to src/Interfaces/IDataServices.cs index 04072f4..a8406a4 100644 --- a/src/Provider/IDataServices.cs +++ b/src/Interfaces/IDataServices.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Text; -namespace System.Data.Linq.Provider { +namespace System.Data.Linq { using System.Data.Linq.Mapping; internal interface IDataServices { @@ -17,9 +17,4 @@ internal interface IDataServices { object InsertLookupCachedObject(MetaType type, object instance); void OnEntityMaterialized(MetaType type, object instance); } - - internal interface IDeferredSourceFactory { - IEnumerable CreateDeferredSource(object instance); - IEnumerable CreateDeferredSource(object[] keyValues); - } -} +} \ No newline at end of file diff --git a/src/Interfaces/IDeferredSourceFactory.cs b/src/Interfaces/IDeferredSourceFactory.cs new file mode 100644 index 0000000..6ab48f6 --- /dev/null +++ b/src/Interfaces/IDeferredSourceFactory.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Linq; +using System.Text; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + + internal interface IDeferredSourceFactory + { + IEnumerable CreateDeferredSource(object instance); + IEnumerable CreateDeferredSource(object[] keyValues); + } +} + diff --git a/src/Interfaces/IExecuteResult.cs b/src/Interfaces/IExecuteResult.cs new file mode 100644 index 0000000..b4c4371 --- /dev/null +++ b/src/Interfaces/IExecuteResult.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace System.Data.Linq +{ + /// + /// The result of executing a query. + /// + public interface IExecuteResult : IDisposable + { + /// + /// The return value or result of the executed query. This object has the same type as the + /// query expression's Type property. + /// + object ReturnValue { get; } + + /// + /// Retrieves the nth output parameter. This method is normally used when the query is a mapped + /// function with output parameters. + /// + /// + /// + object GetParameterValue(int parameterIndex); + } +} + diff --git a/src/Interfaces/IFunctionResult.cs b/src/Interfaces/IFunctionResult.cs new file mode 100644 index 0000000..3b5ba31 --- /dev/null +++ b/src/Interfaces/IFunctionResult.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace System.Data.Linq +{ + /// + /// Interface providing access to a function return value. + /// + public interface IFunctionResult + { + /// + /// The value. + /// + object ReturnValue { get; } + } +} + diff --git a/src/Interfaces/IMultipleResults.cs b/src/Interfaces/IMultipleResults.cs new file mode 100644 index 0000000..9e8bb37 --- /dev/null +++ b/src/Interfaces/IMultipleResults.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace System.Data.Linq { + + /// + /// An interface for representing results of mapped functions or queries with variable return sequences. + /// + public interface IMultipleResults : IFunctionResult, IDisposable { + /// + /// Retrieves the next result as a sequence of Type 'TElement'. + /// + /// + /// + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "[....]: Generic parameters are required for strong-typing of the return type.")] + IEnumerable GetResult(); + } +} diff --git a/src/Interfaces/ISingleResult.cs b/src/Interfaces/ISingleResult.cs new file mode 100644 index 0000000..a1a1067 --- /dev/null +++ b/src/Interfaces/ISingleResult.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace System.Data.Linq +{ + /// + /// An interface for representing the result of a mapped function with a single return sequence. + /// + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "[....]: Meant to represent a database table which is delayed loaded and doesn't provide collection semantics.")] + public interface ISingleResult : IEnumerable, IFunctionResult, IDisposable { } +} + diff --git a/src/Interfaces/ITable.cs b/src/Interfaces/ITable.cs new file mode 100644 index 0000000..bbb3498 --- /dev/null +++ b/src/Interfaces/ITable.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Configuration; +using System.Data; +using System.Data.Common; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Transactions; +using System.Xml; +using System.Runtime.CompilerServices; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + using System.Diagnostics.CodeAnalysis; + using System.Data.Linq.BindingLists; + + /// + /// ITable is the common interface for DataContext tables. It can be used as the source + /// of a dynamic/runtime-generated query. + /// + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "[....]: Meant to represent a database table which is delayed loaded and doesn't provide collection semantics.")] + public interface ITable : IQueryable + { + /// + /// The DataContext containing this Table. + /// + DataContext Context { get; } + /// + /// Adds an entity in a 'pending insert' state to this table. The added entity will not be observed + /// in query results from this table until after SubmitChanges has been called. Any untracked + /// objects referenced directly or transitively by the entity will also be inserted. + /// + /// + void InsertOnSubmit(object entity); + /// + /// Adds all entities of a collection to the DataContext in a 'pending insert' state. + /// The added entities will not be observed in query results until after SubmitChanges() + /// has been called. Any untracked objects referenced directly or transitively by the + /// the inserted entities will also be inserted. + /// + /// + void InsertAllOnSubmit(IEnumerable entities); + /// + /// Attaches an entity to the DataContext in an unmodified state, similiar to as if it had been + /// retrieved via a query. Other entities accessible from this entity are attached as unmodified + /// but may subsequently be transitioned to other states by performing table operations on them + /// individually. + /// + /// + void Attach(object entity); + /// + /// Attaches an entity to the DataContext in either a modified or unmodified state. + /// If attaching as modified, the entity must either declare a version member or must + /// not participate in update conflict checking. Other entities accessible from this + /// entity are attached as unmodified but may subsequently be transitioned to other + /// states by performing table operations on them individually. + /// + /// + /// + void Attach(object entity, bool asModified); + /// + /// Attaches an entity to the DataContext in either a modified or unmodified state by specifying both the entity + /// and its original state. Other entities accessible from this + /// entity are attached as unmodified but may subsequently be transitioned to other + /// states by performing table operations on them individually. + /// + /// The entity to attach. + /// An instance of the same entity type with data members containing + /// the original values. + void Attach(object entity, object original); + /// + /// Attaches all entities of a collection to the DataContext in an unmodified state, + /// similiar to as if each had been retrieved via a query. Other entities accessible from these + /// entities are attached as unmodified but may subsequently be transitioned to other + /// states by performing table operations on them individually. + /// + /// + void AttachAll(IEnumerable entities); + /// + /// Attaches all entities of a collection to the DataContext in either a modified or unmodified state. + /// If attaching as modified, the entity must either declare a version member or must not participate in update conflict checking. + /// Other entities accessible from these + /// entities are attached as unmodified but may subsequently be transitioned to other + /// states by performing table operations on them individually. + /// + /// The collection of entities. + /// True if the entities are to be attach as modified. + void AttachAll(IEnumerable entities, bool asModified); + /// + /// Puts an entity from this table into a 'pending delete' state. The removed entity will not be observed + /// missing from query results until after SubmitChanges() has been called. + /// + /// The entity to remove. + void DeleteOnSubmit(object entity); + /// + /// Puts all entities from the collection 'entities' into a 'pending delete' state. The removed entities will + /// not be observed missing from the query results until after SubmitChanges() is called. + /// + /// + void DeleteAllOnSubmit(IEnumerable entities); + /// + /// Returns an instance containing the original state of the entity. + /// + /// + /// + object GetOriginalEntityState(object entity); + /// + /// Returns an array of modified members containing their current and original values + /// for the entity specified. + /// + /// + /// + ModifiedMemberInfo[] GetModifiedMembers(object entity); + /// + /// True if the table is read-only. + /// + bool IsReadOnly { get; } + } +} + diff --git a/src/Interfaces/ITableOfT.cs b/src/Interfaces/ITableOfT.cs new file mode 100644 index 0000000..9f91b45 --- /dev/null +++ b/src/Interfaces/ITableOfT.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Configuration; +using System.Data; +using System.Data.Common; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Transactions; +using System.Xml; +using System.Runtime.CompilerServices; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + using System.Diagnostics.CodeAnalysis; + using System.Data.Linq.BindingLists; + + /// + /// Defines behavior for implementations of IQueryable that allow modifications to the membership of the resulting set. + /// + /// Type of entities returned from the queryable. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] + public interface ITable : IQueryable + where TEntity : class + { + /// + /// Notify the set that an object representing a new entity should be added to the set. + /// Depending on the implementation, the change to the set may not be visible in an enumeration of the set + /// until changes to that set have been persisted in some manner. + /// + /// Entity object to be added. + void InsertOnSubmit(TEntity entity); + + /// + /// Notify the set that an object representing a new entity should be added to the set. + /// Depending on the implementation, the change to the set may not be visible in an enumeration of the set + /// until changes to that set have been persisted in some manner. + /// + /// Entity object to be attached. + void Attach(TEntity entity); + + /// + /// Notify the set that an object representing an entity should be removed from the set. + /// Depending on the implementation, the change to the set may not be visible in an enumeration of the set + /// until changes to that set have been persisted in some manner. + /// + /// Entity object to be removed. + /// Throws if the specified object is not in the set. + void DeleteOnSubmit(TEntity entity); + } +} + diff --git a/src/Mapping/EntityRef.cs b/src/Mapping/EntityRef.cs new file mode 100644 index 0000000..289e5d8 --- /dev/null +++ b/src/Mapping/EntityRef.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq.Expressions; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Diagnostics; +using System.Runtime.Serialization; +using System.Diagnostics.CodeAnalysis; +using System.Data.Linq.BindingLists; + +namespace System.Data.Linq +{ + [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "[....]: Types are never compared to each other. When comparisons happen it is against the entities that are represented by these constructs.")] + public struct EntityRef + where TEntity : class + { + IEnumerable source; + TEntity entity; + + public EntityRef(TEntity entity) + { + this.entity = entity; + this.source = SourceState.Assigned; + } + + public EntityRef(IEnumerable source) + { + this.source = source; + this.entity = default(TEntity); + } + + public EntityRef(EntityRef entityRef) + { + this.source = entityRef.source; + this.entity = entityRef.entity; + } + + public TEntity Entity + { + get + { + if(this.HasSource) + { + + IEnumerable src = this.source; + this.entity = Enumerable.SingleOrDefault(src); + this.source = SourceState.Loaded; + } + return this.entity; + } + set + { + this.entity = value; + this.source = SourceState.Assigned; + } + } + + public bool HasLoadedOrAssignedValue + { + get { return this.HasLoadedValue || this.HasAssignedValue; } + } + + internal bool HasValue + { + get { return this.source == null || this.HasLoadedValue || this.HasAssignedValue; } + } + + internal bool HasLoadedValue + { + get { return this.source == SourceState.Loaded; } + } + + internal bool HasAssignedValue + { + get { return this.source == SourceState.Assigned; } + } + + internal bool HasSource + { + get { return this.source != null && !this.HasLoadedValue && !this.HasAssignedValue; } + } + + internal IEnumerable Source + { + get { return this.source; } + } + + internal TEntity UnderlyingValue + { + get { return this.entity; } + } + } +} + diff --git a/src/Mapping/EntitySet.cs b/src/Mapping/EntitySet.cs new file mode 100644 index 0000000..c2d4911 --- /dev/null +++ b/src/Mapping/EntitySet.cs @@ -0,0 +1,633 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq.Expressions; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Diagnostics; +using System.Runtime.Serialization; +using System.Diagnostics.CodeAnalysis; +using System.Data.Linq.BindingLists; + +namespace System.Data.Linq +{ + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "[....]: Naming chosen to represent a different concept from a collection because it is delayed loaded.")] + public sealed class EntitySet : IList, IList, IListSource + where TEntity : class + { + IEnumerable source; + ItemList entities; + ItemList removedEntities; + Action onAdd; + Action onRemove; + TEntity onAddEntity; + TEntity onRemoveEntity; + int version; + private ListChangedEventHandler onListChanged; + private bool isModified; + private bool isLoaded; + bool listChanged; + + public EntitySet() + { + } + + public EntitySet(Action onAdd, Action onRemove) + { + this.onAdd = onAdd; + this.onRemove = onRemove; + } + + internal EntitySet(EntitySet es, bool copyNotifications) + { + this.source = es.source; + foreach(TEntity e in es.entities) entities.Add(e); + foreach(TEntity e in es.removedEntities) removedEntities.Add(e); + this.version = es.version; + if(copyNotifications) + { + this.onAdd = es.onAdd; + this.onRemove = es.onRemove; + } + } + + public int Count + { + get + { + Load(); + return entities.Count; + } + } + + public TEntity this[int index] + { + get + { + Load(); + if(index < 0 || index >= entities.Count) + throw Error.ArgumentOutOfRange("index"); + return entities[index]; + } + set + { + Load(); + if(index < 0 || index >= entities.Count) + throw Error.ArgumentOutOfRange("index"); + if(value == null || IndexOf(value) >= 0) + throw Error.ArgumentOutOfRange("value"); + CheckModify(); + TEntity old = entities[index]; + OnRemove(old); + OnListChanged(ListChangedType.ItemDeleted, index); + + OnAdd(value); + entities[index] = value; + OnModified(); + OnListChanged(ListChangedType.ItemAdded, index); + } + } + + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "[....]: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")] + public void Add(TEntity entity) + { + if(entity == null) + { + throw Error.ArgumentNull("entity"); + } + if(entity != onAddEntity) + { + CheckModify(); + if(!entities.Contains(entity)) + { + OnAdd(entity); + if(this.HasSource) removedEntities.Remove(entity); + entities.Add(entity); + OnListChanged(ListChangedType.ItemAdded, entities.IndexOf(entity)); + } + OnModified(); + } + } + + public void AddRange(IEnumerable collection) + { + if(collection == null) + throw Error.ArgumentNull("collection"); + CheckModify(); + // convert to List in case adding elements here removes them from the 'collection' (ie entityset to entityset assignment) + collection = collection.ToList(); + foreach(TEntity e in collection) + { + if(!entities.Contains(e)) + { + OnAdd(e); + if(this.HasSource) removedEntities.Remove(e); + entities.Add(e); + OnListChanged(ListChangedType.ItemAdded, entities.IndexOf(e)); + } + } + OnModified(); + } + + public void Assign(IEnumerable entitySource) + { + // No-op if assigning the same object to itself + if(Object.ReferenceEquals(this, entitySource)) + { + return; + } + + Clear(); + if(entitySource != null) + AddRange(entitySource); + + // When an entity set is assigned, it is considered loaded. + // Since with defer loading enabled, a load is triggered + // anyways, this is only necessary in cases where defer loading + // is disabled. In such cases, the materializer assigns a + // prefetched collection and we want IsLoaded to be true. + this.isLoaded = true; + } + + public void Clear() + { + Load(); + CheckModify(); + if(entities.Items != null) + { + List removeList = new List(entities.Items); + foreach(TEntity e in removeList) + { + Remove(e); + } + } + entities = default(ItemList); + OnModified(); + OnListChanged(ListChangedType.Reset, 0); + } + + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "[....]: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")] + public bool Contains(TEntity entity) + { + return IndexOf(entity) >= 0; + } + + public void CopyTo(TEntity[] array, int arrayIndex) + { + Load(); + if(entities.Count > 0) Array.Copy(entities.Items, 0, array, arrayIndex, entities.Count); + } + + public IEnumerator GetEnumerator() + { + Load(); + return new Enumerator(this); + } + + internal IEnumerable GetUnderlyingValues() + { + return new UnderlyingValues(this); + } + + class UnderlyingValues : IEnumerable + { + EntitySet entitySet; + internal UnderlyingValues(EntitySet entitySet) + { + this.entitySet = entitySet; + } + public IEnumerator GetEnumerator() + { + return new Enumerator(this.entitySet); + } + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + } + + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "[....]: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")] + public int IndexOf(TEntity entity) + { + Load(); + return entities.IndexOf(entity); + } + + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "1#", Justification = "[....]: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")] + public void Insert(int index, TEntity entity) + { + Load(); + if(index < 0 || index > Count) + throw Error.ArgumentOutOfRange("index"); + if(entity == null || IndexOf(entity) >= 0) + throw Error.ArgumentOutOfRange("entity"); + CheckModify(); + entities.Insert(index, entity); + OnListChanged(ListChangedType.ItemAdded, index); + + OnAdd(entity); + } + + /// + /// Returns true if this entity set has a deferred query + /// that hasn't been executed yet. + /// + public bool IsDeferred + { + get { return HasSource; } + } + + /// + /// Returns true if values have been either assigned or loaded. + /// + internal bool HasValues + { + get { return this.source == null || this.HasAssignedValues || this.HasLoadedValues; } + } + + /// + /// Returns true if the entity set has been modified in any way by the user or its items + /// have been loaded from the database. + /// + public bool HasLoadedOrAssignedValues + { + get { return this.HasAssignedValues || this.HasLoadedValues; } + } + + /// + /// Returns true if the set has been modified in any way by the user. + /// + internal bool HasAssignedValues + { + get { return this.isModified; } + } + + /// + /// Returns true if the set has been loaded from the database. + /// + internal bool HasLoadedValues + { + get { return this.isLoaded; } + } + + /// + /// Returns true if the set has a deferred source query that hasn't been loaded yet. + /// + internal bool HasSource + { + get { return this.source != null && !this.HasLoadedValues; } + } + + /// + /// Returns true if the collection has been loaded. + /// + internal bool IsLoaded + { + get + { + return this.isLoaded; + } + } + + internal IEnumerable Source + { + get { return this.source; } + } + + public void Load() + { + if(this.HasSource) + { + ItemList addedEntities = entities; + entities = default(ItemList); + foreach(TEntity e in source) entities.Add(e); + foreach(TEntity e in addedEntities) entities.Include(e); + foreach(TEntity e in removedEntities) entities.Remove(e); + source = SourceState.Loaded; + isLoaded = true; + removedEntities = default(ItemList); + } + } + + private void OnModified() + { + isModified = true; + } + + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "[....]: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")] + public bool Remove(TEntity entity) + { + if(entity == null || entity == onRemoveEntity) return false; + CheckModify(); + int index = -1; + bool removed = false; + if(this.HasSource) + { + if(!removedEntities.Contains(entity)) + { + OnRemove(entity); + // check in entities in case it has been pre-added + index = entities.IndexOf(entity); + if(index != -1) + { + entities.RemoveAt(index); + } + else + { + removedEntities.Add(entity); + } + removed = true; + } + } + else + { + index = entities.IndexOf(entity); + if(index != -1) + { + OnRemove(entity); + entities.RemoveAt(index); + removed = true; + } + } + if(removed) + { + OnModified(); + // If index == -1 here, that means that the entity was not in the list before Remove was called, + // so we shouldn't fire the event since the list itself will not be changed, even though the Remove will still be tracked + // on the removedEntities list in case a subsequent Load brings in this entity. + if(index != -1) + { + OnListChanged(ListChangedType.ItemDeleted, index); + } + } + return removed; + } + + public void RemoveAt(int index) + { + Load(); + if(index < 0 || index >= Count) + { + throw Error.ArgumentOutOfRange("index"); + } + CheckModify(); + TEntity entity = entities[index]; + OnRemove(entity); + entities.RemoveAt(index); + OnModified(); + OnListChanged(ListChangedType.ItemDeleted, index); + } + + public void SetSource(IEnumerable entitySource) + { + if(this.HasAssignedValues || this.HasLoadedValues) + throw Error.EntitySetAlreadyLoaded(); + this.source = entitySource; + } + + void CheckModify() + { + if(onAddEntity != null || onRemoveEntity != null) + throw Error.ModifyDuringAddOrRemove(); + version++; + } + + void OnAdd(TEntity entity) + { + if(onAdd != null) + { + TEntity e = onAddEntity; + onAddEntity = entity; + try + { + onAdd(entity); + } + finally + { + onAddEntity = e; + } + } + } + + void OnRemove(TEntity entity) + { + if(onRemove != null) + { + TEntity e = onRemoveEntity; + onRemoveEntity = entity; + try + { + onRemove(entity); + } + finally + { + onRemoveEntity = e; + } + } + } + + class Enumerable : IEnumerable + { + EntitySet entitySet; + public Enumerable(EntitySet entitySet) + { + this.entitySet = entitySet; + } + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + public IEnumerator GetEnumerator() + { + return new Enumerator(this.entitySet); + } + } + + class Enumerator : IEnumerator + { + EntitySet entitySet; + TEntity[] items; + int index; + int endIndex; + int version; + + public Enumerator(EntitySet entitySet) + { + this.entitySet = entitySet; + this.items = entitySet.entities.Items; + this.index = -1; + this.endIndex = entitySet.entities.Count - 1; + this.version = entitySet.version; + } + + public void Dispose() + { + // Technically, calling GC.SuppressFinalize is not required because the class does not + // have a finalizer, but it does no harm, protects against the case where a finalizer is added + // in the future, and prevents an FxCop warning. + GC.SuppressFinalize(this); + } + + public bool MoveNext() + { + if(version != entitySet.version) + throw Error.EntitySetModifiedDuringEnumeration(); + if(index == endIndex) return false; + index++; + return true; + } + + public TEntity Current + { + get { return items[index]; } + } + + object IEnumerator.Current + { + get { return items[index]; } + } + + void IEnumerator.Reset() + { + if(version != entitySet.version) + throw Error.EntitySetModifiedDuringEnumeration(); + index = -1; + } + } + + int IList.Add(object value) + { + TEntity entity = value as TEntity; + if(entity == null || IndexOf(entity) >= 0) + { + throw Error.ArgumentOutOfRange("value"); + } + CheckModify(); + int i = entities.Count; + entities.Add(entity); + OnAdd(entity); + return i; + } + + bool IList.Contains(object value) + { + return Contains(value as TEntity); + } + + int IList.IndexOf(object value) + { + return IndexOf(value as TEntity); + } + + void IList.Insert(int index, object value) + { + TEntity entity = value as TEntity; + if(value == null) + throw Error.ArgumentOutOfRange("value"); + Insert(index, entity); + } + + bool IList.IsFixedSize + { + get { return false; } + } + + bool IList.IsReadOnly + { + get { return false; } + } + + void IList.Remove(object value) + { + Remove(value as TEntity); + } + + object IList.this[int index] + { + get + { + return this[index]; + } + set + { + TEntity entity = value as TEntity; + if(value == null) throw Error.ArgumentOutOfRange("value"); + this[index] = entity; + } + } + + void ICollection.CopyTo(Array array, int index) + { + Load(); + if(entities.Count > 0) Array.Copy(entities.Items, 0, array, index, entities.Count); + } + + bool ICollection.IsSynchronized + { + get { return false; } + } + + object ICollection.SyncRoot + { + get { return this; } + } + + bool ICollection.IsReadOnly + { + get { return false; } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + void OnListChanged(ListChangedType type, int index) + { + listChanged = true; + if(onListChanged != null) + { + onListChanged(this, new ListChangedEventArgs(type, index)); + } + } + + public event ListChangedEventHandler ListChanged + { + add + { + onListChanged += value; + } + remove + { + onListChanged -= value; + } + } + + bool IListSource.ContainsListCollection + { + get { return true; } + } + + private IBindingList cachedList = null; + + IList IListSource.GetList() + { + if(cachedList == null || listChanged) + { + cachedList = GetNewBindingList(); + listChanged = false; + } + return cachedList; + } + + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Method doesn't represent a property of the type.")] + public IBindingList GetNewBindingList() + { + return new EntitySetBindingList(this.ToList(), this); + } + } +} + diff --git a/src/Mapping/ItemList.cs b/src/Mapping/ItemList.cs new file mode 100644 index 0000000..33f2c3a --- /dev/null +++ b/src/Mapping/ItemList.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq.Expressions; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Diagnostics; +using System.Runtime.Serialization; +using System.Diagnostics.CodeAnalysis; +using System.Data.Linq.BindingLists; + +namespace System.Data.Linq +{ + internal struct ItemList where T : class + { + T[] items; + int count; + + public int Count + { + get { return count; } + } + + public T[] Items + { + get { return items; } + } + + public T this[int index] + { + get { return items[index]; } + set { items[index] = value; } + } + + public void Add(T item) + { + if(items == null || items.Length == count) GrowItems(); + items[count] = item; + count++; + } + + public bool Contains(T item) + { + return IndexOf(item) >= 0; + } + + public Enumerator GetEnumerator() + { + Enumerator e; + e.items = items; + e.index = -1; + e.endIndex = count - 1; + return e; + } + + public bool Include(T item) + { + if(LastIndexOf(item) >= 0) return false; + Add(item); + return true; + } + + public int IndexOf(T item) + { + for(int i = 0; i < count; i++) + { + if(items[i] == item) return i; + } + return -1; + } + + public void Insert(int index, T item) + { + if(items == null || items.Length == count) GrowItems(); + if(index < count) Array.Copy(items, index, items, index + 1, count - index); + items[index] = item; + count++; + } + + public int LastIndexOf(T item) + { + int i = count; + while(i > 0) + { + --i; + if(items[i] == item) return i; + } + return -1; + } + + public bool Remove(T item) + { + int i = IndexOf(item); + if(i < 0) return false; + RemoveAt(i); + return true; + } + + public void RemoveAt(int index) + { + count--; + if(index < count) Array.Copy(items, index + 1, items, index, count - index); + items[count] = default(T); + } + + void GrowItems() + { + Array.Resize(ref items, count == 0 ? 4 : count * 2); + } + + public struct Enumerator + { + internal T[] items; + internal int index; + internal int endIndex; + + public bool MoveNext() + { + if(index == endIndex) return false; + index++; + return true; + } + + public T Current + { + get { return items[index]; } + } + } + } +} + diff --git a/src/Mapping/Link.cs b/src/Mapping/Link.cs new file mode 100644 index 0000000..2379dee --- /dev/null +++ b/src/Mapping/Link.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq.Expressions; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Diagnostics; +using System.Runtime.Serialization; +using System.Diagnostics.CodeAnalysis; +using System.Data.Linq.BindingLists; + +namespace System.Data.Linq +{ + [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "[....]: Types are never compared to each other. When comparisons happen it is against the entities that are represented by these constructs.")] + public struct Link + { + T underlyingValue; + IEnumerable source; + + public Link(T value) + { + this.underlyingValue = value; + this.source = null; + } + + public Link(IEnumerable source) + { + this.source = source; + this.underlyingValue = default(T); + } + + public Link(Link link) + { + this.underlyingValue = link.underlyingValue; + this.source = link.source; + } + + public bool HasValue + { + get { return this.source == null || this.HasLoadedValue || this.HasAssignedValue; } + } + + public bool HasLoadedOrAssignedValue + { + get { return this.HasLoadedValue || this.HasAssignedValue; } + } + + internal bool HasLoadedValue + { + get { return this.source == SourceState.Loaded; } + } + + internal bool HasAssignedValue + { + get { return this.source == SourceState.Assigned; } + } + + internal T UnderlyingValue + { + get { return this.underlyingValue; } + } + + internal IEnumerable Source + { + get { return this.source; } + } + + internal bool HasSource + { + get { return this.source != null && !this.HasAssignedValue && !this.HasLoadedValue; } + } + + public T Value + { + get + { + if(this.HasSource) + { + this.underlyingValue = Enumerable.SingleOrDefault(this.source); + this.source = SourceState.Loaded; + } + return this.underlyingValue; + } + set + { + this.underlyingValue = value; + this.source = SourceState.Assigned; + } + } + } +} + diff --git a/src/Mapping/SourceState.cs b/src/Mapping/SourceState.cs new file mode 100644 index 0000000..85c9ced --- /dev/null +++ b/src/Mapping/SourceState.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq.Expressions; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Diagnostics; +using System.Runtime.Serialization; +using System.Diagnostics.CodeAnalysis; +using System.Data.Linq.BindingLists; + +namespace System.Data.Linq { + internal static class SourceState { + internal static readonly IEnumerable Loaded = (IEnumerable)new T[] { }; + internal static readonly IEnumerable Assigned = (IEnumerable)new T[] { }; + } +} diff --git a/src/Mapping/Table.cs b/src/Mapping/Table.cs new file mode 100644 index 0000000..5145d89 --- /dev/null +++ b/src/Mapping/Table.cs @@ -0,0 +1,695 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Configuration; +using System.Data; +using System.Data.Common; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Transactions; +using System.Xml; +using System.Runtime.CompilerServices; + +namespace System.Data.Linq +{ + using System.Data.Linq.Mapping; + using System.Data.Linq.Provider; + using System.Diagnostics.CodeAnalysis; + using System.Data.Linq.BindingLists; + + /// + /// Table is a collection of persistent entities. It always contains the set of entities currently + /// persisted in the database. Use it as a source of queries and to add/insert and remove/delete entities. + /// + /// + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "[....]: Meant to represent a database table which is delayed loaded and doesn't provide collection semantics.")] + public sealed class Table : IQueryable, IQueryProvider, IEnumerable, IQueryable, IEnumerable, ITable, IListSource, ITable + where TEntity : class + { + DataContext context; + MetaTable metaTable; + + internal Table(DataContext context, MetaTable metaTable) + { + System.Diagnostics.Debug.Assert(metaTable != null); + this.context = context; + this.metaTable = metaTable; + } + + /// + /// The DataContext containing this Table. + /// + public DataContext Context + { + get { return this.context; } + } + + /// + /// True if the table is read-only. + /// + public bool IsReadOnly + { + get { return !metaTable.RowType.IsEntity; } + } + + Expression IQueryable.Expression + { + get { return Expression.Constant(this); } + } + + Type IQueryable.ElementType + { + get { return typeof(TEntity); } + } + + IQueryProvider IQueryable.Provider + { + get + { + return (IQueryProvider)this; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + IQueryable IQueryProvider.CreateQuery(Expression expression) + { + if(expression == null) + { + throw Error.ArgumentNull("expression"); + } + Type eType = System.Data.Linq.SqlClient.TypeSystem.GetElementType(expression.Type); + Type qType = typeof(IQueryable<>).MakeGenericType(eType); + if(!qType.IsAssignableFrom(expression.Type)) + { + throw Error.ExpectedQueryableArgument("expression", qType); + } + Type dqType = typeof(DataQuery<>).MakeGenericType(eType); + return (IQueryable)Activator.CreateInstance(dqType, new object[] { this.context, expression }); + } + + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "[....]: Generic parameters are required for strong-typing of the return type.")] + IQueryable IQueryProvider.CreateQuery(Expression expression) + { + if(expression == null) + { + throw Error.ArgumentNull("expression"); + } + if(!typeof(IQueryable).IsAssignableFrom(expression.Type)) + { + throw Error.ExpectedQueryableArgument("expression", typeof(IEnumerable)); + } + return new DataQuery(this.context, expression); + } + + object IQueryProvider.Execute(Expression expression) + { + return this.context.Provider.Execute(expression).ReturnValue; + } + + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "[....]: Generic parameters are required for strong-typing of the return type.")] + TResult IQueryProvider.Execute(Expression expression) + { + return (TResult)this.context.Provider.Execute(expression).ReturnValue; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return ((IEnumerable)this.context.Provider.Execute(Expression.Constant(this)).ReturnValue).GetEnumerator(); + } + + bool IListSource.ContainsListCollection + { + get { return false; } + } + + private IBindingList cachedList; + + IList IListSource.GetList() + { + if(cachedList == null) + { + cachedList = GetNewBindingList(); + } + return cachedList; + } + + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Method doesn't represent a property of the type.")] + public IBindingList GetNewBindingList() + { + return BindingList.Create(this.context, this); + } + + /// + /// Adds an entity in a 'pending insert' state to this table. The added entity will not be observed + /// in query results from this table until after SubmitChanges() has been called. Any untracked + /// objects referenced directly or transitively by the entity will also be inserted. + /// + /// + public void InsertOnSubmit(TEntity entity) + { + if(entity == null) + { + throw Error.ArgumentNull("entity"); + } + CheckReadOnly(); + context.CheckNotInSubmitChanges(); + context.VerifyTrackingEnabled(); + MetaType type = this.metaTable.RowType.GetInheritanceType(entity.GetType()); + if(!IsTrackableType(type)) + { + throw Error.TypeCouldNotBeAdded(type.Type); + } + TrackedObject tracked = this.context.Services.ChangeTracker.GetTrackedObject(entity); + if(tracked == null) + { + tracked = this.context.Services.ChangeTracker.Track(entity); + tracked.ConvertToNew(); + } + else if(tracked.IsWeaklyTracked) + { + tracked.ConvertToNew(); + } + else if(tracked.IsDeleted) + { + tracked.ConvertToPossiblyModified(); + } + else if(tracked.IsRemoved) + { + tracked.ConvertToNew(); + } + else if(!tracked.IsNew) + { + throw Error.CantAddAlreadyExistingItem(); + } + } + + void ITable.InsertOnSubmit(object entity) + { + if(entity == null) + { + throw Error.ArgumentNull("entity"); + } + TEntity tEntity = entity as TEntity; + if(tEntity == null) + { + throw Error.EntityIsTheWrongType(); + } + this.InsertOnSubmit(tEntity); + } + + /// + /// Adds all entities of a collection to the DataContext in a 'pending insert' state. + /// The added entities will not be observed in query results until after SubmitChanges() + /// has been called. + /// + /// + public void InsertAllOnSubmit(IEnumerable entities) where TSubEntity : TEntity + { + if(entities == null) + { + throw Error.ArgumentNull("entities"); + } + CheckReadOnly(); + context.CheckNotInSubmitChanges(); + context.VerifyTrackingEnabled(); + List list = entities.ToList(); + foreach(TEntity entity in list) + { + this.InsertOnSubmit(entity); + } + } + + void ITable.InsertAllOnSubmit(IEnumerable entities) + { + if(entities == null) + { + throw Error.ArgumentNull("entities"); + } + CheckReadOnly(); + context.CheckNotInSubmitChanges(); + context.VerifyTrackingEnabled(); + List list = entities.Cast().ToList(); + ITable itable = this; + foreach(object entity in list) + { + itable.InsertOnSubmit(entity); + } + } + + /// + /// Returns true if this specific type is mapped into the database. + /// For example, an abstract type can't be present because it can not be instantiated. + /// + private static bool IsTrackableType(MetaType type) + { + if(type == null) + { + return false; + } + if(!type.CanInstantiate) + { + return false; + } + if(type.HasInheritance && !type.HasInheritanceCode) + { + return false; + } + return true; + } + + /// + /// Puts an entity from this table into a 'pending delete' state. The removed entity will not be observed + /// missing from query results until after SubmitChanges() has been called. + /// + /// + public void DeleteOnSubmit(TEntity entity) + { + if(entity == null) + { + throw Error.ArgumentNull("entity"); + } + CheckReadOnly(); + context.CheckNotInSubmitChanges(); + context.VerifyTrackingEnabled(); + TrackedObject tracked = this.context.Services.ChangeTracker.GetTrackedObject(entity); + if(tracked != null) + { + if(tracked.IsNew) + { + tracked.ConvertToRemoved(); + } + else if(tracked.IsPossiblyModified || tracked.IsModified) + { + tracked.ConvertToDeleted(); + } + } + else + { + throw Error.CannotRemoveUnattachedEntity(); + } + } + + void ITable.DeleteOnSubmit(object entity) + { + if(entity == null) + { + throw Error.ArgumentNull("entity"); + } + TEntity tEntity = entity as TEntity; + if(tEntity == null) + { + throw Error.EntityIsTheWrongType(); + } + this.DeleteOnSubmit(tEntity); + } + + /// + /// Puts all entities from the collection 'entities' into a 'pending delete' state. The removed entities will + /// not be observed missing from the query results until after SubmitChanges() is called. + /// + /// + public void DeleteAllOnSubmit(IEnumerable entities) where TSubEntity : TEntity + { + if(entities == null) + { + throw Error.ArgumentNull("entities"); + } + CheckReadOnly(); + context.CheckNotInSubmitChanges(); + context.VerifyTrackingEnabled(); + List list = entities.ToList(); + foreach(TEntity entity in list) + { + this.DeleteOnSubmit(entity); + } + } + + void ITable.DeleteAllOnSubmit(IEnumerable entities) + { + if(entities == null) + { + throw Error.ArgumentNull("entities"); + } + CheckReadOnly(); + context.CheckNotInSubmitChanges(); + context.VerifyTrackingEnabled(); + List list = entities.Cast().ToList(); + ITable itable = this; + foreach(object entity in list) + { + itable.DeleteOnSubmit(entity); + } + } + + /// + /// Attaches an entity to the DataContext in an unmodified state, similiar to as if it had been + /// retrieved via a query. Deferred loading is not enabled. Other entities accessible from this + /// entity are not automatically attached. + /// + /// + public void Attach(TEntity entity) + { + if(entity == null) + { + throw Error.ArgumentNull("entity"); + } + this.Attach(entity, false); + } + + void ITable.Attach(object entity) + { + if(entity == null) + { + throw Error.ArgumentNull("entity"); + } + TEntity tEntity = entity as TEntity; + if(tEntity == null) + { + throw Error.EntityIsTheWrongType(); + } + this.Attach(tEntity, false); + } + + /// + /// Attaches an entity to the DataContext in either a modified or unmodified state. + /// If attaching as modified, the entity must either declare a version member or must not participate in update conflict checking. + /// Deferred loading is not enabled. Other entities accessible from this entity are not automatically attached. + /// + /// + /// + public void Attach(TEntity entity, bool asModified) + { + if(entity == null) + { + throw Error.ArgumentNull("entity"); + } + CheckReadOnly(); + context.CheckNotInSubmitChanges(); + context.VerifyTrackingEnabled(); + MetaType type = this.metaTable.RowType.GetInheritanceType(entity.GetType()); + if(!IsTrackableType(type)) + { + throw Error.TypeCouldNotBeTracked(type.Type); + } + if(asModified) + { + bool canAttach = type.VersionMember != null || !type.HasUpdateCheck; + if(!canAttach) + { + throw Error.CannotAttachAsModifiedWithoutOriginalState(); + } + } + TrackedObject tracked = this.Context.Services.ChangeTracker.GetTrackedObject(entity); + if(tracked == null || tracked.IsWeaklyTracked) + { + if(tracked == null) + { + tracked = this.context.Services.ChangeTracker.Track(entity, true); + } + if(asModified) + { + tracked.ConvertToModified(); + } + else + { + tracked.ConvertToUnmodified(); + } + if(this.Context.Services.InsertLookupCachedObject(type, entity) != entity) + { + throw new DuplicateKeyException(entity, Strings.CantAddAlreadyExistingKey); + } + tracked.InitializeDeferredLoaders(); + } + else + { + throw Error.CannotAttachAlreadyExistingEntity(); + } + } + + void ITable.Attach(object entity, bool asModified) + { + if(entity == null) + { + throw Error.ArgumentNull("entity"); + } + TEntity tEntity = entity as TEntity; + if(tEntity == null) + { + throw Error.EntityIsTheWrongType(); + } + this.Attach(tEntity, asModified); + } + + /// + /// Attaches an entity to the DataContext in either a modified or unmodified state by specifying both the entity + /// and its original state. + /// + /// The entity to attach. + /// An instance of the same entity type with data members containing + /// the original values. + public void Attach(TEntity entity, TEntity original) + { + if(entity == null) + { + throw Error.ArgumentNull("entity"); + } + if(original == null) + { + throw Error.ArgumentNull("original"); + } + if(entity.GetType() != original.GetType()) + { + throw Error.OriginalEntityIsWrongType(); + } + CheckReadOnly(); + context.CheckNotInSubmitChanges(); + context.VerifyTrackingEnabled(); + MetaType type = this.metaTable.RowType.GetInheritanceType(entity.GetType()); + if(!IsTrackableType(type)) + { + throw Error.TypeCouldNotBeTracked(type.Type); + } + TrackedObject tracked = this.context.Services.ChangeTracker.GetTrackedObject(entity); + if(tracked == null || tracked.IsWeaklyTracked) + { + if(tracked == null) + { + tracked = this.context.Services.ChangeTracker.Track(entity, true); + } + tracked.ConvertToPossiblyModified(original); + if(this.Context.Services.InsertLookupCachedObject(type, entity) != entity) + { + throw new DuplicateKeyException(entity, Strings.CantAddAlreadyExistingKey); + } + tracked.InitializeDeferredLoaders(); + } + else + { + throw Error.CannotAttachAlreadyExistingEntity(); + } + } + + void ITable.Attach(object entity, object original) + { + if(entity == null) + { + throw Error.ArgumentNull("entity"); + } + if(original == null) + { + throw Error.ArgumentNull("original"); + } + CheckReadOnly(); + context.CheckNotInSubmitChanges(); + context.VerifyTrackingEnabled(); + TEntity tEntity = entity as TEntity; + if(tEntity == null) + { + throw Error.EntityIsTheWrongType(); + } + if(entity.GetType() != original.GetType()) + { + throw Error.OriginalEntityIsWrongType(); + } + this.Attach(tEntity, (TEntity)original); + } + + /// + /// Attaches all entities of a collection to the DataContext in an unmodified state, + /// similiar to as if each had been retrieved via a query. Deferred loading is not enabled. + /// Other entities accessible from these entities are not automatically attached. + /// + /// + public void AttachAll(IEnumerable entities) where TSubEntity : TEntity + { + if(entities == null) + { + throw Error.ArgumentNull("entities"); + } + this.AttachAll(entities, false); + } + + void ITable.AttachAll(IEnumerable entities) + { + if(entities == null) + { + throw Error.ArgumentNull("entities"); + } + ((ITable)this).AttachAll(entities, false); + } + + /// + /// Attaches all entities of a collection to the DataContext in either a modified or unmodified state. + /// If attaching as modified, the entity must either declare a version member or must not participate in update conflict checking. + /// Deferred loading is not enabled. Other entities accessible from these entities are not automatically attached. + /// + /// The collection of entities. + /// True if the entities are to be attach as modified. + public void AttachAll(IEnumerable entities, bool asModified) where TSubEntity : TEntity + { + if(entities == null) + { + throw Error.ArgumentNull("entities"); + } + CheckReadOnly(); + context.CheckNotInSubmitChanges(); + context.VerifyTrackingEnabled(); + List list = entities.ToList(); + foreach(TEntity entity in list) + { + this.Attach(entity, asModified); + } + } + + void ITable.AttachAll(IEnumerable entities, bool asModified) + { + if(entities == null) + { + throw Error.ArgumentNull("entities"); + } + CheckReadOnly(); + context.CheckNotInSubmitChanges(); + context.VerifyTrackingEnabled(); + List list = entities.Cast().ToList(); + ITable itable = this; + foreach(object entity in list) + { + itable.Attach(entity, asModified); + } + } + + /// + /// Returns an instance containing the original state of the entity. + /// + /// + /// + public TEntity GetOriginalEntityState(TEntity entity) + { + if(entity == null) + { + throw Error.ArgumentNull("entity"); + } + MetaType type = this.Context.Mapping.GetMetaType(entity.GetType()); + if(type == null || !type.IsEntity) + { + throw Error.EntityIsTheWrongType(); + } + TrackedObject tracked = this.Context.Services.ChangeTracker.GetTrackedObject(entity); + if(tracked != null) + { + if(tracked.Original != null) + { + return (TEntity)tracked.CreateDataCopy(tracked.Original); + } + else + { + return (TEntity)tracked.CreateDataCopy(tracked.Current); + } + } + return null; + } + + object ITable.GetOriginalEntityState(object entity) + { + if(entity == null) + { + throw Error.ArgumentNull("entity"); + } + TEntity tEntity = entity as TEntity; + if(tEntity == null) + { + throw Error.EntityIsTheWrongType(); + } + return this.GetOriginalEntityState(tEntity); + } + + /// + /// Returns an array of modified members containing their current and original values + /// for the entity specified. + /// + /// + /// + public ModifiedMemberInfo[] GetModifiedMembers(TEntity entity) + { + if(entity == null) + { + throw Error.ArgumentNull("entity"); + } + MetaType type = this.Context.Mapping.GetMetaType(entity.GetType()); + if(type == null || !type.IsEntity) + { + throw Error.EntityIsTheWrongType(); + } + TrackedObject tracked = this.Context.Services.ChangeTracker.GetTrackedObject(entity); + if(tracked != null) + { + return tracked.GetModifiedMembers().ToArray(); + } + return new ModifiedMemberInfo[] { }; + } + + ModifiedMemberInfo[] ITable.GetModifiedMembers(object entity) + { + if(entity == null) + { + throw Error.ArgumentNull("entity"); + } + TEntity tEntity = entity as TEntity; + if(tEntity == null) + { + throw Error.EntityIsTheWrongType(); + } + return this.GetModifiedMembers(tEntity); + } + + private void CheckReadOnly() + { + if(this.IsReadOnly) + { + throw Error.CannotPerformCUDOnReadOnlyTable(ToString()); + } + } + + public override string ToString() + { + return "Table(" + typeof(TEntity).Name + ")"; + } + } +} + diff --git a/src/DbConvert.cs b/src/Miscellaneous/DbConvert.cs similarity index 97% rename from src/DbConvert.cs rename to src/Miscellaneous/DbConvert.cs index 03af1ad..7548639 100644 --- a/src/DbConvert.cs +++ b/src/Miscellaneous/DbConvert.cs @@ -12,6 +12,7 @@ namespace System.Data.Linq { +#warning [FB] REFACTOR FOR #9 public static class DBConvert { private static Type[] StringArg = new Type[] { typeof(string) }; @@ -26,7 +27,8 @@ public static object ChangeType(object value, Type type) { if (value == null) return null; MethodInfo mi; - Type toType = System.Data.Linq.SqlClient.TypeSystem.GetNonNullableType(type); +#warning [FB] REFACTOR: TYPE OBTAINED FROM SQLCLIENT. WILL CAUSE CONFLICTS WHEN OTHER DBs ARE ADDED. + Type toType = System.Data.Linq.SqlClient.TypeSystem.GetNonNullableType(type); Type fromType = value.GetType(); if (toType.IsAssignableFrom(fromType)) return value; diff --git a/src/misc/SecurityUtils.cs b/src/Miscellaneous/SecurityUtils.cs similarity index 100% rename from src/misc/SecurityUtils.cs rename to src/Miscellaneous/SecurityUtils.cs diff --git a/src/Provider/IProvider.cs b/src/Provider/IProvider.cs index 56eccaa..1b3e3f9 100644 --- a/src/Provider/IProvider.cs +++ b/src/Provider/IProvider.cs @@ -115,124 +115,4 @@ internal interface IProvider : IDisposable { /// DbCommand GetCommand(Expression query); } - - /// - /// A compiled query. - /// - internal interface ICompiledQuery { - /// - /// Executes the compiled query using the specified provider and a set of arguments. - /// - /// The provider that will execute the compiled query. - /// Argument values to supply to the parameters of the compiled query, - /// when the query is specified as a LambdaExpression. - /// - IExecuteResult Execute(IProvider provider, object[] arguments); - } - - internal static class DataManipulation { - /// - /// The method signature used to encode an Insert command. - /// The method will throw a NotImplementedException if called directly. - /// - /// - /// - /// - /// - /// - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "item", Justification = "[....]: The method is being used to represent a method signature")] - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "resultSelector", Justification = "[....]: The method is being used to represent a method signature")] - public static TResult Insert(TEntity item, Func resultSelector) { - throw new NotImplementedException(); - } - /// - /// The method signature used to encode an Insert command. - /// The method will throw a NotImplementedException if called directly. - /// - /// - /// - /// - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "item", Justification = "[....]: The method is being used to represent a method signature")] - public static int Insert(TEntity item) { - throw new NotImplementedException(); - } - /// - /// The method signature used to encode an Update command. - /// The method will throw a NotImplementedException if called directly. - /// - /// - /// - /// - /// - /// - /// - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "item", Justification = "[....]: The method is being used to represent a method signature")] - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "check", Justification = "[....]: The method is being used to represent a method signature")] - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "resultSelector", Justification = "[....]: The method is being used to represent a method signature")] - public static TResult Update(TEntity item, Func check, Func resultSelector) { - throw new NotImplementedException(); - } - /// - /// The method signature used to encode an Update command. - /// The method will throw a NotImplementedException if called directly. - /// - /// - /// - /// - /// - /// - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "item", Justification = "[....]: The method is being used to represent a method signature")] - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "resultSelector", Justification = "[....]: The method is being used to represent a method signature")] - public static TResult Update(TEntity item, Func resultSelector) { - throw new NotImplementedException(); - } - /// - /// The method signature used to encode an Update command. - /// The method will throw a NotImplementedException if called directly. - /// - /// - /// - /// - /// - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "item", Justification = "[....]: The method is being used to represent a method signature")] - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "check", Justification = "[....]: The method is being used to represent a method signature")] - public static int Update(TEntity item, Func check) { - throw new NotImplementedException(); - } - /// - /// The method signature used to encode an Update command. - /// The method will throw a NotImplementedException if called directly. - /// - /// - /// - /// - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "item", Justification = "[....]: The method is being used to represent a method signature")] - public static int Update(TEntity item) { - throw new NotImplementedException(); - } - /// - /// The method signature used to encode a Delete command. - /// The method will throw a NotImplementedException if called directly. - /// - /// - /// - /// - /// - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "item", Justification = "[....]: The method is being used to represent a method signature")] - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "check", Justification = "[....]: The method is being used to represent a method signature")] - public static int Delete(TEntity item, Func check) { - throw new NotImplementedException(); - } - /// - /// The method signature used to encode a Delete command. - /// The method will throw a NotImplementedException if called directly. - /// - /// - /// - /// - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "item", Justification = "[....]: The method is being used to represent a method signature")] - public static int Delete(TEntity item) { - throw new NotImplementedException(); - } - } -} +} \ No newline at end of file diff --git a/src/CompiledQuery.cs b/src/Querying/CompiledQuery.cs similarity index 100% rename from src/CompiledQuery.cs rename to src/Querying/CompiledQuery.cs diff --git a/src/DataContext.cs b/src/Querying/DataContext.cs similarity index 53% rename from src/DataContext.cs rename to src/Querying/DataContext.cs index 5d7fb47..ae7d6a0 100644 --- a/src/DataContext.cs +++ b/src/Querying/DataContext.cs @@ -20,43 +20,8 @@ namespace System.Data.Linq { using System.Data.Linq.Mapping; using System.Data.Linq.Provider; using System.Diagnostics.CodeAnalysis; + using System.Data.Linq.BindingLists; - /// - /// Used to specify how a submit should behave when one - /// or more updates fail due to optimistic concurrency - /// conflicts. - /// - public enum ConflictMode { - /// - /// Fail immediately when the first change conflict is encountered. - /// - FailOnFirstConflict, - /// - /// Only fail after all changes have been attempted. - /// - ContinueOnConflict - } - - /// - /// Used to specify a value synchronization strategy. - /// - public enum RefreshMode { - /// - /// Keep the current values. - /// - KeepCurrentValues, - /// - /// Current values that have been changed are not modified, but - /// any unchanged values are updated with the current database - /// values. No changes are lost in this merge. - /// - KeepChanges, - /// - /// All current values are overwritten with current database values, - /// regardless of whether they have been changed. - /// - OverwriteCurrentValues - } /// /// The DataContext is the source of all entities mapped over a database connection. @@ -960,748 +925,4 @@ public ChangeConflictCollection ChangeConflicts { } } } - - /// - /// Defines behavior for implementations of IQueryable that allow modifications to the membership of the resulting set. - /// - /// Type of entities returned from the queryable. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] - public interface ITable : IQueryable - where TEntity : class - { - /// - /// Notify the set that an object representing a new entity should be added to the set. - /// Depending on the implementation, the change to the set may not be visible in an enumeration of the set - /// until changes to that set have been persisted in some manner. - /// - /// Entity object to be added. - void InsertOnSubmit(TEntity entity); - - /// - /// Notify the set that an object representing a new entity should be added to the set. - /// Depending on the implementation, the change to the set may not be visible in an enumeration of the set - /// until changes to that set have been persisted in some manner. - /// - /// Entity object to be attached. - void Attach(TEntity entity); - - /// - /// Notify the set that an object representing an entity should be removed from the set. - /// Depending on the implementation, the change to the set may not be visible in an enumeration of the set - /// until changes to that set have been persisted in some manner. - /// - /// Entity object to be removed. - /// Throws if the specified object is not in the set. - void DeleteOnSubmit(TEntity entity); - } - - /// - /// ITable is the common interface for DataContext tables. It can be used as the source - /// of a dynamic/runtime-generated query. - /// - [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification="[....]: Meant to represent a database table which is delayed loaded and doesn't provide collection semantics.")] - public interface ITable : IQueryable { - /// - /// The DataContext containing this Table. - /// - DataContext Context { get; } - /// - /// Adds an entity in a 'pending insert' state to this table. The added entity will not be observed - /// in query results from this table until after SubmitChanges has been called. Any untracked - /// objects referenced directly or transitively by the entity will also be inserted. - /// - /// - void InsertOnSubmit(object entity); - /// - /// Adds all entities of a collection to the DataContext in a 'pending insert' state. - /// The added entities will not be observed in query results until after SubmitChanges() - /// has been called. Any untracked objects referenced directly or transitively by the - /// the inserted entities will also be inserted. - /// - /// - void InsertAllOnSubmit(IEnumerable entities); - /// - /// Attaches an entity to the DataContext in an unmodified state, similiar to as if it had been - /// retrieved via a query. Other entities accessible from this entity are attached as unmodified - /// but may subsequently be transitioned to other states by performing table operations on them - /// individually. - /// - /// - void Attach(object entity); - /// - /// Attaches an entity to the DataContext in either a modified or unmodified state. - /// If attaching as modified, the entity must either declare a version member or must - /// not participate in update conflict checking. Other entities accessible from this - /// entity are attached as unmodified but may subsequently be transitioned to other - /// states by performing table operations on them individually. - /// - /// - /// - void Attach(object entity, bool asModified); - /// - /// Attaches an entity to the DataContext in either a modified or unmodified state by specifying both the entity - /// and its original state. Other entities accessible from this - /// entity are attached as unmodified but may subsequently be transitioned to other - /// states by performing table operations on them individually. - /// - /// The entity to attach. - /// An instance of the same entity type with data members containing - /// the original values. - void Attach(object entity, object original); - /// - /// Attaches all entities of a collection to the DataContext in an unmodified state, - /// similiar to as if each had been retrieved via a query. Other entities accessible from these - /// entities are attached as unmodified but may subsequently be transitioned to other - /// states by performing table operations on them individually. - /// - /// - void AttachAll(IEnumerable entities); - /// - /// Attaches all entities of a collection to the DataContext in either a modified or unmodified state. - /// If attaching as modified, the entity must either declare a version member or must not participate in update conflict checking. - /// Other entities accessible from these - /// entities are attached as unmodified but may subsequently be transitioned to other - /// states by performing table operations on them individually. - /// - /// The collection of entities. - /// True if the entities are to be attach as modified. - void AttachAll(IEnumerable entities, bool asModified); - /// - /// Puts an entity from this table into a 'pending delete' state. The removed entity will not be observed - /// missing from query results until after SubmitChanges() has been called. - /// - /// The entity to remove. - void DeleteOnSubmit(object entity); - /// - /// Puts all entities from the collection 'entities' into a 'pending delete' state. The removed entities will - /// not be observed missing from the query results until after SubmitChanges() is called. - /// - /// - void DeleteAllOnSubmit(IEnumerable entities); - /// - /// Returns an instance containing the original state of the entity. - /// - /// - /// - object GetOriginalEntityState(object entity); - /// - /// Returns an array of modified members containing their current and original values - /// for the entity specified. - /// - /// - /// - ModifiedMemberInfo[] GetModifiedMembers(object entity); - /// - /// True if the table is read-only. - /// - bool IsReadOnly { get; } - } - - /// - /// Table is a collection of persistent entities. It always contains the set of entities currently - /// persisted in the database. Use it as a source of queries and to add/insert and remove/delete entities. - /// - /// - [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification="[....]: Meant to represent a database table which is delayed loaded and doesn't provide collection semantics.")] - public sealed class Table : IQueryable, IQueryProvider, IEnumerable, IQueryable, IEnumerable, ITable, IListSource, ITable - where TEntity : class { - DataContext context; - MetaTable metaTable; - - internal Table(DataContext context, MetaTable metaTable) { - System.Diagnostics.Debug.Assert(metaTable != null); - this.context = context; - this.metaTable = metaTable; - } - - /// - /// The DataContext containing this Table. - /// - public DataContext Context { - get { return this.context; } - } - - /// - /// True if the table is read-only. - /// - public bool IsReadOnly { - get { return !metaTable.RowType.IsEntity; } - } - - Expression IQueryable.Expression { - get { return Expression.Constant(this); } - } - - Type IQueryable.ElementType { - get { return typeof(TEntity); } - } - - IQueryProvider IQueryable.Provider{ - get{ - return (IQueryProvider)this; - } - } - - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] - IQueryable IQueryProvider.CreateQuery(Expression expression) { - if (expression == null) { - throw Error.ArgumentNull("expression"); - } - Type eType = System.Data.Linq.SqlClient.TypeSystem.GetElementType(expression.Type); - Type qType = typeof(IQueryable<>).MakeGenericType(eType); - if (!qType.IsAssignableFrom(expression.Type)) { - throw Error.ExpectedQueryableArgument("expression", qType); - } - Type dqType = typeof(DataQuery<>).MakeGenericType(eType); - return (IQueryable)Activator.CreateInstance(dqType, new object[] { this.context, expression }); - } - - [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "[....]: Generic parameters are required for strong-typing of the return type.")] - IQueryable IQueryProvider.CreateQuery(Expression expression) { - if (expression == null) { - throw Error.ArgumentNull("expression"); - } - if (!typeof(IQueryable).IsAssignableFrom(expression.Type)) { - throw Error.ExpectedQueryableArgument("expression", typeof(IEnumerable)); - } - return new DataQuery(this.context, expression); - } - - object IQueryProvider.Execute(Expression expression) { - return this.context.Provider.Execute(expression).ReturnValue; - } - - [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "[....]: Generic parameters are required for strong-typing of the return type.")] - TResult IQueryProvider.Execute(Expression expression) { - return (TResult)this.context.Provider.Execute(expression).ReturnValue; - } - - IEnumerator IEnumerable.GetEnumerator() { - return this.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() { - return this.GetEnumerator(); - } - - public IEnumerator GetEnumerator() { - return ((IEnumerable)this.context.Provider.Execute(Expression.Constant(this)).ReturnValue).GetEnumerator(); - } - - bool IListSource.ContainsListCollection { - get { return false; } - } - - private IBindingList cachedList; - - IList IListSource.GetList() { - if (cachedList == null) { - cachedList = GetNewBindingList(); - } - return cachedList; - } - - [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification="Method doesn't represent a property of the type.")] - public IBindingList GetNewBindingList() { - return BindingList.Create(this.context, this); - } - - /// - /// Adds an entity in a 'pending insert' state to this table. The added entity will not be observed - /// in query results from this table until after SubmitChanges() has been called. Any untracked - /// objects referenced directly or transitively by the entity will also be inserted. - /// - /// - public void InsertOnSubmit(TEntity entity) { - if (entity == null) { - throw Error.ArgumentNull("entity"); - } - CheckReadOnly(); - context.CheckNotInSubmitChanges(); - context.VerifyTrackingEnabled(); - MetaType type = this.metaTable.RowType.GetInheritanceType(entity.GetType()); - if (!IsTrackableType(type)) { - throw Error.TypeCouldNotBeAdded(type.Type); - } - TrackedObject tracked = this.context.Services.ChangeTracker.GetTrackedObject(entity); - if (tracked == null) { - tracked = this.context.Services.ChangeTracker.Track(entity); - tracked.ConvertToNew(); - } else if (tracked.IsWeaklyTracked) { - tracked.ConvertToNew(); - } else if (tracked.IsDeleted) { - tracked.ConvertToPossiblyModified(); - } else if (tracked.IsRemoved) { - tracked.ConvertToNew(); - } else if (!tracked.IsNew) { - throw Error.CantAddAlreadyExistingItem(); - } - } - - void ITable.InsertOnSubmit(object entity) { - if (entity == null) { - throw Error.ArgumentNull("entity"); - } - TEntity tEntity = entity as TEntity; - if (tEntity == null) { - throw Error.EntityIsTheWrongType(); - } - this.InsertOnSubmit(tEntity); - } - - /// - /// Adds all entities of a collection to the DataContext in a 'pending insert' state. - /// The added entities will not be observed in query results until after SubmitChanges() - /// has been called. - /// - /// - public void InsertAllOnSubmit(IEnumerable entities) where TSubEntity : TEntity { - if (entities == null) { - throw Error.ArgumentNull("entities"); - } - CheckReadOnly(); - context.CheckNotInSubmitChanges(); - context.VerifyTrackingEnabled(); - List list = entities.ToList(); - foreach (TEntity entity in list) { - this.InsertOnSubmit(entity); - } - } - - void ITable.InsertAllOnSubmit(IEnumerable entities) { - if (entities == null) { - throw Error.ArgumentNull("entities"); - } - CheckReadOnly(); - context.CheckNotInSubmitChanges(); - context.VerifyTrackingEnabled(); - List list = entities.Cast().ToList(); - ITable itable = this; - foreach (object entity in list) { - itable.InsertOnSubmit(entity); - } - } - - /// - /// Returns true if this specific type is mapped into the database. - /// For example, an abstract type can't be present because it can not be instantiated. - /// - private static bool IsTrackableType(MetaType type) { - if (type == null) { - return false; - } - if (!type.CanInstantiate) { - return false; - } - if (type.HasInheritance && !type.HasInheritanceCode) { - return false; - } - return true; - } - - /// - /// Puts an entity from this table into a 'pending delete' state. The removed entity will not be observed - /// missing from query results until after SubmitChanges() has been called. - /// - /// - public void DeleteOnSubmit(TEntity entity) { - if (entity == null) { - throw Error.ArgumentNull("entity"); - } - CheckReadOnly(); - context.CheckNotInSubmitChanges(); - context.VerifyTrackingEnabled(); - TrackedObject tracked = this.context.Services.ChangeTracker.GetTrackedObject(entity); - if (tracked != null) { - if (tracked.IsNew) { - tracked.ConvertToRemoved(); - } - else if (tracked.IsPossiblyModified || tracked.IsModified) { - tracked.ConvertToDeleted(); - } - } - else { - throw Error.CannotRemoveUnattachedEntity(); - } - } - - void ITable.DeleteOnSubmit(object entity) { - if (entity == null) { - throw Error.ArgumentNull("entity"); - } - TEntity tEntity = entity as TEntity; - if (tEntity == null) { - throw Error.EntityIsTheWrongType(); - } - this.DeleteOnSubmit(tEntity); - } - - /// - /// Puts all entities from the collection 'entities' into a 'pending delete' state. The removed entities will - /// not be observed missing from the query results until after SubmitChanges() is called. - /// - /// - public void DeleteAllOnSubmit(IEnumerable entities) where TSubEntity : TEntity { - if (entities == null) { - throw Error.ArgumentNull("entities"); - } - CheckReadOnly(); - context.CheckNotInSubmitChanges(); - context.VerifyTrackingEnabled(); - List list = entities.ToList(); - foreach (TEntity entity in list) { - this.DeleteOnSubmit(entity); - } - } - - void ITable.DeleteAllOnSubmit(IEnumerable entities) { - if (entities == null) { - throw Error.ArgumentNull("entities"); - } - CheckReadOnly(); - context.CheckNotInSubmitChanges(); - context.VerifyTrackingEnabled(); - List list = entities.Cast().ToList(); - ITable itable = this; - foreach (object entity in list) { - itable.DeleteOnSubmit(entity); - } - } - - /// - /// Attaches an entity to the DataContext in an unmodified state, similiar to as if it had been - /// retrieved via a query. Deferred loading is not enabled. Other entities accessible from this - /// entity are not automatically attached. - /// - /// - public void Attach(TEntity entity) { - if (entity == null) { - throw Error.ArgumentNull("entity"); - } - this.Attach(entity, false); - } - - void ITable.Attach(object entity) { - if (entity == null) { - throw Error.ArgumentNull("entity"); - } - TEntity tEntity = entity as TEntity; - if (tEntity == null) { - throw Error.EntityIsTheWrongType(); - } - this.Attach(tEntity, false); - } - - /// - /// Attaches an entity to the DataContext in either a modified or unmodified state. - /// If attaching as modified, the entity must either declare a version member or must not participate in update conflict checking. - /// Deferred loading is not enabled. Other entities accessible from this entity are not automatically attached. - /// - /// - /// - public void Attach(TEntity entity, bool asModified) { - if (entity == null) { - throw Error.ArgumentNull("entity"); - } - CheckReadOnly(); - context.CheckNotInSubmitChanges(); - context.VerifyTrackingEnabled(); - MetaType type = this.metaTable.RowType.GetInheritanceType(entity.GetType()); - if (!IsTrackableType(type)) { - throw Error.TypeCouldNotBeTracked(type.Type); - } - if (asModified) { - bool canAttach = type.VersionMember != null || !type.HasUpdateCheck; - if (!canAttach) { - throw Error.CannotAttachAsModifiedWithoutOriginalState(); - } - } - TrackedObject tracked = this.Context.Services.ChangeTracker.GetTrackedObject(entity); - if (tracked == null || tracked.IsWeaklyTracked) { - if (tracked == null) { - tracked = this.context.Services.ChangeTracker.Track(entity, true); - } - if (asModified) { - tracked.ConvertToModified(); - } else { - tracked.ConvertToUnmodified(); - } - if (this.Context.Services.InsertLookupCachedObject(type, entity) != entity) { - throw new DuplicateKeyException(entity, Strings.CantAddAlreadyExistingKey); - } - tracked.InitializeDeferredLoaders(); - } - else { - throw Error.CannotAttachAlreadyExistingEntity(); - } - } - - void ITable.Attach(object entity, bool asModified) { - if (entity == null) { - throw Error.ArgumentNull("entity"); - } - TEntity tEntity = entity as TEntity; - if (tEntity == null) { - throw Error.EntityIsTheWrongType(); - } - this.Attach(tEntity, asModified); - } - - /// - /// Attaches an entity to the DataContext in either a modified or unmodified state by specifying both the entity - /// and its original state. - /// - /// The entity to attach. - /// An instance of the same entity type with data members containing - /// the original values. - public void Attach(TEntity entity, TEntity original) { - if (entity == null) { - throw Error.ArgumentNull("entity"); - } - if (original == null) { - throw Error.ArgumentNull("original"); - } - if (entity.GetType() != original.GetType()) { - throw Error.OriginalEntityIsWrongType(); - } - CheckReadOnly(); - context.CheckNotInSubmitChanges(); - context.VerifyTrackingEnabled(); - MetaType type = this.metaTable.RowType.GetInheritanceType(entity.GetType()); - if (!IsTrackableType(type)) { - throw Error.TypeCouldNotBeTracked(type.Type); - } - TrackedObject tracked = this.context.Services.ChangeTracker.GetTrackedObject(entity); - if (tracked == null || tracked.IsWeaklyTracked) { - if (tracked == null) { - tracked = this.context.Services.ChangeTracker.Track(entity, true); - } - tracked.ConvertToPossiblyModified(original); - if (this.Context.Services.InsertLookupCachedObject(type, entity) != entity) { - throw new DuplicateKeyException(entity, Strings.CantAddAlreadyExistingKey); - } - tracked.InitializeDeferredLoaders(); - } - else { - throw Error.CannotAttachAlreadyExistingEntity(); - } - } - - void ITable.Attach(object entity, object original) { - if (entity == null) { - throw Error.ArgumentNull("entity"); - } - if (original == null) { - throw Error.ArgumentNull("original"); - } - CheckReadOnly(); - context.CheckNotInSubmitChanges(); - context.VerifyTrackingEnabled(); - TEntity tEntity = entity as TEntity; - if (tEntity == null) { - throw Error.EntityIsTheWrongType(); - } - if (entity.GetType() != original.GetType()) { - throw Error.OriginalEntityIsWrongType(); - } - this.Attach(tEntity, (TEntity)original); - } - - /// - /// Attaches all entities of a collection to the DataContext in an unmodified state, - /// similiar to as if each had been retrieved via a query. Deferred loading is not enabled. - /// Other entities accessible from these entities are not automatically attached. - /// - /// - public void AttachAll(IEnumerable entities) where TSubEntity : TEntity { - if (entities == null) { - throw Error.ArgumentNull("entities"); - } - this.AttachAll(entities, false); - } - - void ITable.AttachAll(IEnumerable entities) { - if (entities == null) { - throw Error.ArgumentNull("entities"); - } - ((ITable)this).AttachAll(entities, false); - } - - /// - /// Attaches all entities of a collection to the DataContext in either a modified or unmodified state. - /// If attaching as modified, the entity must either declare a version member or must not participate in update conflict checking. - /// Deferred loading is not enabled. Other entities accessible from these entities are not automatically attached. - /// - /// The collection of entities. - /// True if the entities are to be attach as modified. - public void AttachAll(IEnumerable entities, bool asModified) where TSubEntity : TEntity { - if (entities == null) { - throw Error.ArgumentNull("entities"); - } - CheckReadOnly(); - context.CheckNotInSubmitChanges(); - context.VerifyTrackingEnabled(); - List list = entities.ToList(); - foreach (TEntity entity in list) { - this.Attach(entity, asModified); - } - } - - void ITable.AttachAll(IEnumerable entities, bool asModified) { - if (entities == null) { - throw Error.ArgumentNull("entities"); - } - CheckReadOnly(); - context.CheckNotInSubmitChanges(); - context.VerifyTrackingEnabled(); - List list = entities.Cast().ToList(); - ITable itable = this; - foreach (object entity in list) { - itable.Attach(entity, asModified); - } - } - - /// - /// Returns an instance containing the original state of the entity. - /// - /// - /// - public TEntity GetOriginalEntityState(TEntity entity) { - if (entity == null) { - throw Error.ArgumentNull("entity"); - } - MetaType type = this.Context.Mapping.GetMetaType(entity.GetType()); - if (type == null || !type.IsEntity) { - throw Error.EntityIsTheWrongType(); - } - TrackedObject tracked = this.Context.Services.ChangeTracker.GetTrackedObject(entity); - if (tracked != null) { - if (tracked.Original != null) { - return (TEntity) tracked.CreateDataCopy(tracked.Original); - } - else { - return (TEntity) tracked.CreateDataCopy(tracked.Current); - } - } - return null; - } - - object ITable.GetOriginalEntityState(object entity) { - if (entity == null) { - throw Error.ArgumentNull("entity"); - } - TEntity tEntity = entity as TEntity; - if (tEntity == null) { - throw Error.EntityIsTheWrongType(); - } - return this.GetOriginalEntityState(tEntity); - } - - /// - /// Returns an array of modified members containing their current and original values - /// for the entity specified. - /// - /// - /// - public ModifiedMemberInfo[] GetModifiedMembers(TEntity entity) { - if (entity == null) { - throw Error.ArgumentNull("entity"); - } - MetaType type = this.Context.Mapping.GetMetaType(entity.GetType()); - if (type == null || !type.IsEntity) { - throw Error.EntityIsTheWrongType(); - } - TrackedObject tracked = this.Context.Services.ChangeTracker.GetTrackedObject(entity); - if (tracked != null) { - return tracked.GetModifiedMembers().ToArray(); - } - return new ModifiedMemberInfo[] { }; - } - - ModifiedMemberInfo[] ITable.GetModifiedMembers(object entity) { - if (entity == null) { - throw Error.ArgumentNull("entity"); - } - TEntity tEntity = entity as TEntity; - if (tEntity == null) { - throw Error.EntityIsTheWrongType(); - } - return this.GetModifiedMembers(tEntity); - } - - private void CheckReadOnly() { - if (this.IsReadOnly) { - throw Error.CannotPerformCUDOnReadOnlyTable(ToString()); - } - } - - public override string ToString() { - return "Table(" + typeof(TEntity).Name + ")"; - } - } - - [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "ChangeSet", Justification="The capitalization was deliberately chosen.")] - public sealed class ChangeSet { - ReadOnlyCollection inserts; - ReadOnlyCollection deletes; - ReadOnlyCollection updates; - - internal ChangeSet( - ReadOnlyCollection inserts, - ReadOnlyCollection deletes, - ReadOnlyCollection updates - ) { - this.inserts = inserts; - this.deletes = deletes; - this.updates = updates; - } - - public IList Inserts { - get { return this.inserts; } - } - - public IList Deletes { - get { return this.deletes; } - } - - public IList Updates { - get { return this.updates; } - } - - public override string ToString() { - return "{" + - string.Format( - Globalization.CultureInfo.InvariantCulture, - "Inserts: {0}, Deletes: {1}, Updates: {2}", - this.Inserts.Count, - this.Deletes.Count, - this.Updates.Count - ) + "}"; - } - } - - [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "[....]: Types are never compared to each other. When comparisons happen it is against the entities that are represented by these constructs.")] - public struct ModifiedMemberInfo { - MemberInfo member; - object current; - object original; - - internal ModifiedMemberInfo(MemberInfo member, object current, object original) { - this.member = member; - this.current = current; - this.original = original; - } - - public MemberInfo Member { - get { return this.member; } - } - - public object CurrentValue { - get { return this.current; } - } - - public object OriginalValue { - get { return this.original; } - } - } } diff --git a/src/DataShape.cs b/src/Querying/DataLoadOptions.cs similarity index 93% rename from src/DataShape.cs rename to src/Querying/DataLoadOptions.cs index e46bb6e..e5517be 100644 --- a/src/DataShape.cs +++ b/src/Querying/DataLoadOptions.cs @@ -14,7 +14,61 @@ sealed public class DataLoadOptions { Dictionary includes = new Dictionary(); Dictionary subqueries = new Dictionary(); - /// + #region Private classes + private static class Searcher { + static internal MemberInfo MemberInfoOf(LambdaExpression lambda) { + Visitor v = new Visitor(); + v.VisitLambda(lambda); + return v.MemberInfo; + } +#warning [FB] REFACTOR: VISITOR OBTAINED FROM SQLCLIENT. WILL CAUSE CONFLICTS WHEN OTHER DBs ARE ADDED. + private class Visitor : System.Data.Linq.SqlClient.ExpressionVisitor { + internal MemberInfo MemberInfo; + internal override Expression VisitMemberAccess(MemberExpression m) { + this.MemberInfo = m.Member; + return base.VisitMemberAccess(m); + } + + internal override Expression VisitMethodCall(MethodCallExpression m) { + this.Visit(m.Object); + foreach (Expression arg in m.Arguments) { + this.Visit(arg); + break; // Only follow the extension method 'this' + } + return m; + } + + } + } + + + /// + /// Ensure that the subquery follows the rules for subqueries. + /// +#warning [FB] REFACTOR: VISITOR OBTAINED FROM SQLCLIENT. WILL CAUSE CONFLICTS WHEN OTHER DBs ARE ADDED. + private class SubqueryValidator : System.Data.Linq.SqlClient.ExpressionVisitor + { + bool isTopLevel = true; + internal override Expression VisitMethodCall(MethodCallExpression m) + { + bool was = isTopLevel; + try + { + if(isTopLevel && !SubqueryRules.IsSupportedTopLevelMethod(m.Method)) + throw Error.SubqueryDoesNotSupportOperator(m.Method.Name); + isTopLevel = false; + return base.VisitMethodCall(m); + } + finally + { + isTopLevel = was; + } + } + } + #endregion + + + /// /// Describe a property that is automatically loaded when the containing instance is loaded /// [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "[....]: Generic types are an important part of Linq APIs and they could not exist without nested generic support.")] @@ -180,30 +234,6 @@ private static MemberInfo GetLoadWithMemberInfo(LambdaExpression lambda) } } - private static class Searcher { - static internal MemberInfo MemberInfoOf(LambdaExpression lambda) { - Visitor v = new Visitor(); - v.VisitLambda(lambda); - return v.MemberInfo; - } - private class Visitor : System.Data.Linq.SqlClient.ExpressionVisitor { - internal MemberInfo MemberInfo; - internal override Expression VisitMemberAccess(MemberExpression m) { - this.MemberInfo = m.Member; - return base.VisitMemberAccess(m); - } - - internal override Expression VisitMethodCall(MethodCallExpression m) { - this.Visit(m.Object); - foreach (Expression arg in m.Arguments) { - this.Visit(arg); - break; // Only follow the extension method 'this' - } - return m; - } - - } - } private void ValidateTypeGraphAcyclic() { IEnumerable edges = this.includes.Values; @@ -262,25 +292,6 @@ private static void ValidateSubqueryExpression(LambdaExpression subquery) { new SubqueryValidator().VisitLambda(subquery); } - /// - /// Ensure that the subquery follows the rules for subqueries. - /// - private class SubqueryValidator : System.Data.Linq.SqlClient.ExpressionVisitor { - bool isTopLevel = true; - internal override Expression VisitMethodCall(MethodCallExpression m) { - bool was = isTopLevel; - try { - if (isTopLevel && !SubqueryRules.IsSupportedTopLevelMethod(m.Method)) - throw Error.SubqueryDoesNotSupportOperator(m.Method.Name); - isTopLevel = false; - return base.VisitMethodCall(m); - } - finally { - isTopLevel = was; - } - } - } - /// /// Whether there have been LoadOptions specified. /// diff --git a/src/DataQuery.cs b/src/Querying/DataQuery.cs similarity index 98% rename from src/DataQuery.cs rename to src/Querying/DataQuery.cs index 704d09d..f78a813 100644 --- a/src/DataQuery.cs +++ b/src/Querying/DataQuery.cs @@ -11,6 +11,7 @@ namespace System.Data.Linq { using System.Data.Linq.Mapping; using System.Data.Linq.Provider; + using System.Data.Linq.BindingLists; internal sealed class DataQuery : IOrderedQueryable, IQueryProvider, IEnumerable, IOrderedQueryable, IEnumerable, IListSource { DataContext context; diff --git a/src/DataServices.cs b/src/Querying/DataServices.cs similarity index 97% rename from src/DataServices.cs rename to src/Querying/DataServices.cs index 9a1cc8b..9e8ba4a 100644 --- a/src/DataServices.cs +++ b/src/Querying/DataServices.cs @@ -21,7 +21,173 @@ internal class CommonDataServices : IDataServices { bool hasCachedObjects; Dictionary factoryMap; - internal CommonDataServices(DataContext context, MetaModel model) { + #region Private classes + private class DeferredSourceFactory : IDeferredSourceFactory { + MetaDataMember member; + CommonDataServices services; + ICompiledQuery query; + bool refersToPrimaryKey; + T[] empty; + + #region Private classes + /// + /// [FB] Nested class in a nested class... InceptionDeferredSource? ;) + /// Left here as it's just a glorified enumerator. + /// + private class DeferredSource : IEnumerable, IEnumerable { + DeferredSourceFactory factory; + object instance; + + internal DeferredSource(DeferredSourceFactory factory, object instance) { + this.factory = factory; + this.instance = instance; + } + + public IEnumerator GetEnumerator() { + object[] keyValues = this.instance as object[]; + if (keyValues != null) { + return this.factory.ExecuteKeys(keyValues); + } + return this.factory.Execute(this.instance); + } + + IEnumerator IEnumerable.GetEnumerator() { + return this.GetEnumerator(); + } + } + #endregion + + + internal DeferredSourceFactory(MetaDataMember member, CommonDataServices services) { + this.member = member; + this.services = services; + this.refersToPrimaryKey = this.member.IsAssociation && this.member.Association.OtherKeyIsPrimaryKey; + this.empty = new T[] { }; + } + + public IEnumerable CreateDeferredSource(object instance) { + if (instance == null) + throw Error.ArgumentNull("instance"); + return new DeferredSource(this, instance); + } + + public IEnumerable CreateDeferredSource(object[] keyValues) { + if (keyValues == null) + throw Error.ArgumentNull("keyValues"); + return new DeferredSource(this, keyValues); + } + + private IEnumerator Execute(object instance) { + ReadOnlyCollection keys = null; + if (this.member.IsAssociation) { + keys = this.member.Association.ThisKey; + } + else { + keys = this.member.DeclaringType.IdentityMembers; + } + object[] keyValues = new object[keys.Count]; + for (int i = 0, n = keys.Count; i < n; i++) { + object value = keys[i].StorageAccessor.GetBoxedValue(instance); + keyValues[i] = value; + } + + if (this.HasNullForeignKey(keyValues)) { + return ((IEnumerable)this.empty).GetEnumerator(); + } + + T cached; + if (this.TryGetCachedObject(keyValues, out cached)) { + return ((IEnumerable)(new T[] { cached })).GetEnumerator(); + } + + if (this.member.LoadMethod != null) { + try { + object result = this.member.LoadMethod.Invoke(this.services.Context, new object[] { instance }); + if (typeof(T).IsAssignableFrom(this.member.LoadMethod.ReturnType)) { + return ((IEnumerable)new T[] { (T)result }).GetEnumerator(); + } + else { + return ((IEnumerable)result).GetEnumerator(); + } + } + catch (TargetInvocationException tie) { + if (tie.InnerException != null) { + throw tie.InnerException; + } + throw; + } + } + else { + return this.ExecuteKeyQuery(keyValues); + } + } + + private IEnumerator ExecuteKeys(object[] keyValues) { + if (this.HasNullForeignKey(keyValues)) { + return ((IEnumerable)this.empty).GetEnumerator(); + } + + T cached; + if (this.TryGetCachedObject(keyValues, out cached)) { + return ((IEnumerable)(new T[] { cached })).GetEnumerator(); + } + + return this.ExecuteKeyQuery(keyValues); + } + + private bool HasNullForeignKey(object[] keyValues) { + if (this.refersToPrimaryKey) { + bool keyHasNull = false; + for (int i = 0, n = keyValues.Length; i < n; i++) { + keyHasNull |= keyValues[i] == null; + } + if (keyHasNull) { + return true; + } + } + return false; + } + + private bool TryGetCachedObject(object[] keyValues, out T cached) { + cached = default(T); + if (this.refersToPrimaryKey) { + // look to see if we already have this object in the identity cache + MetaType mt = this.member.IsAssociation ? this.member.Association.OtherType : this.member.DeclaringType; + object obj = this.services.GetCachedObject(mt, keyValues); + if (obj != null) { + cached = (T)obj; + return true; + } + } + return false; + } + + private IEnumerator ExecuteKeyQuery(object[] keyValues) { + if (this.query == null) { + ParameterExpression p = Expression.Parameter(typeof(object[]), "keys"); + Expression[] keyExprs = new Expression[keyValues.Length]; + ReadOnlyCollection members = this.member.IsAssociation ? this.member.Association.OtherKey : this.member.DeclaringType.IdentityMembers; + for (int i = 0, n = keyValues.Length; i < n; i++) { + MetaDataMember mm = members[i]; + keyExprs[i] = Expression.Convert( +#pragma warning disable 618 // Disable the 'obsolete' warning + Expression.ArrayIndex(p, Expression.Constant(i)), +#pragma warning restore 618 + mm.Type + ); + } + Expression q = this.services.GetDataMemberQuery(this.member, keyExprs); + LambdaExpression lambda = Expression.Lambda(q, p); + this.query = this.services.Context.Provider.Compile(lambda); + } + return ((IEnumerable)this.query.Execute(this.services.Context.Provider, new object[] { keyValues }).ReturnValue).GetEnumerator(); + } + + } + #endregion + + + internal CommonDataServices(DataContext context, MetaModel model) { this.context = context; this.metaModel = model; bool asReadOnly = !context.ObjectTrackingEnabled; @@ -298,161 +464,6 @@ public IDeferredSourceFactory GetDeferredSourceFactory(MetaDataMember member) { return factory; } - class DeferredSourceFactory : IDeferredSourceFactory { - MetaDataMember member; - CommonDataServices services; - ICompiledQuery query; - bool refersToPrimaryKey; - T[] empty; - - internal DeferredSourceFactory(MetaDataMember member, CommonDataServices services) { - this.member = member; - this.services = services; - this.refersToPrimaryKey = this.member.IsAssociation && this.member.Association.OtherKeyIsPrimaryKey; - this.empty = new T[] { }; - } - - public IEnumerable CreateDeferredSource(object instance) { - if (instance == null) - throw Error.ArgumentNull("instance"); - return new DeferredSource(this, instance); - } - - public IEnumerable CreateDeferredSource(object[] keyValues) { - if (keyValues == null) - throw Error.ArgumentNull("keyValues"); - return new DeferredSource(this, keyValues); - } - - private IEnumerator Execute(object instance) { - ReadOnlyCollection keys = null; - if (this.member.IsAssociation) { - keys = this.member.Association.ThisKey; - } - else { - keys = this.member.DeclaringType.IdentityMembers; - } - object[] keyValues = new object[keys.Count]; - for (int i = 0, n = keys.Count; i < n; i++) { - object value = keys[i].StorageAccessor.GetBoxedValue(instance); - keyValues[i] = value; - } - - if (this.HasNullForeignKey(keyValues)) { - return ((IEnumerable)this.empty).GetEnumerator(); - } - - T cached; - if (this.TryGetCachedObject(keyValues, out cached)) { - return ((IEnumerable)(new T[] { cached })).GetEnumerator(); - } - - if (this.member.LoadMethod != null) { - try { - object result = this.member.LoadMethod.Invoke(this.services.Context, new object[] { instance }); - if (typeof(T).IsAssignableFrom(this.member.LoadMethod.ReturnType)) { - return ((IEnumerable)new T[] { (T)result }).GetEnumerator(); - } - else { - return ((IEnumerable)result).GetEnumerator(); - } - } - catch (TargetInvocationException tie) { - if (tie.InnerException != null) { - throw tie.InnerException; - } - throw; - } - } - else { - return this.ExecuteKeyQuery(keyValues); - } - } - - private IEnumerator ExecuteKeys(object[] keyValues) { - if (this.HasNullForeignKey(keyValues)) { - return ((IEnumerable)this.empty).GetEnumerator(); - } - - T cached; - if (this.TryGetCachedObject(keyValues, out cached)) { - return ((IEnumerable)(new T[] { cached })).GetEnumerator(); - } - - return this.ExecuteKeyQuery(keyValues); - } - - private bool HasNullForeignKey(object[] keyValues) { - if (this.refersToPrimaryKey) { - bool keyHasNull = false; - for (int i = 0, n = keyValues.Length; i < n; i++) { - keyHasNull |= keyValues[i] == null; - } - if (keyHasNull) { - return true; - } - } - return false; - } - - private bool TryGetCachedObject(object[] keyValues, out T cached) { - cached = default(T); - if (this.refersToPrimaryKey) { - // look to see if we already have this object in the identity cache - MetaType mt = this.member.IsAssociation ? this.member.Association.OtherType : this.member.DeclaringType; - object obj = this.services.GetCachedObject(mt, keyValues); - if (obj != null) { - cached = (T)obj; - return true; - } - } - return false; - } - - private IEnumerator ExecuteKeyQuery(object[] keyValues) { - if (this.query == null) { - ParameterExpression p = Expression.Parameter(typeof(object[]), "keys"); - Expression[] keyExprs = new Expression[keyValues.Length]; - ReadOnlyCollection members = this.member.IsAssociation ? this.member.Association.OtherKey : this.member.DeclaringType.IdentityMembers; - for (int i = 0, n = keyValues.Length; i < n; i++) { - MetaDataMember mm = members[i]; - keyExprs[i] = Expression.Convert( -#pragma warning disable 618 // Disable the 'obsolete' warning - Expression.ArrayIndex(p, Expression.Constant(i)), -#pragma warning restore 618 - mm.Type - ); - } - Expression q = this.services.GetDataMemberQuery(this.member, keyExprs); - LambdaExpression lambda = Expression.Lambda(q, p); - this.query = this.services.Context.Provider.Compile(lambda); - } - return ((IEnumerable)this.query.Execute(this.services.Context.Provider, new object[] { keyValues }).ReturnValue).GetEnumerator(); - } - - class DeferredSource : IEnumerable, IEnumerable { - DeferredSourceFactory factory; - object instance; - - internal DeferredSource(DeferredSourceFactory factory, object instance) { - this.factory = factory; - this.instance = instance; - } - - public IEnumerator GetEnumerator() { - object[] keyValues = this.instance as object[]; - if (keyValues != null) { - return this.factory.ExecuteKeys(keyValues); - } - return this.factory.Execute(this.instance); - } - - IEnumerator IEnumerable.GetEnumerator() { - return this.GetEnumerator(); - } - } - } - /// /// Returns true if any objects have been added to the identity cache. If /// object tracking is disabled, this still returns true if any attempts @@ -599,13 +610,4 @@ internal object GetObjectByKey(MetaType type, object[] keyValues) { return target; } } - - internal struct RelatedItem { - internal MetaType Type; - internal object Item; - internal RelatedItem(MetaType type, object item) { - this.Type = type; - this.Item = item; - } - } -} +} \ No newline at end of file diff --git a/src/SubqueryRules.cs b/src/Querying/SubqueryRules.cs similarity index 100% rename from src/SubqueryRules.cs rename to src/Querying/SubqueryRules.cs diff --git a/src/SD.Tools.LinqToSQL2.csproj b/src/SD.Tools.LinqToSQL2.csproj index 70a3257..5df65e3 100644 --- a/src/SD.Tools.LinqToSQL2.csproj +++ b/src/SD.Tools.LinqToSQL2.csproj @@ -51,17 +51,44 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -94,12 +121,12 @@ - + - - + + - + @@ -131,11 +158,16 @@ - + + - + + + + - + + @@ -207,9 +239,11 @@ + - - + + + @@ -219,6 +253,7 @@ + diff --git a/src/SqlClient/Query/Funcletizer.cs b/src/SqlClient/Query/Funcletizer.cs index 57d09b7..29d3941 100644 --- a/src/SqlClient/Query/Funcletizer.cs +++ b/src/SqlClient/Query/Funcletizer.cs @@ -130,7 +130,7 @@ internal override Expression VisitMemberAccess(MemberExpression m) { } internal override Expression VisitMethodCall(MethodCallExpression m) { base.VisitMethodCall(m); - this.isRemote |= m.Method.DeclaringType == typeof(System.Data.Linq.Provider.DataManipulation) + this.isRemote |= m.Method.DeclaringType == typeof(DMLMethodPlaceholders) || Attribute.IsDefined(m.Method, typeof(FunctionAttribute)); return m; } diff --git a/src/SqlClient/Query/QueryConverter.cs b/src/SqlClient/Query/QueryConverter.cs index 55ae51e..8c9b8f5 100644 --- a/src/SqlClient/Query/QueryConverter.cs +++ b/src/SqlClient/Query/QueryConverter.cs @@ -2317,7 +2317,7 @@ private SqlNode VisitSequenceOperatorCall(MethodCallExpression mc) { } private static bool IsDataManipulationCall(MethodCallExpression mc) { - return mc.Method.IsStatic && mc.Method.DeclaringType == typeof(DataManipulation); + return mc.Method.IsStatic && mc.Method.DeclaringType == typeof(DMLMethodPlaceholders); } private SqlNode VisitDataManipulationCall(MethodCallExpression mc) { diff --git a/src/SqlClient/SqlProvider.cs b/src/SqlClient/SqlProvider.cs index 9b3c5d0..ddece3e 100644 --- a/src/SqlClient/SqlProvider.cs +++ b/src/SqlClient/SqlProvider.cs @@ -21,7 +21,13 @@ using System.Runtime.Versioning; using System.Runtime.CompilerServices; +#warning [FB] REFACTOR INTO SEPARATE ASSEMBLY SO OTHER DBs CAN BE SUPPORTED AS WELL + + namespace System.Data.Linq.SqlClient { + using System.Data.Linq.BindingLists; + + public sealed class Sql2000Provider : SqlProvider { public Sql2000Provider() : base(ProviderMode.Sql2000) { @@ -1372,7 +1378,7 @@ private ResultShape GetResultShape(Expression query) { } } } - else if (mce.Method.DeclaringType == typeof(DataManipulation) && mce.Method.ReturnType == typeof(int)) { + else if (mce.Method.DeclaringType == typeof(DMLMethodPlaceholders) && mce.Method.ReturnType == typeof(int)) { return ResultShape.Return; } } diff --git a/src/Types.cs b/src/Types.cs deleted file mode 100644 index c6d89a0..0000000 --- a/src/Types.cs +++ /dev/null @@ -1,869 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq.Expressions; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Diagnostics; -using System.Runtime.Serialization; -using System.Diagnostics.CodeAnalysis; - -namespace System.Data.Linq { - internal static class SourceState { - internal static readonly IEnumerable Loaded = (IEnumerable)new T[] { }; - internal static readonly IEnumerable Assigned = (IEnumerable)new T[] { }; - } - - [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "[....]: Types are never compared to each other. When comparisons happen it is against the entities that are represented by these constructs.")] - public struct Link { - T underlyingValue; - IEnumerable source; - - public Link(T value) { - this.underlyingValue = value; - this.source = null; - } - - public Link(IEnumerable source) { - this.source = source; - this.underlyingValue = default(T); - } - - public Link(Link link) { - this.underlyingValue = link.underlyingValue; - this.source = link.source; - } - - public bool HasValue { - get { return this.source == null || this.HasLoadedValue || this.HasAssignedValue; } - } - - public bool HasLoadedOrAssignedValue { - get { return this.HasLoadedValue || this.HasAssignedValue; } - } - - internal bool HasLoadedValue { - get { return this.source == SourceState.Loaded; } - } - - internal bool HasAssignedValue { - get { return this.source == SourceState.Assigned; } - } - - internal T UnderlyingValue { - get { return this.underlyingValue; } - } - - internal IEnumerable Source { - get { return this.source; } - } - - internal bool HasSource { - get { return this.source != null && !this.HasAssignedValue && !this.HasLoadedValue; } - } - - public T Value { - get { - if (this.HasSource) { - this.underlyingValue = Enumerable.SingleOrDefault(this.source); - this.source = SourceState.Loaded; - } - return this.underlyingValue; - } - set { - this.underlyingValue = value; - this.source = SourceState.Assigned; - } - } - } - - [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification="[....]: Types are never compared to each other. When comparisons happen it is against the entities that are represented by these constructs.")] - public struct EntityRef - where TEntity : class { - IEnumerable source; - TEntity entity; - - public EntityRef(TEntity entity) { - this.entity = entity; - this.source = SourceState.Assigned; - } - - public EntityRef(IEnumerable source) { - this.source = source; - this.entity = default(TEntity); - } - - public EntityRef(EntityRef entityRef) { - this.source = entityRef.source; - this.entity = entityRef.entity; - } - - public TEntity Entity { - get { - if (this.HasSource) { - - IEnumerable src = this.source; - this.entity = Enumerable.SingleOrDefault(src); - this.source = SourceState.Loaded; - } - return this.entity; - } - set { - this.entity = value; - this.source = SourceState.Assigned; - } - } - - public bool HasLoadedOrAssignedValue { - get { return this.HasLoadedValue || this.HasAssignedValue; } - } - - internal bool HasValue { - get { return this.source == null || this.HasLoadedValue || this.HasAssignedValue; } - } - - internal bool HasLoadedValue { - get { return this.source == SourceState.Loaded; } - } - - internal bool HasAssignedValue { - get { return this.source == SourceState.Assigned; } - } - - internal bool HasSource { - get { return this.source != null && !this.HasLoadedValue && !this.HasAssignedValue; } - } - - internal IEnumerable Source { - get { return this.source; } - } - - internal TEntity UnderlyingValue { - get { return this.entity; } - } - } - - [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification="[....]: Naming chosen to represent a different concept from a collection because it is delayed loaded.")] - public sealed class EntitySet : IList, IList, IListSource - where TEntity : class { - IEnumerable source; - ItemList entities; - ItemList removedEntities; - Action onAdd; - Action onRemove; - TEntity onAddEntity; - TEntity onRemoveEntity; - int version; - private ListChangedEventHandler onListChanged; - private bool isModified; - private bool isLoaded; - bool listChanged; - - public EntitySet() { - } - - public EntitySet(Action onAdd, Action onRemove) { - this.onAdd = onAdd; - this.onRemove = onRemove; - } - - internal EntitySet(EntitySet es, bool copyNotifications) { - this.source = es.source; - foreach (TEntity e in es.entities) entities.Add(e); - foreach (TEntity e in es.removedEntities) removedEntities.Add(e); - this.version = es.version; - if (copyNotifications) { - this.onAdd = es.onAdd; - this.onRemove = es.onRemove; - } - } - - public int Count { - get { - Load(); - return entities.Count; - } - } - - public TEntity this[int index] { - get { - Load(); - if (index < 0 || index >= entities.Count) - throw Error.ArgumentOutOfRange("index"); - return entities[index]; - } - set { - Load(); - if (index < 0 || index >= entities.Count) - throw Error.ArgumentOutOfRange("index"); - if (value == null || IndexOf(value) >= 0) - throw Error.ArgumentOutOfRange("value"); - CheckModify(); - TEntity old = entities[index]; - OnRemove(old); - OnListChanged(ListChangedType.ItemDeleted, index); - - OnAdd(value); - entities[index] = value; - OnModified(); - OnListChanged(ListChangedType.ItemAdded, index); - } - } - - [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "[....]: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")] - public void Add(TEntity entity) { - if (entity == null) { - throw Error.ArgumentNull("entity"); - } - if (entity != onAddEntity) { - CheckModify(); - if (!entities.Contains(entity)) { - OnAdd(entity); - if (this.HasSource) removedEntities.Remove(entity); - entities.Add(entity); - OnListChanged(ListChangedType.ItemAdded, entities.IndexOf(entity)); - } - OnModified(); - } - } - - public void AddRange(IEnumerable collection) { - if (collection == null) - throw Error.ArgumentNull("collection"); - CheckModify(); - // convert to List in case adding elements here removes them from the 'collection' (ie entityset to entityset assignment) - collection = collection.ToList(); - foreach (TEntity e in collection) { - if (!entities.Contains(e)) { - OnAdd(e); - if (this.HasSource) removedEntities.Remove(e); - entities.Add(e); - OnListChanged(ListChangedType.ItemAdded, entities.IndexOf(e)); - } - } - OnModified(); - } - - public void Assign(IEnumerable entitySource) { - // No-op if assigning the same object to itself - if (Object.ReferenceEquals(this, entitySource)) { - return; - } - - Clear(); - if (entitySource != null) - AddRange(entitySource); - - // When an entity set is assigned, it is considered loaded. - // Since with defer loading enabled, a load is triggered - // anyways, this is only necessary in cases where defer loading - // is disabled. In such cases, the materializer assigns a - // prefetched collection and we want IsLoaded to be true. - this.isLoaded = true; - } - - public void Clear() { - Load(); - CheckModify(); - if (entities.Items != null) { - List removeList = new List(entities.Items); - foreach (TEntity e in removeList) { - Remove(e); - } - } - entities = default(ItemList); - OnModified(); - OnListChanged(ListChangedType.Reset, 0); - } - - [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "[....]: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")] - public bool Contains(TEntity entity) { - return IndexOf(entity) >= 0; - } - - public void CopyTo(TEntity[] array, int arrayIndex) { - Load(); - if (entities.Count > 0) Array.Copy(entities.Items, 0, array, arrayIndex, entities.Count); - } - - public IEnumerator GetEnumerator() { - Load(); - return new Enumerator(this); - } - - internal IEnumerable GetUnderlyingValues() { - return new UnderlyingValues(this); - } - - class UnderlyingValues : IEnumerable { - EntitySet entitySet; - internal UnderlyingValues(EntitySet entitySet) { - this.entitySet = entitySet; - } - public IEnumerator GetEnumerator() { - return new Enumerator(this.entitySet); - } - IEnumerator IEnumerable.GetEnumerator() { - return this.GetEnumerator(); - } - } - - [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "[....]: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")] - public int IndexOf(TEntity entity) { - Load(); - return entities.IndexOf(entity); - } - - [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "1#", Justification = "[....]: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")] - public void Insert(int index, TEntity entity) { - Load(); - if (index < 0 || index > Count) - throw Error.ArgumentOutOfRange("index"); - if (entity == null || IndexOf(entity) >= 0) - throw Error.ArgumentOutOfRange("entity"); - CheckModify(); - entities.Insert(index, entity); - OnListChanged(ListChangedType.ItemAdded, index); - - OnAdd(entity); - } - - /// - /// Returns true if this entity set has a deferred query - /// that hasn't been executed yet. - /// - public bool IsDeferred - { - get { return HasSource; } - } - - /// - /// Returns true if values have been either assigned or loaded. - /// - internal bool HasValues { - get { return this.source == null || this.HasAssignedValues || this.HasLoadedValues; } - } - - /// - /// Returns true if the entity set has been modified in any way by the user or its items - /// have been loaded from the database. - /// - public bool HasLoadedOrAssignedValues { - get { return this.HasAssignedValues || this.HasLoadedValues; } - } - - /// - /// Returns true if the set has been modified in any way by the user. - /// - internal bool HasAssignedValues { - get { return this.isModified; } - } - - /// - /// Returns true if the set has been loaded from the database. - /// - internal bool HasLoadedValues { - get { return this.isLoaded; } - } - - /// - /// Returns true if the set has a deferred source query that hasn't been loaded yet. - /// - internal bool HasSource { - get { return this.source != null && !this.HasLoadedValues; } - } - - /// - /// Returns true if the collection has been loaded. - /// - internal bool IsLoaded { - get { - return this.isLoaded; - } - } - - internal IEnumerable Source { - get { return this.source; } - } - - public void Load() { - if (this.HasSource) { - ItemList addedEntities = entities; - entities = default(ItemList); - foreach (TEntity e in source) entities.Add(e); - foreach (TEntity e in addedEntities) entities.Include(e); - foreach (TEntity e in removedEntities) entities.Remove(e); - source = SourceState.Loaded; - isLoaded = true; - removedEntities = default(ItemList); - } - } - - private void OnModified() { - isModified = true; - } - - [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "[....]: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")] - public bool Remove(TEntity entity) { - if (entity == null || entity == onRemoveEntity) return false; - CheckModify(); - int index = -1; - bool removed = false; - if (this.HasSource) { - if (!removedEntities.Contains(entity)) { - OnRemove(entity); - // check in entities in case it has been pre-added - index = entities.IndexOf(entity); - if (index != -1) { - entities.RemoveAt(index); - } - else { - removedEntities.Add(entity); - } - removed = true; - } - } else { - index = entities.IndexOf(entity); - if (index != -1) { - OnRemove(entity); - entities.RemoveAt(index); - removed = true; - } - } - if (removed) { - OnModified(); - // If index == -1 here, that means that the entity was not in the list before Remove was called, - // so we shouldn't fire the event since the list itself will not be changed, even though the Remove will still be tracked - // on the removedEntities list in case a subsequent Load brings in this entity. - if (index != -1) { - OnListChanged(ListChangedType.ItemDeleted, index); - } - } - return removed; - } - - public void RemoveAt(int index) { - Load(); - if (index < 0 || index >= Count) { - throw Error.ArgumentOutOfRange("index"); - } - CheckModify(); - TEntity entity = entities[index]; - OnRemove(entity); - entities.RemoveAt(index); - OnModified(); - OnListChanged(ListChangedType.ItemDeleted, index); - } - - public void SetSource(IEnumerable entitySource) { - if (this.HasAssignedValues || this.HasLoadedValues) - throw Error.EntitySetAlreadyLoaded(); - this.source = entitySource; - } - - void CheckModify() { - if (onAddEntity != null || onRemoveEntity != null) - throw Error.ModifyDuringAddOrRemove(); - version++; - } - - void OnAdd(TEntity entity) { - if (onAdd != null) { - TEntity e = onAddEntity; - onAddEntity = entity; - try { - onAdd(entity); - } finally { - onAddEntity = e; - } - } - } - - void OnRemove(TEntity entity) { - if (onRemove != null) { - TEntity e = onRemoveEntity; - onRemoveEntity = entity; - try { - onRemove(entity); - } finally { - onRemoveEntity = e; - } - } - } - - class Enumerable : IEnumerable { - EntitySet entitySet; - public Enumerable(EntitySet entitySet) { - this.entitySet = entitySet; - } - IEnumerator IEnumerable.GetEnumerator() { - return this.GetEnumerator(); - } - public IEnumerator GetEnumerator() { - return new Enumerator(this.entitySet); - } - } - - class Enumerator : IEnumerator { - EntitySet entitySet; - TEntity[] items; - int index; - int endIndex; - int version; - - public Enumerator(EntitySet entitySet) { - this.entitySet = entitySet; - this.items = entitySet.entities.Items; - this.index = -1; - this.endIndex = entitySet.entities.Count - 1; - this.version = entitySet.version; - } - - public void Dispose() - { - // Technically, calling GC.SuppressFinalize is not required because the class does not - // have a finalizer, but it does no harm, protects against the case where a finalizer is added - // in the future, and prevents an FxCop warning. - GC.SuppressFinalize(this); - } - - public bool MoveNext() { - if (version != entitySet.version) - throw Error.EntitySetModifiedDuringEnumeration(); - if (index == endIndex) return false; - index++; - return true; - } - - public TEntity Current { - get { return items[index]; } - } - - object IEnumerator.Current { - get { return items[index]; } - } - - void IEnumerator.Reset() { - if (version != entitySet.version) - throw Error.EntitySetModifiedDuringEnumeration(); - index = -1; - } - } - - int IList.Add(object value) { - TEntity entity = value as TEntity; - if (entity == null || IndexOf(entity) >= 0) { - throw Error.ArgumentOutOfRange("value"); - } - CheckModify(); - int i = entities.Count; - entities.Add(entity); - OnAdd(entity); - return i; - } - - bool IList.Contains(object value) { - return Contains(value as TEntity); - } - - int IList.IndexOf(object value) { - return IndexOf(value as TEntity); - } - - void IList.Insert(int index, object value) { - TEntity entity = value as TEntity; - if (value == null) - throw Error.ArgumentOutOfRange("value"); - Insert(index, entity); - } - - bool IList.IsFixedSize { - get { return false; } - } - - bool IList.IsReadOnly { - get { return false; } - } - - void IList.Remove(object value) { - Remove(value as TEntity); - } - - object IList.this[int index] { - get { - return this[index]; - } - set { - TEntity entity = value as TEntity; - if (value == null) throw Error.ArgumentOutOfRange("value"); - this[index] = entity; - } - } - - void ICollection.CopyTo(Array array, int index) { - Load(); - if (entities.Count > 0) Array.Copy(entities.Items, 0, array, index, entities.Count); - } - - bool ICollection.IsSynchronized { - get { return false; } - } - - object ICollection.SyncRoot { - get { return this; } - } - - bool ICollection.IsReadOnly { - get { return false; } - } - - IEnumerator IEnumerable.GetEnumerator() { - return GetEnumerator(); - } - - void OnListChanged(ListChangedType type, int index) { - listChanged = true; - if (onListChanged != null) { - onListChanged(this, new ListChangedEventArgs(type, index)); - } - } - - public event ListChangedEventHandler ListChanged { - add { - onListChanged += value; - } - remove { - onListChanged -= value; - } - } - - bool IListSource.ContainsListCollection { - get { return true; } - } - - private IBindingList cachedList = null; - - IList IListSource.GetList() { - if (cachedList == null || listChanged) { - cachedList = GetNewBindingList(); - listChanged = false; - } - return cachedList; - } - - [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification="Method doesn't represent a property of the type.")] - public IBindingList GetNewBindingList() { - return new EntitySetBindingList(this.ToList(), this); - } - } - - struct ItemList where T : class { - T[] items; - int count; - - public int Count { - get { return count; } - } - - public T[] Items { - get { return items; } - } - - public T this[int index] { - get { return items[index]; } - set { items[index] = value; } - } - - public void Add(T item) { - if (items == null || items.Length == count) GrowItems(); - items[count] = item; - count++; - } - - public bool Contains(T item) { - return IndexOf(item) >= 0; - } - - public Enumerator GetEnumerator() { - Enumerator e; - e.items = items; - e.index = -1; - e.endIndex = count - 1; - return e; - } - - public bool Include(T item) { - if (LastIndexOf(item) >= 0) return false; - Add(item); - return true; - } - - public int IndexOf(T item) { - for (int i = 0; i < count; i++) { - if (items[i] == item) return i; - } - return -1; - } - - public void Insert(int index, T item) { - if (items == null || items.Length == count) GrowItems(); - if (index < count) Array.Copy(items, index, items, index + 1, count - index); - items[index] = item; - count++; - } - - public int LastIndexOf(T item) { - int i = count; - while (i > 0) { - --i; - if (items[i] == item) return i; - } - return -1; - } - - public bool Remove(T item) { - int i = IndexOf(item); - if (i < 0) return false; - RemoveAt(i); - return true; - } - - public void RemoveAt(int index) { - count--; - if (index < count) Array.Copy(items, index + 1, items, index, count - index); - items[count] = default(T); - } - - void GrowItems() { - Array.Resize(ref items, count == 0 ? 4 : count * 2); - } - - public struct Enumerator { - internal T[] items; - internal int index; - internal int endIndex; - - public bool MoveNext() { - if (index == endIndex) return false; - index++; - return true; - } - - public T Current { - get { return items[index]; } - } - } - } - - [SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces", Justification = "[....]: The name clearly describes function and the namespace is under a DLinq namespace which will make the distinction clear.")] - [DataContract] - [Serializable] - public sealed class Binary : IEquatable { - [DataMember(Name="Bytes")] - byte[] bytes; - int? hashCode; - - public Binary(byte[] value) { - if (value == null) { - this.bytes = new byte[0]; - } - else { - this.bytes = new byte[value.Length]; - Array.Copy(value, this.bytes, value.Length); - } - this.ComputeHash(); - } - - public byte[] ToArray() { - byte[] copy = new byte[this.bytes.Length]; - Array.Copy(this.bytes, copy, copy.Length); - return copy; - } - - public int Length { - get { return this.bytes.Length; } - } - - public static implicit operator Binary(byte[] value) { - return new Binary(value); - } - - public bool Equals(Binary other) { - return this.EqualsTo(other); - } - - public static bool operator ==(Binary binary1, Binary binary2) { - if ((object)binary1 == (object)binary2) - return true; - if ((object)binary1 == null && (object)binary2 == null) - return true; - if ((object)binary1 == null || (object)binary2 == null) - return false; - return binary1.EqualsTo(binary2); - } - - public static bool operator !=(Binary binary1, Binary binary2) { - if ((object)binary1 == (object)binary2) - return false; - if ((object)binary1 == null && (object)binary2 == null) - return false; - if ((object)binary1 == null || (object)binary2 == null) - return true; - return !binary1.EqualsTo(binary2); - } - - public override bool Equals(object obj) { - return this.EqualsTo(obj as Binary); - } - - public override int GetHashCode() { - if (!hashCode.HasValue) { - // hash code is not marked [DataMember], so when - // using the DataContractSerializer, we'll need - // to recompute the hash after deserialization. - ComputeHash(); - } - return this.hashCode.Value; - } - - public override string ToString() { - StringBuilder sb = new StringBuilder(); - sb.Append("\""); - sb.Append(System.Convert.ToBase64String(this.bytes, 0, this.bytes.Length)); - sb.Append("\""); - return sb.ToString(); - } - - private bool EqualsTo(Binary binary) { - if ((object)this == (object)binary) - return true; - if ((object)binary == null) - return false; - if (this.bytes.Length != binary.bytes.Length) - return false; - if (this.GetHashCode() != binary.GetHashCode()) - return false; - for (int i = 0, n = this.bytes.Length; i < n; i++) { - if (this.bytes[i] != binary.bytes[i]) - return false; - } - return true; - } - - /// - /// Simple hash using pseudo-random coefficients for each byte in - /// the array to achieve order dependency. - /// - private void ComputeHash() { - int s = 314, t = 159; - hashCode = 0; - for (int i = 0; i < bytes.Length; i++) { - hashCode = hashCode * s + bytes[i]; - s = s * t; - } - } - } -} diff --git a/src/Types/Binary.cs b/src/Types/Binary.cs new file mode 100644 index 0000000..1a69c25 --- /dev/null +++ b/src/Types/Binary.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq.Expressions; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Diagnostics; +using System.Runtime.Serialization; +using System.Diagnostics.CodeAnalysis; +using System.Data.Linq.BindingLists; + +namespace System.Data.Linq +{ + [SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces", Justification = "[....]: The name clearly describes function and the namespace is under a DLinq namespace which will make the distinction clear.")] + [DataContract] + [Serializable] + public sealed class Binary : IEquatable + { + [DataMember(Name = "Bytes")] + byte[] bytes; + int? hashCode; + + public Binary(byte[] value) + { + if(value == null) + { + this.bytes = new byte[0]; + } + else + { + this.bytes = new byte[value.Length]; + Array.Copy(value, this.bytes, value.Length); + } + this.ComputeHash(); + } + + public byte[] ToArray() + { + byte[] copy = new byte[this.bytes.Length]; + Array.Copy(this.bytes, copy, copy.Length); + return copy; + } + + public int Length + { + get { return this.bytes.Length; } + } + + public static implicit operator Binary(byte[] value) + { + return new Binary(value); + } + + public bool Equals(Binary other) + { + return this.EqualsTo(other); + } + + public static bool operator ==(Binary binary1, Binary binary2) + { + if((object)binary1 == (object)binary2) + return true; + if((object)binary1 == null && (object)binary2 == null) + return true; + if((object)binary1 == null || (object)binary2 == null) + return false; + return binary1.EqualsTo(binary2); + } + + public static bool operator !=(Binary binary1, Binary binary2) + { + if((object)binary1 == (object)binary2) + return false; + if((object)binary1 == null && (object)binary2 == null) + return false; + if((object)binary1 == null || (object)binary2 == null) + return true; + return !binary1.EqualsTo(binary2); + } + + public override bool Equals(object obj) + { + return this.EqualsTo(obj as Binary); + } + + public override int GetHashCode() + { + if(!hashCode.HasValue) + { + // hash code is not marked [DataMember], so when + // using the DataContractSerializer, we'll need + // to recompute the hash after deserialization. + ComputeHash(); + } + return this.hashCode.Value; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("\""); + sb.Append(System.Convert.ToBase64String(this.bytes, 0, this.bytes.Length)); + sb.Append("\""); + return sb.ToString(); + } + + private bool EqualsTo(Binary binary) + { + if((object)this == (object)binary) + return true; + if((object)binary == null) + return false; + if(this.bytes.Length != binary.bytes.Length) + return false; + if(this.GetHashCode() != binary.GetHashCode()) + return false; + for(int i = 0, n = this.bytes.Length; i < n; i++) + { + if(this.bytes[i] != binary.bytes[i]) + return false; + } + return true; + } + + /// + /// Simple hash using pseudo-random coefficients for each byte in + /// the array to achieve order dependency. + /// + private void ComputeHash() + { + int s = 314, t = 159; + hashCode = 0; + for(int i = 0; i < bytes.Length; i++) + { + hashCode = hashCode * s + bytes[i]; + s = s * t; + } + } + } +} +