Пример #1
0
        private string link;                                        // The entity link string or NULL

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="propertyNameMap">
        /// The dictionary mapping JSON property names to their entity equivalents.
        /// </param>
        /// <param name="context">The optional entity context.</param>
        public DynamicEntity(IDictionary <string, string> propertyNameMap, IDynamicEntityContext context = null)
        {
            Covenant.Requires <ArgumentNullException>(propertyNameMap != null);

            this.propertyNameMap = propertyNameMap;
            this.context         = context;
            this.self            = (IDynamicEntity)this;
            this.parent          = null;
        }
Пример #2
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="parentEntity">The <see cref="IDynamicEntity"/> that owns this mapper.</param>
        /// <param name="jsonName">The JSON property name.</param>
        /// <param name="propertyName">The entity property name.</param>
        /// <param name="context">The <see cref="IDynamicEntityContext"/> or <c>null</c>.</param>
        public SimpleMapper(IDynamicEntity parentEntity, string jsonName, string propertyName, IDynamicEntityContext context)
        {
            Covenant.Requires <ArgumentNullException>(parentEntity != null);
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(jsonName));
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(propertyName));

            this.parentEntity = parentEntity;
            this.context      = context;
            this.JsonName     = jsonName;
            this.PropertyName = propertyName;
            this.property     = null;
        }
Пример #3
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="parentEntity">The <see cref="IDynamicEntity"/> that owns this list.</param>
        /// <param name="context">The <see cref="IDynamicEntityContext"/> or <c>null</c>.</param>
        /// <param name="jArray">The underlying <see cref="jArray"/>.</param>
        /// <param name="items">The initial items or <c>null</c> to initialize from <paramref name="jArray"/>.</param>
        public EntityListWrapper(IDynamicEntity parentEntity, IDynamicEntityContext context, JArray jArray, IEnumerable <TEntity> items)
        {
            Covenant.Requires <ArgumentNullException>(parentEntity != null);
            Covenant.Requires <ArgumentNullException>(jArray != null);

            this.parentEntity       = parentEntity;
            this.context            = context;
            this.jArray             = jArray;
            this.itemChangedHandler = new EventHandler <EventArgs>(OnItemChanged);
            this.list = new ObservableCollection <TEntity>();

            if (items != null)
            {
                Covenant.Assert(jArray.Count == 0);

                foreach (var item in items)
                {
                    Add(item);
                }
            }
            else
            {
                foreach (var jToken in jArray)
                {
                    if (jToken.Type == JTokenType.Object)
                    {
                        var item = DynamicEntity.Create <TEntity>((JObject)jToken, context);

                        item.Changed += itemChangedHandler;   // We need to bubble up nested change events
                        list.Add(item);
                    }
                    else
                    {
                        list.Add(null); // Ignore anything that's not an object.
                    }
                }
            }

            // We're going to listen to our own collection changed event to
            // bubble them up.

            list.CollectionChanged +=
                (s, a) =>
            {
                CollectionChanged?.Invoke(this, a);
                parentEntity._OnChanged();
            };
        }
Пример #4
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="parentEntity">The <see cref="IDynamicEntity"/> that owns this list.</param>
        /// <param name="context">The <see cref="IDynamicEntityContext"/> or <c>null</c>.</param>
        /// <param name="jArray">The underlying <see cref="jArray"/>.</param>
        /// <param name="items">The initial items or <c>null</c> to initialize from <paramref name="jArray"/>.</param>
        public LinkListWrapper(IDynamicEntity parentEntity, IDynamicEntityContext context, JArray jArray, IEnumerable <TEntity> items)
        {
            Covenant.Requires <ArgumentNullException>(parentEntity != null);
            Covenant.Requires <ArgumentNullException>(jArray != null);

            this.parentEntity = parentEntity;
            this.context      = context;
            this.jArray       = jArray;
            this.list         = new ObservableCollection <LinkState>();

            if (items != null)
            {
                Covenant.Assert(jArray.Count == 0);

                foreach (var item in items)
                {
                    Add(item);
                }
            }
            else
            {
                foreach (var token in jArray)
                {
                    list.Add(new LinkState()
                    {
                        Link = GetLink(token)
                    });
                }
            }

            // We're going to listen to our own collection changed event to
            // bubble them up.

            list.CollectionChanged +=
                (s, a) =>
            {
                if (!notifyDisabled)
                {
                    CollectionChanged?.Invoke(this, a);
                    parentEntity._OnChanged();
                }
            };
        }
