| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923 |
- 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 {
- /// <summary>
- /// Starts tracking an object as 'unchanged'
- /// </summary>
- /// <param name="obj"></param>
- /// <returns></returns>
- internal abstract TrackedObject Track(object obj);
- /// <summary>
- /// Starts tracking an object as 'unchanged', and optionally
- /// 'weakly' tracks all other referenced objects recursively.
- /// </summary>
- /// <param name="obj"></param>
- /// <param name="recurse">True if all untracked objects in the graph
- /// should be tracked recursively.</param>
- /// <returns></returns>
- internal abstract TrackedObject Track(object obj, bool recurse);
- /// <summary>
- /// Fast-tracks an object that is already in identity cache
- /// </summary>
- /// <param name="obj"></param>
- 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<TrackedObject> GetInterestingObjects();
- internal static ChangeTracker CreateChangeTracker(CommonDataServices dataServices, bool asReadOnly) {
- if (asReadOnly) {
- return new ReadOnlyChangeTracker();
- }
- else {
- return new StandardChangeTracker(dataServices);
- }
- }
- class StandardChangeTracker : ChangeTracker {
- Dictionary<object, StandardTrackedObject> items;
- PropertyChangingEventHandler onPropertyChanging;
- CommonDataServices services;
- internal StandardChangeTracker(CommonDataServices services) {
- this.services = services;
- this.items = new Dictionary<object, StandardTrackedObject>();
- this.onPropertyChanging = new PropertyChangingEventHandler(this.OnPropertyChanging);
- }
- /// <summary>
- /// Given a type root and a discriminator, return the type that would be instantiated.
- /// </summary>
- 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<object, object> visited = new Dictionary<object, object>();
- return Track(type, obj, visited, recurse, 1);
- }
- private TrackedObject Track(MetaType mt, object obj, Dictionary<object, object> 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<StandardTrackedObject> list = new List<StandardTrackedObject>((IEnumerable<StandardTrackedObject>)this.items.Values);
- foreach (TrackedObject item in list) {
- item.AcceptChanges();
- }
- }
- internal override IEnumerable<TrackedObject> 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);
- }
- }
- /// <summary>
- /// 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.
- /// </summary>
- private void ResetDirtyMemberTracking() {
- this.dirtyMemberCache.SetAll(false);
- }
- /// <summary>
- /// Refresh internal tracking state using the original value and mode
- /// specified.
- /// </summary>
- 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();
- }
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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<Object>().SingleOrDefault());
- }
- }
- }
- }
- }
- private IEnumerable<MetaDataMember> 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<ModifiedMemberInfo> 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<MetaDataMember> 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;
- }
- /// <summary>
- /// Called to initialize deferred loaders for New or Attached entities.
- /// </summary>
- 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<MetaDataMember> 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<MetaDataMember> 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;
- }
- }
- }
- /// <summary>
- /// This is the implementation used when change tracking is disabled.
- /// </summary>
- 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<TrackedObject> GetInterestingObjects() { return new TrackedObject[0]; }
- }
- }
- internal abstract class TrackedObject {
- internal abstract MetaType Type { get; }
- /// <summary>
- /// The current client value.
- /// </summary>
- internal abstract object Current { get; }
- /// <summary>
- /// The last read database value. This is updated whenever the
- /// item is refreshed.
- /// </summary>
- 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; }
- /// <summary>
- /// True if the object is being tracked (perhaps during a recursive
- /// attach operation) but can be transitioned to other states.
- /// </summary>
- internal abstract bool IsWeaklyTracked { get; }
- internal abstract bool HasDeferredLoaders { get; }
- internal abstract bool HasChangedValues();
- internal abstract IEnumerable<ModifiedMemberInfo> 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();
- /// <summary>
- /// Refresh the item by making the value passed in the current
- /// Database value, and refreshing the current values using the
- /// mode specified.
- /// </summary>
- internal abstract void Refresh(RefreshMode mode, object freshInstance);
- /// <summary>
- /// 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.
- /// </summary>
- /// <param name="member"></param>
- /// <param name="mode"></param>
- /// <param name="freshValue"></param>
- internal abstract void RefreshMember(MetaDataMember member, RefreshMode mode, object freshValue);
- /// <summary>
- /// Create a data-member only copy of the instance (no associations)
- /// </summary>
- /// <returns></returns>
- internal abstract object CreateDataCopy(object instance);
- internal abstract bool SynchDependentData();
- internal abstract bool IsPendingGeneration(IEnumerable<MetaDataMember> keyMembers);
- internal abstract bool IsMemberPendingGeneration(MetaDataMember keyMember);
- internal abstract void InitializeDeferredLoaders();
- }
- }
|