* ObjectTest.cs: chore: 继续简化代码
* AVObject.cs: * AVObjectCoder.cs: * MutableObjectState.cs: * AVUserController.cs:
parent
a9ce795c45
commit
fcb513aee3
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue