diff --git a/Storage/Storage.Test/OperationTest.cs b/Storage/Storage.Test/OperationTest.cs new file mode 100644 index 0000000..51951bf --- /dev/null +++ b/Storage/Storage.Test/OperationTest.cs @@ -0,0 +1,88 @@ +using NUnit.Framework; +using System.Collections.Generic; +using System.Threading.Tasks; +using LeanCloud.Storage; + +namespace LeanCloud.Test { + public class OperationTest { + [SetUp] + public void SetUp() { + Logger.LogDelegate += Utils.Print; + LeanCloud.Initialize("ikGGdRE2YcVOemAaRbgp1xGJ-gzGzoHsz", "NUKmuRbdAhg1vrb2wexYo1jo", "https://ikggdre2.lc-cn-n1-shared.com"); + } + + [TearDown] + public void TearDown() { + Logger.LogDelegate -= Utils.Print; + } + + [Test] + public async Task Increment() { + LCQuery query = new LCQuery("Account"); + LCObject account = await query.Get("5e154a5143c257006fbff63f"); + TestContext.WriteLine(account["balance"]); + int balance = (int)account["balance"]; + account.Increment("balance", 100); + await account.Save(); + TestContext.WriteLine(account["balance"]); + Assert.AreEqual((int)account["balance"], balance + 100); + } + + [Test] + public async Task Decrement() { + LCQuery query = new LCQuery("Account"); + LCObject account = await query.Get("5e154a5143c257006fbff63f"); + TestContext.WriteLine(account["balance"]); + int balance = (int)account["balance"]; + account.Increment("balance", -10); + await account.Save(); + TestContext.WriteLine(account["balance"]); + Assert.AreEqual((int)account["balance"], balance - 10); + } + + [Test] + public async Task AddAndRemove() { + LCObject book = new LCObject("Book"); + book["pages"] = new List { 1, 2, 3, 4, 5 }; + await book.Save(); + + // add + book.Add("pages", 6); + await book.Save(); + TestContext.WriteLine(book["pages"]); + Assert.AreEqual((book["pages"] as List).Count, 6); + book.AddAll("pages", new List { 7, 8, 9 }); + await book.Save(); + TestContext.WriteLine(book["pages"]); + Assert.AreEqual((book["pages"] as List).Count, 9); + + // remove + book.Remove("pages", 2); + TestContext.WriteLine(book["pages"]); + await book.Save(); + Assert.AreEqual((book["pages"] as List).Count, 8); + book.RemoveAll("pages", new List { 1, 2, 3 }); + await book.Save(); + TestContext.WriteLine(book["pages"]); + Assert.AreEqual((book["pages"] as List).Count, 6); + } + + [Test] + public async Task AddUnique() { + LCObject book = new LCObject("Book"); + book["pages"] = new List { 1, 2, 3, 4, 5 }; + await book.Save(); + + // add + book.AddUnique("pages", 1); + await book.Save(); + TestContext.WriteLine(book["pages"]); + Assert.AreEqual((book["pages"] as List).Count, 5); + + book.AddAllUnique("pages", new List { 5, 6, 7 }); + await book.Save(); + TestContext.WriteLine(book["pages"]); + Assert.AreEqual((book["pages"] as List).Count, 7); + } + } +} diff --git a/Storage/Storage/Internal/Operation/LCAddOperation.cs b/Storage/Storage/Internal/Operation/LCAddOperation.cs index a734aed..0d1ae4d 100644 --- a/Storage/Storage/Internal/Operation/LCAddOperation.cs +++ b/Storage/Storage/Internal/Operation/LCAddOperation.cs @@ -28,7 +28,7 @@ namespace LeanCloud.Storage.Internal.Operation { object ILCOperation.Encode() { return new Dictionary { - { "op", "Add" }, + { "__op", "Add" }, { "objects", LCEncoder.Encode(valueList) } }; } diff --git a/Storage/Storage/Internal/Operation/LCAddUniqueOperation.cs b/Storage/Storage/Internal/Operation/LCAddUniqueOperation.cs index ce623e5..78953d4 100644 --- a/Storage/Storage/Internal/Operation/LCAddUniqueOperation.cs +++ b/Storage/Storage/Internal/Operation/LCAddUniqueOperation.cs @@ -36,7 +36,7 @@ namespace LeanCloud.Storage.Internal.Operation { set.UnionWith(oldValue as IEnumerable); } set.UnionWith(values); - return set; + return set.ToList(); } public IEnumerable GetNewObjectList() { diff --git a/Storage/Storage/Internal/Operation/LCDecrementOperation.cs b/Storage/Storage/Internal/Operation/LCDecrementOperation.cs deleted file mode 100644 index 5932ccf..0000000 --- a/Storage/Storage/Internal/Operation/LCDecrementOperation.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal.Operation { - internal class LCDecrementOperation : ILCOperation { - - - internal LCDecrementOperation() { - } - - public ILCOperation MergeWithPrevious(ILCOperation previousOp) { - throw new NotImplementedException(); - } - - public object Encode() { - throw new NotImplementedException(); - } - - public object Apply(object oldValue, string key) { - throw new NotImplementedException(); - } - - public IEnumerable GetNewObjectList() { - throw new NotImplementedException(); - } - } -} diff --git a/Storage/Storage/Internal/Operation/LCDeleteOperation.cs b/Storage/Storage/Internal/Operation/LCDeleteOperation.cs index bbc44a7..fdfa50e 100644 --- a/Storage/Storage/Internal/Operation/LCDeleteOperation.cs +++ b/Storage/Storage/Internal/Operation/LCDeleteOperation.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections; +using System.Collections; using System.Collections.Generic; namespace LeanCloud.Storage.Internal.Operation { diff --git a/Storage/Storage/Internal/Operation/LCIncrementOperation.cs b/Storage/Storage/Internal/Operation/LCIncrementOperation.cs deleted file mode 100644 index 644dac8..0000000 --- a/Storage/Storage/Internal/Operation/LCIncrementOperation.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System; -namespace LeanCloud.Storage.Internal.Operation { - public class LCIncrementOperation { - public LCIncrementOperation() { - } - } -} diff --git a/Storage/Storage/Internal/Operation/LCNumberOperation.cs b/Storage/Storage/Internal/Operation/LCNumberOperation.cs new file mode 100644 index 0000000..5fc5102 --- /dev/null +++ b/Storage/Storage/Internal/Operation/LCNumberOperation.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections; +using System.Linq; +using System.Collections.Generic; + +namespace LeanCloud.Storage.Internal.Operation { + internal class LCNumberOperation : ILCOperation { + static readonly IDictionary, Func> adders; + + static LCNumberOperation() { + adders = new Dictionary, Func> { + {new Tuple(typeof(sbyte), typeof(sbyte)), (left, right) => (sbyte)left + (sbyte)right}, + {new Tuple(typeof(sbyte), typeof(short)), (left, right) => (sbyte)left + (short)right}, + {new Tuple(typeof(sbyte), typeof(int)), (left, right) => (sbyte)left + (int)right}, + {new Tuple(typeof(sbyte), typeof(long)), (left, right) => (sbyte)left + (long)right}, + {new Tuple(typeof(sbyte), typeof(float)), (left, right) => (sbyte)left + (float)right}, + {new Tuple(typeof(sbyte), typeof(double)), (left, right) => (sbyte)left + (double)right}, + {new Tuple(typeof(sbyte), typeof(decimal)), (left, right) => (sbyte)left + (decimal)right}, + {new Tuple(typeof(byte), typeof(byte)), (left, right) => (byte)left + (byte)right}, + {new Tuple(typeof(byte), typeof(short)), (left, right) => (byte)left + (short)right}, + {new Tuple(typeof(byte), typeof(ushort)), (left, right) => (byte)left + (ushort)right}, + {new Tuple(typeof(byte), typeof(int)), (left, right) => (byte)left + (int)right}, + {new Tuple(typeof(byte), typeof(uint)), (left, right) => (byte)left + (uint)right}, + {new Tuple(typeof(byte), typeof(long)), (left, right) => (byte)left + (long)right}, + {new Tuple(typeof(byte), typeof(ulong)), (left, right) => (byte)left + (ulong)right}, + {new Tuple(typeof(byte), typeof(float)), (left, right) => (byte)left + (float)right}, + {new Tuple(typeof(byte), typeof(double)), (left, right) => (byte)left + (double)right}, + {new Tuple(typeof(byte), typeof(decimal)), (left, right) => (byte)left + (decimal)right}, + {new Tuple(typeof(short), typeof(short)), (left, right) => (short)left + (short)right}, + {new Tuple(typeof(short), typeof(int)), (left, right) => (short)left + (int)right}, + {new Tuple(typeof(short), typeof(long)), (left, right) => (short)left + (long)right}, + {new Tuple(typeof(short), typeof(float)), (left, right) => (short)left + (float)right}, + {new Tuple(typeof(short), typeof(double)), (left, right) => (short)left + (double)right}, + {new Tuple(typeof(short), typeof(decimal)), (left, right) => (short)left + (decimal)right}, + {new Tuple(typeof(ushort), typeof(ushort)), (left, right) => (ushort)left + (ushort)right}, + {new Tuple(typeof(ushort), typeof(int)), (left, right) => (ushort)left + (int)right}, + {new Tuple(typeof(ushort), typeof(uint)), (left, right) => (ushort)left + (uint)right}, + {new Tuple(typeof(ushort), typeof(long)), (left, right) => (ushort)left + (long)right}, + {new Tuple(typeof(ushort), typeof(ulong)), (left, right) => (ushort)left + (ulong)right}, + {new Tuple(typeof(ushort), typeof(float)), (left, right) => (ushort)left + (float)right}, + {new Tuple(typeof(ushort), typeof(double)), (left, right) => (ushort)left + (double)right}, + {new Tuple(typeof(ushort), typeof(decimal)), (left, right) => (ushort)left + (decimal)right}, + {new Tuple(typeof(int), typeof(int)), (left, right) => (int)left + (int)right}, + {new Tuple(typeof(int), typeof(long)), (left, right) => (int)left + (long)right}, + {new Tuple(typeof(int), typeof(float)), (left, right) => (int)left + (float)right}, + {new Tuple(typeof(int), typeof(double)), (left, right) => (int)left + (double)right}, + {new Tuple(typeof(int), typeof(decimal)), (left, right) => (int)left + (decimal)right}, + {new Tuple(typeof(uint), typeof(uint)), (left, right) => (uint)left + (uint)right}, + {new Tuple(typeof(uint), typeof(long)), (left, right) => (uint)left + (long)right}, + {new Tuple(typeof(uint), typeof(ulong)), (left, right) => (uint)left + (ulong)right}, + {new Tuple(typeof(uint), typeof(float)), (left, right) => (uint)left + (float)right}, + {new Tuple(typeof(uint), typeof(double)), (left, right) => (uint)left + (double)right}, + {new Tuple(typeof(uint), typeof(decimal)), (left, right) => (uint)left + (decimal)right}, + {new Tuple(typeof(long), typeof(long)), (left, right) => (long)left + (long)right}, + {new Tuple(typeof(long), typeof(float)), (left, right) => (long)left + (float)right}, + {new Tuple(typeof(long), typeof(double)), (left, right) => (long)left + (double)right}, + {new Tuple(typeof(long), typeof(decimal)), (left, right) => (long)left + (decimal)right}, + {new Tuple(typeof(char), typeof(char)), (left, right) => (char)left + (char)right}, + {new Tuple(typeof(char), typeof(ushort)), (left, right) => (char)left + (ushort)right}, + {new Tuple(typeof(char), typeof(int)), (left, right) => (char)left + (int)right}, + {new Tuple(typeof(char), typeof(uint)), (left, right) => (char)left + (uint)right}, + {new Tuple(typeof(char), typeof(long)), (left, right) => (char)left + (long)right}, + {new Tuple(typeof(char), typeof(ulong)), (left, right) => (char)left + (ulong)right}, + {new Tuple(typeof(char), typeof(float)), (left, right) => (char)left + (float)right}, + {new Tuple(typeof(char), typeof(double)), (left, right) => (char)left + (double)right}, + {new Tuple(typeof(char), typeof(decimal)), (left, right) => (char)left + (decimal)right}, + {new Tuple(typeof(float), typeof(float)), (left, right) => (float)left + (float)right}, + {new Tuple(typeof(float), typeof(double)), (left, right) => (float)left + (double)right}, + {new Tuple(typeof(ulong), typeof(ulong)), (left, right) => (ulong)left + (ulong)right}, + {new Tuple(typeof(ulong), typeof(float)), (left, right) => (ulong)left + (float)right}, + {new Tuple(typeof(ulong), typeof(double)), (left, right) => (ulong)left + (double)right}, + {new Tuple(typeof(ulong), typeof(decimal)), (left, right) => (ulong)left + (decimal)right}, + {new Tuple(typeof(double), typeof(double)), (left, right) => (double)left + (double)right}, + {new Tuple(typeof(decimal), typeof(decimal)), (left, right) => (decimal)left + (decimal)right} + }; + foreach (var pair in adders.Keys.ToList()) { + if (pair.Item1.Equals(pair.Item2)) { + continue; + } + var reversePair = new Tuple(pair.Item2, pair.Item1); + var func = adders[pair]; + adders[reversePair] = (left, right) => func(right, left); + } + } + + protected object value; + + internal LCNumberOperation(object value) { + this.value = value; + } + + public ILCOperation MergeWithPrevious(ILCOperation previousOp) { + if (previousOp is LCSetOperation || previousOp is LCDeleteOperation) { + return previousOp; + } + if (previousOp is LCNumberOperation incrementOp) { + object otherAmount = incrementOp.value; + return new LCNumberOperation(Add(otherAmount, value)); + } + return this; + } + + public object Encode() { + return new Dictionary { + { "__op", "Increment" }, + { "amount", value } + }; + } + + public object Apply(object oldValue, string key) { + oldValue = oldValue ?? 0; + return Add(oldValue, value); + } + + public IEnumerable GetNewObjectList() { + return null; + } + + static object Add(object obj1, object obj2) { + Func adder; + if (adders.TryGetValue(new Tuple(obj1.GetType(), obj2.GetType()), out adder)) { + return adder(obj1, obj2); + } + throw new InvalidCastException("Cannot add " + obj1.GetType() + " to " + obj2.GetType()); + } + } +} diff --git a/Storage/Storage/Internal/Operation/LCRemoveOperation.cs b/Storage/Storage/Internal/Operation/LCRemoveOperation.cs index 51d85ae..4a2f57a 100644 --- a/Storage/Storage/Internal/Operation/LCRemoveOperation.cs +++ b/Storage/Storage/Internal/Operation/LCRemoveOperation.cs @@ -8,10 +8,8 @@ namespace LeanCloud.Storage.Internal.Operation { internal class LCRemoveOperation : ILCOperation { List valueList; - internal LCRemoveOperation(IEnumerable values) { - valueList = new List { - values.Cast() - }; + internal LCRemoveOperation(IEnumerable values) { + valueList = new List(values); } public ILCOperation MergeWithPrevious(ILCOperation previousOp) { @@ -32,10 +30,8 @@ namespace LeanCloud.Storage.Internal.Operation { } public object Apply(object oldValue, string key) { - List list = new List(); - if (oldValue != null) { - list.AddRange(oldValue as IEnumerable); - } + List list = new List(oldValue as IEnumerable); + list.RemoveAll(item => valueList.Contains(item)); return list; } diff --git a/Storage/Storage/LCObject.cs b/Storage/Storage/LCObject.cs index 7019726..810ed9b 100644 --- a/Storage/Storage/LCObject.cs +++ b/Storage/Storage/LCObject.cs @@ -132,6 +132,10 @@ namespace LeanCloud.Storage { } } + /// + /// 删除字段 + /// + /// public void Unset(string key) { if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException(nameof(key)); @@ -140,6 +144,11 @@ namespace LeanCloud.Storage { ApplyOperation(key, deleteOp); } + /// + /// 增加关联 + /// + /// + /// public void AddRelation(string key, LCObject value) { if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException(nameof(key)); @@ -151,6 +160,11 @@ namespace LeanCloud.Storage { ApplyOperation(key, op); } + /// + /// 删除关联 + /// + /// + /// public void RemoveRelation(string key, LCObject value) { if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException(nameof(key)); @@ -162,6 +176,118 @@ namespace LeanCloud.Storage { ApplyOperation(key, op); } + /// + /// 增加数字属性值 + /// + /// + /// + public void Increment(string key, object value) { + if (string.IsNullOrEmpty(key)) { + throw new ArgumentNullException(nameof(key)); + } + if (value == null) { + throw new ArgumentNullException(nameof(value)); + } + LCNumberOperation op = new LCNumberOperation(value); + ApplyOperation(key, op); + } + + /// + /// 在数组属性中增加一个元素 + /// + /// + /// + public void Add(string key, object value) { + if (string.IsNullOrEmpty(key)) { + throw new ArgumentNullException(nameof(key)); + } + if (value == null) { + throw new ArgumentNullException(nameof(value)); + } + LCAddOperation op = new LCAddOperation(new List { value }); + ApplyOperation(key, op); + } + + /// + /// 在数组属性中增加一组元素 + /// + /// + /// + public void AddAll(string key, IEnumerable values) { + if (string.IsNullOrEmpty(key)) { + throw new ArgumentNullException(nameof(key)); + } + if (values == null) { + throw new ArgumentNullException(nameof(values)); + } + LCAddOperation op = new LCAddOperation(new List(values.Cast())); + ApplyOperation(key, op); + } + + /// + /// 在数组属性中增加一个唯一元素 + /// + /// + /// + public void AddUnique(string key, object value) { + if (string.IsNullOrEmpty(key)) { + throw new ArgumentNullException(nameof(key)); + } + if (value == null) { + throw new ArgumentNullException(nameof(value)); + } + LCAddUniqueOperation op = new LCAddUniqueOperation(new List { value }); + ApplyOperation(key, op); + } + + /// + /// 在数组属性中增加一组唯一元素 + /// + /// + /// + public void AddAllUnique(string key, IEnumerable values) { + if (string.IsNullOrEmpty(key)) { + throw new ArgumentNullException(nameof(key)); + } + if (values == null) { + throw new ArgumentNullException(nameof(values)); + } + LCAddUniqueOperation op = new LCAddUniqueOperation(new List(values.Cast())); + ApplyOperation(key, op); + } + + /// + /// 移除某个元素 + /// + /// + /// + public void Remove(string key, object value) { + if (string.IsNullOrEmpty(key)) { + throw new ArgumentNullException(nameof(key)); + } + if (value == null) { + throw new ArgumentNullException(nameof(value)); + } + LCRemoveOperation op = new LCRemoveOperation(new List { value }); + ApplyOperation(key, op); + } + + /// + /// 移除一组元素 + /// + /// + /// + public void RemoveAll(string key, IEnumerable values) { + if (string.IsNullOrEmpty(key)) { + throw new ArgumentNullException(nameof(key)); + } + if (values == null) { + throw new ArgumentNullException(nameof(values)); + } + LCRemoveOperation op = new LCRemoveOperation(new List(values.Cast())); + ApplyOperation(key, op); + } + static async Task SaveBatches(Stack batches) { while (batches.Count > 0) { LCBatch batch = batches.Pop();