* ObjectTest.cs: chore: 继续简化代码

* AVObject.cs:
* AVObjectCoder.cs:
* MutableObjectState.cs:
* AVUserController.cs:
oneRain 2019-12-12 17:11:17 +08:00
parent a9ce795c45
commit fcb513aee3
5 changed files with 66 additions and 103 deletions

View File

@ -27,7 +27,7 @@ namespace LeanCloud.Test {
{ "hello", 1 }, { "hello", 1 },
{ "world", 2 } { "world", 2 }
}; };
await obj.SaveAsync(fetchWhenSave: true); await obj.SaveAsync(true);
Assert.NotNull(obj.ObjectId); Assert.NotNull(obj.ObjectId);
Assert.NotNull(obj.CreatedAt); Assert.NotNull(obj.CreatedAt);
Assert.NotNull(obj.UpdatedAt); Assert.NotNull(obj.UpdatedAt);

View File

@ -39,30 +39,29 @@ namespace LeanCloud.Storage.Internal
{ {
IDictionary<string, object> serverData = new Dictionary<string, object>(); IDictionary<string, object> serverData = new Dictionary<string, object>();
var mutableData = new Dictionary<string, object>(data); var mutableData = new Dictionary<string, object>(data);
string objectId = extractFromDictionary<string>(mutableData, "objectId", (obj) => string objectId = ExtractFromDictionary<string>(mutableData, "objectId", (obj) =>
{ {
return obj as string; return obj as string;
}); });
DateTime? createdAt = extractFromDictionary<DateTime?>(mutableData, "createdAt", (obj) => DateTime? createdAt = ExtractFromDictionary<DateTime?>(mutableData, "createdAt", (obj) =>
{ {
return (DateTime)obj; return (DateTime)obj;
}); });
DateTime? updatedAt = extractFromDictionary<DateTime?>(mutableData, "updatedAt", (obj) => DateTime? updatedAt = ExtractFromDictionary<DateTime?>(mutableData, "updatedAt", (obj) =>
{ {
return (DateTime)obj; return (DateTime)obj;
}); });
if (mutableData.ContainsKey("ACL")) AVACL acl = ExtractFromDictionary(mutableData, "ACL", (obj) =>
{
serverData["ACL"] = extractFromDictionary<AVACL>(mutableData, "ACL", (obj) =>
{ {
return new AVACL(obj as IDictionary<string, object>); return new AVACL(obj as IDictionary<string, object>);
}); });
}
string className = extractFromDictionary<string>(mutableData, "className", obj => string className = ExtractFromDictionary(mutableData, "className", obj =>
{ {
return obj as string; return obj as string;
}); });
if (createdAt != null && updatedAt == null) if (createdAt != null && updatedAt == null)
{ {
updatedAt = createdAt; updatedAt = createdAt;
@ -83,6 +82,7 @@ namespace LeanCloud.Storage.Internal
return new MutableObjectState return new MutableObjectState
{ {
ObjectId = objectId, ObjectId = objectId,
ACL = acl,
CreatedAt = createdAt, CreatedAt = createdAt,
UpdatedAt = updatedAt, UpdatedAt = updatedAt,
ServerData = serverData, ServerData = serverData,
@ -90,12 +90,11 @@ namespace LeanCloud.Storage.Internal
}; };
} }
private T extractFromDictionary<T>(IDictionary<string, object> data, string key, Func<object, T> action) private T ExtractFromDictionary<T>(IDictionary<string, object> data, string key, Func<object, T> action)
{ {
T result = default(T); T result = default;
if (data.ContainsKey(key)) if (data.TryGetValue(key, out object val)) {
{ result = action(val);
result = action(data[key]);
data.Remove(key); data.Remove(key);
} }

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
namespace LeanCloud.Storage.Internal { namespace LeanCloud.Storage.Internal {
@ -7,6 +6,7 @@ namespace LeanCloud.Storage.Internal {
public bool IsNew { get; set; } public bool IsNew { get; set; }
public string ClassName { get; set; } public string ClassName { get; set; }
public string ObjectId { get; set; } public string ObjectId { get; set; }
public AVACL ACL { get; set; }
public DateTime? UpdatedAt { get; set; } public DateTime? UpdatedAt { get; set; }
public DateTime? CreatedAt { get; set; } public DateTime? CreatedAt { get; set; }
@ -42,6 +42,9 @@ namespace LeanCloud.Storage.Internal {
if (other.ObjectId != null) { if (other.ObjectId != null) {
ObjectId = other.ObjectId; ObjectId = other.ObjectId;
} }
//if (other.ACL != null) {
//}
if (other.UpdatedAt != null) { if (other.UpdatedAt != null) {
UpdatedAt = other.UpdatedAt; UpdatedAt = other.UpdatedAt;
} }
@ -67,7 +70,7 @@ namespace LeanCloud.Storage.Internal {
ObjectId = ObjectId, ObjectId = ObjectId,
CreatedAt = CreatedAt, CreatedAt = CreatedAt,
UpdatedAt = UpdatedAt, UpdatedAt = UpdatedAt,
ServerData = this.ToDictionary(t => t.Key, t => t.Value) ServerData = new Dictionary<string, object>(ServerData)
}; };
} }

View File

@ -52,7 +52,7 @@ namespace LeanCloud.Storage.Internal {
Path = path, Path = path,
Method = HttpMethod.Post, Method = HttpMethod.Post,
Content = new Dictionary<string, object> { Content = new Dictionary<string, object> {
{ "authData", authData} { "authData", authData }
} }
}; };
var ret = await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command); var ret = await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command);

View File

@ -12,6 +12,10 @@ using System.Collections.Concurrent;
namespace LeanCloud { namespace LeanCloud {
public class AVObject : IEnumerable<KeyValuePair<string, object>> { public class AVObject : IEnumerable<KeyValuePair<string, object>> {
static readonly HashSet<string> RESERVED_KEYS = new HashSet<string> {
"objectId", "ACL", "createdAt", "updatedAt"
};
public string ClassName { public string ClassName {
get { get {
return state.ClassName; return state.ClassName;
@ -25,7 +29,9 @@ namespace LeanCloud {
} }
set { set {
IsDirty = true; IsDirty = true;
SetObjectIdInternal(value); MutateState(mutableClone => {
mutableClone.ObjectId = value;
});
} }
} }
@ -35,7 +41,10 @@ namespace LeanCloud {
return GetProperty<AVACL>(null, "ACL"); return GetProperty<AVACL>(null, "ACL");
} }
set { set {
SetProperty(value, "ACL"); IsDirty = true;
MutateState(mutableClone => {
mutableClone.ACL = value;
});
} }
} }
@ -65,9 +74,6 @@ namespace LeanCloud {
private readonly ConcurrentDictionary<string, object> serverData = new ConcurrentDictionary<string, object>(); private readonly ConcurrentDictionary<string, object> serverData = new ConcurrentDictionary<string, object>();
private readonly ConcurrentDictionary<string, object> estimatedData = new ConcurrentDictionary<string, object>(); private readonly ConcurrentDictionary<string, object> estimatedData = new ConcurrentDictionary<string, object>();
private static readonly ThreadLocal<bool> isCreatingPointer = new ThreadLocal<bool>(() => false);
private bool hasBeenFetched;
private bool dirty; private bool dirty;
private IObjectState state; private IObjectState state;
@ -106,9 +112,6 @@ namespace LeanCloud {
} }
public AVObject(string className) { public AVObject(string className) {
var isPointer = isCreatingPointer.Value;
isCreatingPointer.Value = false;
if (className == null) { if (className == null) {
throw new ArgumentException("You must specify a LeanCloud class name when creating a new AVObject."); throw new ArgumentException("You must specify a LeanCloud class name when creating a new AVObject.");
} }
@ -122,14 +125,7 @@ namespace LeanCloud {
state = new MutableObjectState { state = new MutableObjectState {
ClassName = className ClassName = className
}; };
if (!isPointer) {
hasBeenFetched = true;
IsDirty = true; IsDirty = true;
} else {
IsDirty = false;
hasBeenFetched = false;
}
} }
public static AVObject Create(string className) { public static AVObject Create(string className) {
@ -137,8 +133,6 @@ namespace LeanCloud {
} }
public static AVObject CreateWithoutData(string className, string objectId) { public static AVObject CreateWithoutData(string className, string objectId) {
isCreatingPointer.Value = true;
try {
var result = SubclassingController.Instantiate(className); var result = SubclassingController.Instantiate(className);
result.ObjectId = objectId; result.ObjectId = objectId;
result.IsDirty = false; result.IsDirty = false;
@ -146,9 +140,6 @@ namespace LeanCloud {
throw new InvalidOperationException("A AVObject subclass default constructor must not make changes to the object that cause it to be dirty."); throw new InvalidOperationException("A AVObject subclass default constructor must not make changes to the object that cause it to be dirty.");
} }
return result; return result;
} finally {
isCreatingPointer.Value = false;
}
} }
public static T Create<T>() where T : AVObject { public static T Create<T>() where T : AVObject {
@ -231,9 +222,6 @@ namespace LeanCloud {
// Make a new serverData with fetched values. // Make a new serverData with fetched values.
var newServerData = serverState.ToDictionary(t => t.Key, t => t.Value); 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 // We cache the fetched object because subsequent Save operation might flush
// the fetched objects into Pointers. // the fetched objects into Pointers.
//IDictionary<string, AVObject> fetchedObject = CollectFetchedObjects(); //IDictionary<string, AVObject> fetchedObject = CollectFetchedObjects();
@ -367,21 +355,13 @@ namespace LeanCloud {
private static Task<IEnumerable<T>> FetchAllInternalAsync<T>( private static Task<IEnumerable<T>> FetchAllInternalAsync<T>(
IEnumerable<T> objects, bool force, CancellationToken cancellationToken) where T : AVObject { IEnumerable<T> 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."); 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. // Do one Find for each class.
var findsByClass = var findsByClass =
(from obj in objectsToFetch (from obj in objects
group obj.ObjectId by obj.ClassName into classGroup group obj.ObjectId by obj.ClassName into classGroup
where classGroup.Any() where classGroup.Any()
select new { select new {
@ -398,13 +378,12 @@ namespace LeanCloud {
} }
// Merge the data from the Finds into the input objects. // 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 from result in findsByClass[obj.ClassName].Result
where result.ObjectId == obj.ObjectId where result.ObjectId == obj.ObjectId
select new { obj, result }; select new { obj, result };
foreach (var pair in pairs) { foreach (var pair in pairs) {
pair.obj.MergeFromObject(pair.result); pair.obj.MergeFromObject(pair.result);
pair.obj.hasBeenFetched = true;
} }
return objects; return objects;
@ -456,7 +435,7 @@ namespace LeanCloud {
#endregion #endregion
public virtual void Remove(string key) { public virtual void Remove(string key) {
CheckKeyIsMutable(key); CheckKeyValid(key);
PerformOperation(key, AVDeleteOperation.Instance); PerformOperation(key, AVDeleteOperation.Instance);
} }
@ -504,7 +483,7 @@ namespace LeanCloud {
virtual public object this[string key] { virtual public object this[string key] {
get { get {
CheckGetAccess(key); CheckKeyValid(key);
if (!estimatedData.TryGetValue(key, out object value)) { if (!estimatedData.TryGetValue(key, out object value)) {
value = state[key]; value = state[key];
@ -518,7 +497,7 @@ namespace LeanCloud {
return value; return value;
} }
set { set {
CheckKeyIsMutable(key); CheckKeyValid(key);
Set(key, value); Set(key, value);
} }
} }
@ -535,40 +514,54 @@ namespace LeanCloud {
} }
public void Increment(string key, long amount) { public void Increment(string key, long amount) {
CheckKeyIsMutable(key); CheckKeyValid(key);
PerformOperation(key, new AVIncrementOperation(amount)); PerformOperation(key, new AVIncrementOperation(amount));
} }
public void Increment(string key, double amount) { public void Increment(string key, double amount) {
CheckKeyIsMutable(key); CheckKeyValid(key);
PerformOperation(key, new AVIncrementOperation(amount)); PerformOperation(key, new AVIncrementOperation(amount));
} }
#endregion #endregion
public void AddToList(string key, object value) { public void AddToList(string key, object value) {
CheckKeyValid(key);
AddRangeToList(key, new[] { value }); AddRangeToList(key, new[] { value });
} }
public void AddRangeToList<T>(string key, IEnumerable<T> values) { public void AddRangeToList<T>(string key, IEnumerable<T> values) {
CheckKeyIsMutable(key); CheckKeyValid(key);
PerformOperation(key, new AVAddOperation(values.Cast<object>())); PerformOperation(key, new AVAddOperation(values.Cast<object>()));
} }
public void AddUniqueToList(string key, object value) { public void AddUniqueToList(string key, object value) {
CheckKeyValid(key);
AddRangeUniqueToList(key, new object[] { value }); AddRangeUniqueToList(key, new object[] { value });
} }
public void AddRangeUniqueToList<T>(string key, IEnumerable<T> values) { public void AddRangeUniqueToList<T>(string key, IEnumerable<T> values) {
CheckKeyIsMutable(key); CheckKeyValid(key);
PerformOperation(key, new AVAddUniqueOperation(values.Cast<object>())); PerformOperation(key, new AVAddUniqueOperation(values.Cast<object>()));
} }
public void RemoveAllFromList<T>(string key, IEnumerable<T> values) { public void RemoveAllFromList<T>(string key, IEnumerable<T> values) {
CheckKeyIsMutable(key); CheckKeyValid(key);
PerformOperation(key, new AVRemoveOperation(values.Cast<object>())); PerformOperation(key, new AVRemoveOperation(values.Cast<object>()));
} }
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) { public bool ContainsKey(string key) {
return estimatedData.ContainsKey(key) || state.ContainsKey(key); return estimatedData.ContainsKey(key) || state.ContainsKey(key);
} }
@ -608,32 +601,6 @@ namespace LeanCloud {
return false; 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) { public bool HasSameId(AVObject other) {
return other != null && return other != null &&
object.Equals(ClassName, other.ClassName) && object.Equals(ClassName, other.ClassName) &&
@ -657,12 +624,6 @@ namespace LeanCloud {
return dirty || operationDict.Count > 0; return dirty || operationDict.Count > 0;
} }
private void SetObjectIdInternal(string objectId) {
MutateState(mutableClone => {
mutableClone.ObjectId = objectId;
});
}
public void Add(string key, object value) { public void Add(string key, object value) {
if (ContainsKey(key)) { if (ContainsKey(key)) {
throw new ArgumentException("Key already exists", key); throw new ArgumentException("Key already exists", key);