Пример #5
0
        /// <summary>
        /// Constructs a <typeparamref name="TEntity"/> from a <see cref="JObject"/>.
        /// </summary>
        /// <typeparam name="TEntity">The entity type.</typeparam>
        /// <param name="jObject">The backing <see cref="JObject"/>.</param>
        /// <param name="context">The <see cref="IDynamicEntityContext"/> or <c>null</c>.</param>
        /// <returns>The new <typeparamref name="TEntity"/>.</returns>
        /// <remarks>
        /// <para>
        /// This method is capable of instantiating derived entities in a future-proof
        /// manner.  This is possible for entities whose defining <c>interface</c>s were
        /// tagged with a <see cref="DynamicEntityAttribute.Type"/> value.  This is
        /// set to a unique identifier for the type within the application domain.
        /// </para>
        /// <para>
        /// Entities serialize their own type identifier as well as the
        /// identifiers for any inherited types to the internal <see cref="DynamicEntity.EntityTypePathName"/>
        /// JSON property.  The type identifiers are formatted into a list of
        /// colon (<b>:</b>) separated values with the current type's identifier
        /// listed first, followed by its parent's identifier and so on, all the
        /// way to the root entity's identifier.
        /// </para>
        /// <para>
        /// This identifier list provides the information necessary to dynamically
        /// instantiate the derived entity that best fits the data.  For example,
        /// say you release a product that defines a base <c>IProduct</c> interface
        /// with <c>ICandy</c> derived from it.  IProduct defines a <b>Name</b> as
        /// property and ICandy adds <b>Calories</b>.
        /// </para>
        /// <para>
        /// An application can persist a base IProduct instance to the database
        /// as well as a derived ICandy.  Calling this method to load the base
        /// IProduct or ICandy document will simply load it.  Calling this method
        /// to load IProduct but passing the ICandy document, will actually create
        /// an ICandy instance but return it cast to the base type (IProduct).
        /// </para>
        /// <para>
        /// Applications can test entity types using the usual C# <c>is</c>/<c>as</c>
        /// operators or switch on the entity's type property, if one was tagged
        /// using <see cref="DynamicEntityPropertyAttribute.IsTypeProperty"/>.
        /// </para>
        /// <para>
        /// This also enables future-proofing.  Say version 1.0 of an application only
        /// knew about IProduct and ICandy.  Then, version 2.0 is released that adds
        /// <c>IBeer</c> which also derives from IProduct and persists some beer to
        /// a database.  Loading an IBeer document as an IProduct from version 1.0
        /// will still work, even though v1 is not aware of this type.  An IProduct
        /// entity will be returned (what else can be done), but the IBeer properties
        /// will still be loaded into the underlying <c>JObject</c>.
        /// </para>
        /// </remarks>
        public static TEntity Create <TEntity>(JObject jObject, IDynamicEntityContext context)
            where TEntity : class, IDynamicEntity, new()
        {
            Covenant.Requires <ArgumentNullException>(jObject != null);

            EntityCreateDelegate creator = null;

            string[]   typePath = null;
            JToken     typePathToken;
            CreateInfo createInfo;

            if (jObject.TryGetValue(EntityTypePathName, out typePathToken) && typePathToken.Type == JTokenType.String)
            {
                typePath = ((string)typePathToken).Split(colonSplitter);
            }

            var hasTypePath = typePath != null && typePath.Length > 0;

            if (!hasTypePath)
            {
                // The document doesn't have a type path property so we're going to
                // use the creator registered for the type.

                typeToEntityCreator.TryGetValue(typeof(TEntity), out creator);
            }
            else
            {
                // Try each type identifier from the beginning of the path and use the
                // first registered creator we can find.  This will return the derived
                // class that best fits the data.

                foreach (var typeIdentifier in typePath)
                {
                    if (typeIdentifierToEntityCreator.TryGetValue(typeIdentifier, out createInfo))
                    {
                        creator = createInfo.Creator;
                        break;
                    }
                }
            }

            if (creator == null)
            {
                if (hasTypePath)
                {
                    throw new InvalidOperationException($"Entity type [{typeof(TEntity).FullName }] has not registered an entity creation function.  Make sure you have registered the generated entities and binder document types.");
                }
                else
                {
                    throw new InvalidOperationException($"Could not map any type from the [{typePath}] type path to an entity type.  Make sure you have registered the generated entities and binder document types.");
                }
            }

            var untypedEntity = creator(jObject, context);

            if (untypedEntity == null)
            {
                return(null);
            }

            var entity = untypedEntity as TEntity;

            if (entity == null)
            {
                throw new InvalidCastException($"Unable to cast [{untypedEntity.GetType().FullName}] to [{typeof(TEntity).FullName}].  Possible data corruption or invalid type path [{typePath}].");
            }

            return(entity);
        }