internal void Add(MetaAssociation assoc, TrackedObject from, TrackedObject to) { Dictionary<TrackedObject, TrackedObject> pairs; if(!associations.TryGetValue(assoc, out pairs)) { pairs = new Dictionary<TrackedObject, TrackedObject>(); associations.Add(assoc, pairs); } pairs.Add(from, to); }
internal void Add(TrackedObject from, TrackedObject to) { List<TrackedObject> refs; if(!references.TryGetValue(from, out refs)) { refs = new List<TrackedObject>(); references.Add(from, refs); } if(!refs.Contains(to)) refs.Add(to); }
internal TrackedObject this[MetaAssociation assoc, TrackedObject from] { get { Dictionary<TrackedObject, TrackedObject> pairs; if(associations.TryGetValue(assoc, out pairs)) { TrackedObject to; if(pairs.TryGetValue(from, out to)) { return to; } } return null; } }
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<object> facts = (IEnumerable<object>)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(); } } }
/// <summary> /// Constructor. /// </summary> /// <param name="session">The session in which the conflicts occurred.</param> /// <param name="trackedObject">The tracked item in conflict.</param> /// <param name="isDeleted">True if the item in conflict no longer exists in the database.</param> internal ObjectChangeConflict(ChangeConflictSession session, TrackedObject trackedObject, bool isDeleted) : this(session, trackedObject) { this.isDeleted = isDeleted; }
internal abstract int DynamicDelete(TrackedObject item);
internal abstract int Update(TrackedObject item);
internal abstract int Insert(TrackedObject item);
private void BuildDependencyOrderedList(TrackedObject item, List<TrackedObject> list, Dictionary<TrackedObject, VisitState> 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; }
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; }
/// <summary> /// Constructor. /// </summary> /// <param name="session">The session in which the conflicts occurred.</param> /// <param name="trackedObject">The tracked item in conflict.</param> internal ObjectChangeConflict(ChangeConflictSession session, TrackedObject trackedObject) { this.session = session; this.trackedObject = trackedObject; this.original = trackedObject.CreateDataCopy(trackedObject.Original); }
internal abstract void AppendDeleteText(TrackedObject item, StringBuilder appendTo);
internal abstract int DynamicUpdate(TrackedObject item);
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 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; } } } }
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; }
internal abstract int DynamicInsert(TrackedObject item);
/// <summary> /// 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 /// </summary> 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 } }
/// <summary> /// Update the item, returning 0 if the update fails, 1 if it succeeds. /// </summary> 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); } }
// 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); } } } } }
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; }
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); }
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(); } }
internal IEnumerable<TrackedObject> this[TrackedObject from] { get { List<TrackedObject> refs; if (references.TryGetValue(from, out refs)) { return refs; } return Empty; } }
private Expression GetInsertCommand(TrackedObject item) { MetaType mt = item.Type; // bind to InsertFacts if there are any members to syncronize List<MetaDataMember> 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)); } }
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<MetaDataMember> 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<TrackedObject, object[]>(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<MetaDataMember> 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)); } }