diff --git a/Storage/Storage.Test/ObjectTest.cs b/Storage/Storage.Test/ObjectTest.cs index 2f4d715..eaaef43 100644 --- a/Storage/Storage.Test/ObjectTest.cs +++ b/Storage/Storage.Test/ObjectTest.cs @@ -27,7 +27,7 @@ namespace LeanCloud.Test { { "hello", 1 }, { "world", 2 } }; - await obj.SaveAsync(fetchWhenSave: true); + await obj.SaveAsync(true); Assert.NotNull(obj.ObjectId); Assert.NotNull(obj.CreatedAt); Assert.NotNull(obj.UpdatedAt); @@ -214,7 +214,7 @@ namespace LeanCloud.Test { foreach (AVException ie in e.InnerExceptions) { TestContext.Out.WriteLine($"{ie.Code} : {ie.Message}"); } - } + } } [Test] diff --git a/Storage/Storage/Internal/Encoding/AVObjectCoder.cs b/Storage/Storage/Internal/Encoding/AVObjectCoder.cs index 28ca53a..1629084 100644 --- a/Storage/Storage/Internal/Encoding/AVObjectCoder.cs +++ b/Storage/Storage/Internal/Encoding/AVObjectCoder.cs @@ -39,30 +39,29 @@ namespace LeanCloud.Storage.Internal { IDictionary serverData = new Dictionary(); var mutableData = new Dictionary(data); - string objectId = extractFromDictionary(mutableData, "objectId", (obj) => + string objectId = ExtractFromDictionary(mutableData, "objectId", (obj) => { return obj as string; }); - DateTime? createdAt = extractFromDictionary(mutableData, "createdAt", (obj) => + DateTime? createdAt = ExtractFromDictionary(mutableData, "createdAt", (obj) => { return (DateTime)obj; }); - DateTime? updatedAt = extractFromDictionary(mutableData, "updatedAt", (obj) => + DateTime? updatedAt = ExtractFromDictionary(mutableData, "updatedAt", (obj) => { return (DateTime)obj; }); - if (mutableData.ContainsKey("ACL")) + AVACL acl = ExtractFromDictionary(mutableData, "ACL", (obj) => { - serverData["ACL"] = extractFromDictionary(mutableData, "ACL", (obj) => - { - return new AVACL(obj as IDictionary); - }); - } - string className = extractFromDictionary(mutableData, "className", obj => + return new AVACL(obj as IDictionary); + }); + + string className = ExtractFromDictionary(mutableData, "className", obj => { return obj as string; }); + if (createdAt != null && updatedAt == null) { updatedAt = createdAt; @@ -83,6 +82,7 @@ namespace LeanCloud.Storage.Internal return new MutableObjectState { ObjectId = objectId, + ACL = acl, CreatedAt = createdAt, UpdatedAt = updatedAt, ServerData = serverData, @@ -90,12 +90,11 @@ namespace LeanCloud.Storage.Internal }; } - private T extractFromDictionary(IDictionary data, string key, Func action) + private T ExtractFromDictionary(IDictionary data, string key, Func action) { - T result = default(T); - if (data.ContainsKey(key)) - { - result = action(data[key]); + T result = default; + if (data.TryGetValue(key, out object val)) { + result = action(val); data.Remove(key); } diff --git a/Storage/Storage/Internal/Object/State/MutableObjectState.cs b/Storage/Storage/Internal/Object/State/MutableObjectState.cs index cc9a70b..6682307 100644 --- a/Storage/Storage/Internal/Object/State/MutableObjectState.cs +++ b/Storage/Storage/Internal/Object/State/MutableObjectState.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Collections.Generic; namespace LeanCloud.Storage.Internal { @@ -7,6 +6,7 @@ namespace LeanCloud.Storage.Internal { public bool IsNew { get; set; } public string ClassName { get; set; } public string ObjectId { get; set; } + public AVACL ACL { get; set; } public DateTime? UpdatedAt { get; set; } public DateTime? CreatedAt { get; set; } @@ -42,6 +42,9 @@ namespace LeanCloud.Storage.Internal { if (other.ObjectId != null) { ObjectId = other.ObjectId; } + //if (other.ACL != null) { + + //} if (other.UpdatedAt != null) { UpdatedAt = other.UpdatedAt; } @@ -67,7 +70,7 @@ namespace LeanCloud.Storage.Internal { ObjectId = ObjectId, CreatedAt = CreatedAt, UpdatedAt = UpdatedAt, - ServerData = this.ToDictionary(t => t.Key, t => t.Value) + ServerData = new Dictionary(ServerData) }; } diff --git a/Storage/Storage/Internal/User/Controller/AVUserController.cs b/Storage/Storage/Internal/User/Controller/AVUserController.cs index 3b89d27..2853051 100644 --- a/Storage/Storage/Internal/User/Controller/AVUserController.cs +++ b/Storage/Storage/Internal/User/Controller/AVUserController.cs @@ -52,7 +52,7 @@ namespace LeanCloud.Storage.Internal { Path = path, Method = HttpMethod.Post, Content = new Dictionary { - { "authData", authData} + { "authData", authData } } }; var ret = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); diff --git a/Storage/Storage/Public/AVObject.cs b/Storage/Storage/Public/AVObject.cs index 74d7585..7813a1d 100644 --- a/Storage/Storage/Public/AVObject.cs +++ b/Storage/Storage/Public/AVObject.cs @@ -12,6 +12,10 @@ using System.Collections.Concurrent; namespace LeanCloud { public class AVObject : IEnumerable> { + static readonly HashSet RESERVED_KEYS = new HashSet { + "objectId", "ACL", "createdAt", "updatedAt" + }; + public string ClassName { get { return state.ClassName; @@ -25,7 +29,9 @@ namespace LeanCloud { } set { IsDirty = true; - SetObjectIdInternal(value); + MutateState(mutableClone => { + mutableClone.ObjectId = value; + }); } } @@ -35,7 +41,10 @@ namespace LeanCloud { return GetProperty(null, "ACL"); } set { - SetProperty(value, "ACL"); + IsDirty = true; + MutateState(mutableClone => { + mutableClone.ACL = value; + }); } } @@ -65,9 +74,6 @@ namespace LeanCloud { private readonly ConcurrentDictionary serverData = new ConcurrentDictionary(); private readonly ConcurrentDictionary estimatedData = new ConcurrentDictionary(); - private static readonly ThreadLocal isCreatingPointer = new ThreadLocal(() => false); - - private bool hasBeenFetched; private bool dirty; private IObjectState state; @@ -106,9 +112,6 @@ namespace LeanCloud { } public AVObject(string className) { - var isPointer = isCreatingPointer.Value; - isCreatingPointer.Value = false; - if (className == null) { throw new ArgumentException("You must specify a LeanCloud class name when creating a new AVObject."); } @@ -122,14 +125,7 @@ namespace LeanCloud { state = new MutableObjectState { ClassName = className }; - - if (!isPointer) { - hasBeenFetched = true; - IsDirty = true; - } else { - IsDirty = false; - hasBeenFetched = false; - } + IsDirty = true; } public static AVObject Create(string className) { @@ -137,18 +133,13 @@ namespace LeanCloud { } public static AVObject CreateWithoutData(string className, string objectId) { - isCreatingPointer.Value = true; - try { - var result = SubclassingController.Instantiate(className); - result.ObjectId = objectId; - result.IsDirty = false; - if (result.IsDirty) { - throw new InvalidOperationException("A AVObject subclass default constructor must not make changes to the object that cause it to be dirty."); - } - return result; - } finally { - isCreatingPointer.Value = false; + var result = SubclassingController.Instantiate(className); + result.ObjectId = objectId; + result.IsDirty = false; + if (result.IsDirty) { + throw new InvalidOperationException("A AVObject subclass default constructor must not make changes to the object that cause it to be dirty."); } + return result; } public static T Create() where T : AVObject { @@ -231,9 +222,6 @@ namespace LeanCloud { // Make a new serverData with fetched values. var newServerData = serverState.ToDictionary(t => t.Key, t => t.Value); - // Trigger handler based on serverState - hasBeenFetched |= serverState.ObjectId != null; - // We cache the fetched object because subsequent Save operation might flush // the fetched objects into Pointers. //IDictionary fetchedObject = CollectFetchedObjects(); @@ -367,21 +355,13 @@ namespace LeanCloud { private static Task> FetchAllInternalAsync( IEnumerable objects, bool force, CancellationToken cancellationToken) where T : AVObject { - if (objects.Any(obj => { return obj.state.ObjectId == null; })) { + if (objects.Any(obj => obj.state.ObjectId == null)) { throw new InvalidOperationException("You cannot fetch objects that haven't already been saved."); } - - var objectsToFetch = (from obj in objects - where force || !obj.IsDataAvailable - select obj).ToList(); - - if (objectsToFetch.Count == 0) { - return Task.FromResult(objects); - } - + // Do one Find for each class. var findsByClass = - (from obj in objectsToFetch + (from obj in objects group obj.ObjectId by obj.ClassName into classGroup where classGroup.Any() select new { @@ -398,13 +378,12 @@ namespace LeanCloud { } // Merge the data from the Finds into the input objects. - var pairs = from obj in objectsToFetch + var pairs = from obj in objects from result in findsByClass[obj.ClassName].Result where result.ObjectId == obj.ObjectId select new { obj, result }; foreach (var pair in pairs) { pair.obj.MergeFromObject(pair.result); - pair.obj.hasBeenFetched = true; } return objects; @@ -456,7 +435,7 @@ namespace LeanCloud { #endregion public virtual void Remove(string key) { - CheckKeyIsMutable(key); + CheckKeyValid(key); PerformOperation(key, AVDeleteOperation.Instance); } @@ -504,7 +483,7 @@ namespace LeanCloud { virtual public object this[string key] { get { - CheckGetAccess(key); + CheckKeyValid(key); if (!estimatedData.TryGetValue(key, out object value)) { value = state[key]; @@ -518,7 +497,7 @@ namespace LeanCloud { return value; } set { - CheckKeyIsMutable(key); + CheckKeyValid(key); Set(key, value); } } @@ -535,40 +514,54 @@ namespace LeanCloud { } public void Increment(string key, long amount) { - CheckKeyIsMutable(key); + CheckKeyValid(key); PerformOperation(key, new AVIncrementOperation(amount)); } public void Increment(string key, double amount) { - CheckKeyIsMutable(key); + CheckKeyValid(key); PerformOperation(key, new AVIncrementOperation(amount)); } #endregion public void AddToList(string key, object value) { + CheckKeyValid(key); AddRangeToList(key, new[] { value }); } public void AddRangeToList(string key, IEnumerable values) { - CheckKeyIsMutable(key); + CheckKeyValid(key); PerformOperation(key, new AVAddOperation(values.Cast())); } public void AddUniqueToList(string key, object value) { + CheckKeyValid(key); AddRangeUniqueToList(key, new object[] { value }); } public void AddRangeUniqueToList(string key, IEnumerable values) { - CheckKeyIsMutable(key); + CheckKeyValid(key); PerformOperation(key, new AVAddUniqueOperation(values.Cast())); } public void RemoveAllFromList(string key, IEnumerable values) { - CheckKeyIsMutable(key); + CheckKeyValid(key); PerformOperation(key, new AVRemoveOperation(values.Cast())); } + void CheckKeyValid(string key) { + if (string.IsNullOrEmpty(key)) { + throw new ArgumentNullException(nameof(key)); + } + if (key.StartsWith("_", StringComparison.CurrentCulture)) { + throw new ArgumentException("key should not start with _"); + } + if (RESERVED_KEYS.Contains(key)) { + throw new ArgumentException($"key: {key} is reserved by LeanCloud"); + } + } + public bool ContainsKey(string key) { return estimatedData.ContainsKey(key) || state.ContainsKey(key); } @@ -608,32 +601,6 @@ namespace LeanCloud { return false; } - public bool IsDataAvailable { - get { - return hasBeenFetched; - } - } - - private bool CheckIsDataAvailable(string key) { - return IsDataAvailable || estimatedData.ContainsKey(key); - } - - private void CheckGetAccess(string key) { - if (!CheckIsDataAvailable(key)) { - throw new InvalidOperationException("AVObject has no data for this key. Call FetchIfNeededAsync() to get the data."); - } - } - - private void CheckKeyIsMutable(string key) { - if (!IsKeyMutable(key)) { - throw new InvalidOperationException($"Cannot change the `{key}` property of a `{ClassName}` object."); - } - } - - protected virtual bool IsKeyMutable(string key) { - return true; - } - public bool HasSameId(AVObject other) { return other != null && object.Equals(ClassName, other.ClassName) && @@ -657,12 +624,6 @@ namespace LeanCloud { return dirty || operationDict.Count > 0; } - private void SetObjectIdInternal(string objectId) { - MutateState(mutableClone => { - mutableClone.ObjectId = objectId; - }); - } - public void Add(string key, object value) { if (ContainsKey(key)) { throw new ArgumentException("Key already exists", key);