public async Task Save(IEntity entity, SaveBehaviour behaviour) { if (entity == null) { throw new ArgumentNullException(nameof(entity)); } Task save() => new SaveOperation(this, entity, behaviour).Run(); async Task doSave() { if (entity.IsNew) { await save(); } else { using (await GetSyncLock(entity.GetType().FullName + entity.GetId()).Lock()) await save(); } } if (ProviderConfig.Configuration.Transaction.EnforceForSave) { await EnlistOrCreateTransaction(doSave); } else { await doSave(); } }
/// <summary> /// Justifies the order of a specified item and its siblings. /// The value of the "Order" property in those objects will be 10, 20, 30, ... /// </summary> public static async Task JustifyOrders(ISortable item, SaveBehaviour saveBehaviour = SaveBehaviour.Default) { using (await AsyncLock.Lock()) { var changed = new List <Entity>(); var order = 0; foreach (var sibling in (await FindSiblings(item)).OrderBy(i => i.Order).Distinct().ToArray()) { order += INCREMENT; if (sibling.Order == order) { continue; } var clone = sibling.Clone() as Entity; (clone as ISortable).Order = order; //changed.Add(clone); try { await Database.Save(clone, saveBehaviour); } catch { } } //await Database.Save(changed, saveBehaviour); } }
public async Task Save(IEntity entity, SaveBehaviour behaviour) { if (entity == null) { throw new ArgumentNullException(nameof(entity)); } Func <Task> save = async() => await DoSave(entity, behaviour); Func <Task> doSave = async() => { if (entity.IsNew) { await save(); } else { using (await GetSyncLock(entity.GetType().FullName + entity.GetId()).Lock()) await save(); } }; if (Configuration.Transaction.EnforceForSave) { await EnlistOrCreateTransaction(doSave); } else { await doSave(); } }
/// <summary> /// Moves an item up among its siblings. Returns False if the item is already first in the list, otherwise true. /// </summary> public static async Task <bool> MoveUp(ISortable item, SaveBehaviour saveBehaviour = SaveBehaviour.Default) { using (await AsyncLock.Lock()) { var above = await FindItemAbove(item); if (above == null) { return(false); } if (above.Order == item.Order) { above.Order--; } await Swap(item, above, saveBehaviour); item = await Database.Reload(item); above = await Database.Reload(above); } await JustifyOrders(item, saveBehaviour); return(true); }
/// <summary> /// Moves an item up among its siblings. Returns False if the item is already first in the list, otherwise true. /// </summary> public static bool MoveUp(ISortable item, SaveBehaviour saveBehaviour = SaveBehaviour.Default) { lock (SyncLock) { var above = FindItemAbove(item); if (above == null) { return(false); } if (above.Order == item.Order) { above.Order--; } Swap(item, above, saveBehaviour); Database.Reload(ref item); Database.Reload(ref above); JustifyOrders(item, saveBehaviour); return(true); } }
/// <summary> /// Inserts or updates an object in the database. /// </summary> public static void Save(IEntity entity, SaveBehaviour behaviour) { if (entity == null) { throw new ArgumentNullException(nameof(entity)); } Action save = () => DoSave(entity, behaviour); Action doSave = () => { if (entity.IsNew) { save(); } else { lock (string.Intern(entity.GetType().FullName + entity.GetId())) { save(); } } }; if (ENFORCE_SAVE_TRANSACTION) { EnlistOrCreateTransaction(doSave); } else { doSave(); } }
/// <summary> /// Moves this item after a specified other item. If null is specified, it will be moved to the beginning of its siblings. /// </summary> public static async Task MoveAfter(ISortable item, ISortable after, SaveBehaviour saveBehaviour = SaveBehaviour.Default) { var newOrder = (after == null ? 0 : after.Order) + 1; item = await Database.Update(item, o => o.Order = newOrder, saveBehaviour); await JustifyOrders(item, saveBehaviour); }
public SaveOperation(Database db, IEntity entity, SaveBehaviour behaviour) { Entity = entity as Entity ?? throw new ArgumentNullException(nameof(entity)); InTransaction = db.AnyOpenTransaction(); Mode = entity.IsNew ? SaveMode.Insert : SaveMode.Update; ClonedFrom = Entity._ClonedFrom; Provider = db.GetProvider(entity); Behaviour = behaviour; Db = db; }
/// <summary> /// Moves an item up to last among its siblings. Always returns true. /// </summary> public static bool MoveLast(ISortable item, SaveBehaviour saveBehaviour = SaveBehaviour.Default) { lock (SyncLock) { var last = FindSiblings(item).Max(o => o.Order); Database.Update(item, o => o.Order = last + 1, saveBehaviour); JustifyOrders(item, saveBehaviour); return(true); } }
/// <summary> /// Moves an item up to last among its siblings. Always returns true. /// </summary> public static async Task <bool> MoveLast(ISortable item, SaveBehaviour saveBehaviour = SaveBehaviour.Default) { using (await AsyncLock.Lock()) { var last = (await FindSiblings(item)).Max(o => o.Order); await Entity.Database.Update(item, o => o.Order = last + 1, saveBehaviour); await JustifyOrders(item, saveBehaviour); return(true); } }
/// <summary> /// Swaps the order of two specified items. /// </summary> static void Swap(ISortable one, ISortable two, SaveBehaviour saveBehaviour) { var somethingAboveAll = FindSiblings(one).Max(i => i.Order) + 20; Database.EnlistOrCreateTransaction(() => { var order1 = two.Order; var order2 = one.Order; Database.Update(one, i => i.Order = order1, saveBehaviour); Database.Update(two, i => i.Order = order2, saveBehaviour); }); }
/// <summary> /// Moves this item before a specified other item. If null is specified, it will be moved to the end of its siblings. /// </summary> public static async Task MoveBefore(ISortable item, ISortable before, SaveBehaviour saveBehaviour = SaveBehaviour.Default) { var newOrder = (before == null ? int.MaxValue : before.Order) - 1; if (newOrder < 0) { newOrder = 0; } item = await Database.Update(item, o => o.Order = newOrder, saveBehaviour); await JustifyOrders(item, saveBehaviour); }
/// <summary> /// Swaps the order of two specified items. /// </summary> static async Task Swap(ISortable one, ISortable two, SaveBehaviour saveBehaviour) { var somethingAboveAll = (await FindSiblings(one)).Max(i => i.Order) + 20; await Database.EnlistOrCreateTransaction(async() => { var order1 = two.Order; var order2 = one.Order; await Database.Update(one, i => i.Order = order1, saveBehaviour); await Database.Update(two, i => i.Order = order2, saveBehaviour); }); }
/// <summary> /// Runs an update command on a given object's clone and persists the updated object in database. It returns the updated instance. /// </summary> /// <param name="item">The object to be updated in database.</param> /// <param name="action">Update action. For example: o=>o.Property = "Value"</param> public static T Update <T>(T item, Action <T> action, SaveBehaviour behaviour) where T : IEntity { if (item == null) { throw new ArgumentNullException("item"); } if (action == null) { throw new ArgumentNullException("action"); } if (item.IsNew) { throw new ArgumentException("New instances cannot be updated using the Update method."); } if (!(item is Entity)) { throw new ArgumentException("Database.Update() method accepts a type inheriting from {0}. So {1} is not supported.".FormatWith(typeof(Entity).FullName, typeof(T).FullName)); } if ((item as Entity)._ClonedFrom?.IsStale == true && AnyOpenTransaction()) { // No need for an error. We can just get the fresh version here. item = Reload(item); } if (EntityManager.IsImmutable(item as Entity)) { var clone = (T)((IEntity)item).Clone(); action(clone); Save(clone as Entity, behaviour); if (!AnyOpenTransaction()) { action(item); } return(clone); } else { action(item); Save(item, behaviour); return(item); } }
/// <summary> /// Runs an update command on a given object's clone and persists the updated object in database. It returns the updated instance. /// </summary> /// <param name="item">The object to be updated in database.</param> /// <param name="action">Update action. For example: o=>o.Property = "Value"</param> public async Task <T> Update <T>(T item, Action <T> action, SaveBehaviour behaviour) where T : IEntity { if (item == null) { throw new ArgumentNullException(nameof(item)); } if (action == null) { throw new ArgumentNullException(nameof(action)); } if (item.IsNew) { throw new InvalidOperationException("New instances cannot be updated using the Update method."); } if (!(item is Entity)) { throw new ArgumentException($"Database.Update() method accepts a type inheriting from {typeof(Entity).FullName}. So {typeof(T).FullName} is not supported."); } if ((item as Entity)._ClonedFrom?.IsStale == true && AnyOpenTransaction()) { // No need for an error. We can just get the fresh version here. item = await Reload(item); } if (Entity.Services.IsImmutable(item as Entity)) { var clone = (T)((IEntity)item).Clone(); action(clone); await Save(clone as Entity, behaviour); if (!AnyOpenTransaction()) { action(item); } return(clone); } else { action(item); await Save(item, behaviour); return(item); } }
/// <summary> /// Runs an update command on a list of given objects and persists the updated objects in database. /// It returns the updated instances. /// </summary> /// <param name="items">The objects to be updated in database.</param> /// <param name="action">Update action. For example: o=>o.Property = "Value"</param> public static List <T> Update <T>(IEnumerable <T> items, Action <T> action, SaveBehaviour behaviour) where T : IEntity { var result = new List <T>(); EnlistOrCreateTransaction(() => { foreach (var item in items) { result.Add(Update(item, action, behaviour)); } }); return(result); }
/// <summary> /// Inserts or updates an object in the database. /// </summary> public static void Save(IEntity entity, SaveBehaviour behaviour) { if (entity == null) { throw new ArgumentNullException("entity"); } if (ENFORCE_SAVE_TRANSACTION) { EnlistOrCreateTransaction(() => DoSave(entity, behaviour)); } else { DoSave(entity, behaviour); } }
/// <summary> /// Moves an item up to first among its siblings. Returns False if the item is already first in the list, otherwise true. /// </summary> public static bool MoveFirst(ISortable item, SaveBehaviour saveBehaviour = SaveBehaviour.Default) { lock (SyncLock) { var first = FindSiblings(item).Min(o => o.Order); if (first <= 0) { return(false); } Database.Update(item, o => o.Order = first - 1, saveBehaviour); JustifyOrders(item, saveBehaviour); return(true); } }
/// <summary> /// Moves an item up to first among its siblings. Returns False if the item is already first in the list, otherwise true. /// </summary> public static async Task <bool> MoveFirst(ISortable item, SaveBehaviour saveBehaviour = SaveBehaviour.Default) { using (await AsyncLock.Lock()) { var first = (await FindSiblings(item)).Min(o => o.Order); if (first <= 0) { return(false); } await Entity.Database.Update(item, o => o.Order = first - 1, saveBehaviour); await JustifyOrders(item, saveBehaviour); return(true); } }
/// <summary> /// Saves the specified records in the data repository. /// The operation will run in a Transaction. /// </summary> public static IEnumerable <T> Save <T>(IEnumerable <T> records, SaveBehaviour behaviour) where T : IEntity { if (records == null) { throw new ArgumentNullException("records"); } if (records.None()) { return(records); } EnlistOrCreateTransaction(() => { foreach (var record in records) { Save(record as Entity, behaviour); } }); return(records); }
/// <summary> /// Saves the specified records in the data repository. /// The operation will run in a Transaction. /// </summary> public async Task <IEnumerable <T> > Save <T>(IEnumerable <T> records, SaveBehaviour behaviour) where T : IEntity { if (records == null) { throw new ArgumentNullException(nameof(records)); } if (records.None()) { return(records); } await EnlistOrCreateTransaction(async() => { foreach (var record in records) { await Save(record as Entity, behaviour); } }); return(records); }
/// <summary> /// Moves an item down among its siblings. Returns False if the item is already last in the list, otherwise true. /// </summary> public static bool MoveDown(ISortable item, SaveBehaviour saveBehaviour = SaveBehaviour.Default) { lock (SyncLock) { var below = FindItemBelow(item); if (below == null) { return(false); } if (below.Order == item.Order) { item.Order++; } Swap(item, below, saveBehaviour); JustifyOrders(item, saveBehaviour); return(true); } }
/// <summary> /// Moves an item down among its siblings. Returns False if the item is already last in the list, otherwise true. /// </summary> public static async Task <bool> MoveDown(ISortable item, SaveBehaviour saveBehaviour = SaveBehaviour.Default) { using (await AsyncLock.Lock()) { var below = await FindItemBelow(item); if (below == null) { return(false); } if (below.Order == item.Order) { item.Order++; } await Swap(item, below, saveBehaviour); } await JustifyOrders(item, saveBehaviour); return(true); }
/// <summary> /// Justifies the order of a specified item and its siblings. /// The value of the "Order" property in those objects will be 10, 20, 30, ... /// </summary> public static void JustifyOrders(ISortable item, SaveBehaviour saveBehaviour = SaveBehaviour.Default) { lock (SyncLock) { var changed = new List <Entity>(); var order = 0; foreach (var sibling in FindSiblings(item).OrderBy(i => i.Order).ToArray()) { order += INCREMENT; if (sibling.Order == order) { continue; } var clone = sibling.Clone() as Entity; (clone as ISortable).Order = order; changed.Add(clone); } Database.Save(changed, saveBehaviour); } }
static void DoSave(IEntity entity, SaveBehaviour behaviour) { var mode = entity.IsNew ? SaveMode.Insert : SaveMode.Update; var asEntity = entity as Entity; if (mode == SaveMode.Update && (asEntity._ClonedFrom?.IsStale == true) && AnyOpenTransaction()) { throw new InvalidOperationException("This " + entity.GetType().Name + " instance in memory is out-of-date. " + "A clone of it is already updated in the transaction. It is not allowed to update the same instance multiple times in a transaction, because then the earlier updates would be overwriten by the older state of the instance in memory. \r\n\r\n" + @"BAD: Database.Update(myObject, x=> x.P1 = ...); // Note: this could also be nested inside another method that's called here instead. Database.Update(myObject, x=> x.P2 = ...); GOOD: Database.Update(myObject, x=> x.P1 = ...); myObject = Database.Reload(myObject); Database.Update(myObject, x=> x.P2 = ...);"); } if (EntityManager.IsImmutable(entity)) { throw new ArgumentException("An immutable record must be cloned before any modifications can be applied on it. Type=" + entity.GetType().FullName + ", Id=" + entity.GetId() + "."); } var dataProvider = GetProvider(entity); if (!IsSet(behaviour, SaveBehaviour.BypassValidation)) { EntityManager.RaiseOnValidating(entity as Entity, EventArgs.Empty); entity.Validate(); } else if (!dataProvider.SupportValidationBypassing()) { throw new ArgumentException(dataProvider.GetType().Name + " does not support bypassing validation."); } #region Raise saving event if (!IsSet(behaviour, SaveBehaviour.BypassSaving)) { var savingArgs = new System.ComponentModel.CancelEventArgs(); EntityManager.RaiseOnSaving(entity, savingArgs); if (savingArgs.Cancel) { Cache.Current.Remove(entity); return; } } #endregion if (!IsSet(behaviour, SaveBehaviour.BypassLogging) && !(entity is IApplicationEvent) && Config.Get("Log.Record.Application.Events", defaultValue: true)) { ApplicationEventManager.RecordSave(entity, mode); } dataProvider.Save(entity); Cache.Current.UpdateRowVersion(entity); if (mode == SaveMode.Update && asEntity?._ClonedFrom != null && AnyOpenTransaction()) { asEntity._ClonedFrom.IsStale = true; asEntity.IsStale = false; } if (mode == SaveMode.Insert) { EntityManager.SetSaved(entity); } Cache.Current.Remove(entity); if (Transaction.Current != null) { Transaction.Current.TransactionCompleted += (s, e) => { Cache.Current.Remove(entity); } } ; if (DbTransactionScope.Root != null) { DbTransactionScope.Root.OnTransactionCompleted(() => Cache.Current.Remove(entity)); } if (!(entity is IApplicationEvent)) { OnUpdated(new EventArgs <IEntity>(entity)); } if (!IsSet(behaviour, SaveBehaviour.BypassSaved)) { EntityManager.RaiseOnSaved(entity, new SaveEventArgs(mode)); } // OnSaved event handler might have read the object again and put it in the cache, which would // create invalid CachedReference objects. Cache.Current.Remove(entity); }
void Start() { _saveBehaviour = GameObject.FindObjectOfType <SaveBehaviour>(); _switch_scene = GameObject.FindObjectOfType <Switch_Scene>(); _setHighscore = GameObject.FindObjectOfType <SetHighscore>(); }
bool Not(SaveBehaviour behaviour) => !IsSet(Behaviour, behaviour);
public Task <T> Update <T>(T item, Action <T> action, SaveBehaviour behaviour) where T : IEntity => Update(item, action, null, behaviour);
/// <summary> /// Initialize instance of Database by injecting ICache dependency /// </summary> /// internal static bool IsSet(SaveBehaviour setting, SaveBehaviour behaviour) => (setting & behaviour) == behaviour;
public Task <T> Update <T>(T item, Func <T, Task> action, SaveBehaviour behaviour) where T : IEntity => Update(item, null, action, behaviour);