using LeanCloud.Storage.Internal; using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; namespace LeanCloud { /// /// A common base class for AVRelations. /// [EditorBrowsable(EditorBrowsableState.Never)] public abstract class AVRelationBase : IJsonConvertible { private AVObject parent; private string key; private string targetClassName; internal AVRelationBase(AVObject parent, string key) { EnsureParentAndKey(parent, key); } internal AVRelationBase(AVObject parent, string key, string targetClassName) : this(parent, key) { this.targetClassName = targetClassName; } internal static IObjectSubclassingController SubclassingController { get { return AVPlugins.Instance.SubclassingController; } } internal void EnsureParentAndKey(AVObject parent, string key) { this.parent = this.parent ?? parent; this.key = this.key ?? key; Debug.Assert(this.parent == parent, "Relation retrieved from two different objects"); Debug.Assert(this.key == key, "Relation retrieved from two different keys"); } internal void Add(AVObject obj) { var change = new AVRelationOperation(new[] { obj }, null); parent.PerformOperation(key, change); targetClassName = change.TargetClassName; } internal void Remove(AVObject obj) { var change = new AVRelationOperation(null, new[] { obj }); parent.PerformOperation(key, change); targetClassName = change.TargetClassName; } IDictionary IJsonConvertible.ToJSON() { return new Dictionary { { "__type", "Relation"}, { "className", targetClassName} }; } internal AVQuery GetQuery() where T : AVObject { if (targetClassName != null) { return new AVQuery(targetClassName) .WhereRelatedTo(parent, key); } return new AVQuery(parent.ClassName) .RedirectClassName(key) .WhereRelatedTo(parent, key); } internal AVQuery GetReverseQuery(T target) where T : AVObject { if (target.ObjectId == null) { throw new ArgumentNullException("target.ObjectId", "can not query a relation without target ObjectId."); } return new AVQuery(parent.ClassName).WhereEqualTo(key, target); } internal string TargetClassName { get { return targetClassName; } set { targetClassName = value; } } /// /// Produces the proper AVRelation<T> instance for the given classname. /// internal static AVRelationBase CreateRelation(AVObject parent, string key, string targetClassName) { var targetType = SubclassingController.GetType(targetClassName) ?? typeof(AVObject); Expression>> createRelationExpr = () => CreateRelation(parent, key, targetClassName); var createRelationMethod = ((MethodCallExpression)createRelationExpr.Body) .Method .GetGenericMethodDefinition() .MakeGenericMethod(targetType); return (AVRelationBase)createRelationMethod.Invoke(null, new object[] { parent, key, targetClassName }); } private static AVRelation CreateRelation(AVObject parent, string key, string targetClassName) where T : AVObject { return new AVRelation(parent, key, targetClassName); } } /// /// Provides access to all of the children of a many-to-many relationship. Each instance of /// AVRelation is associated with a particular parent and key. /// /// The type of the child objects. public sealed class AVRelation : AVRelationBase where T : AVObject { internal AVRelation(AVObject parent, string key) : base(parent, key) { } internal AVRelation(AVObject parent, string key, string targetClassName) : base(parent, key, targetClassName) { } /// /// Adds an object to this relation. The object must already have been saved. /// /// The object to add. public void Add(T obj) { base.Add(obj); } /// /// Removes an object from this relation. The object must already have been saved. /// /// The object to remove. public void Remove(T obj) { base.Remove(obj); } /// /// Gets a query that can be used to query the objects in this relation. /// public AVQuery Query { get { return base.GetQuery(); } } } }