public static TResult BindSingleValueToArray <TResult>(this EntityProperty value, Type arrayType,
                                                               Func <object, TResult> onBound,
                                                               Func <TResult> onFailedToBind)
        {
            #region Refs

            object ComposeFromBase <TBase>(Type composedType, Type genericCompositionType,
                                           Func <Type, TBase, object> instantiate)
            {
                return(value.BindSingleValueToArray(typeof(TBase),
                                                    objects =>
                {
                    var guids = (TBase[])objects;
                    var resourceType = arrayType.GenericTypeArguments.First();
                    var instantiatableType = genericCompositionType.MakeGenericType(resourceType);

                    var refs = guids
                               .Select(
                        guidValue =>
                    {
                        var instance = instantiate(instantiatableType, guidValue);
                        // Activator.CreateInstance(instantiatableType, new object[] { guidValue });
                        return instance;
                    })
                               .ToArray();
                    var typedRefs = refs.CastArray(arrayType);
                    return typedRefs;
                },
                                                    () => throw new Exception("BindArray failed to bind to Guids?")));
            }

            if (arrayType.IsSubClassOfGeneric(typeof(IRef <>)))
            {
                var values = ComposeFromBase <Guid>(typeof(IRef <>), typeof(EastFive.Ref <>),
                                                    (instantiatableType, guidValue) => Activator.CreateInstance(instantiatableType, new object[] { guidValue }));
                return(onBound(values));
            }

            object ComposeOptionalFromBase <TBase>(Type composedType, Type genericCompositionType, Type optionalBaseType)
            {
                var values = ComposeFromBase <Guid?>(composedType, genericCompositionType,
                                                     (instantiatableType, guidValueMaybe) =>
                {
                    if (!guidValueMaybe.HasValue)
                    {
                        return(Activator.CreateInstance(instantiatableType, new object[] { }));
                    }
                    var guidValue             = guidValueMaybe.Value;
                    var resourceType          = arrayType.GenericTypeArguments.First();
                    var instantiatableRefType = optionalBaseType.MakeGenericType(resourceType);
                    var refValue = Activator.CreateInstance(instantiatableRefType, new object[] { guidValue });
                    var result   = Activator.CreateInstance(instantiatableType, new object[] { refValue });
                    return(result);
                });

                return(values);
            }

            if (arrayType.IsSubClassOfGeneric(typeof(IRefOptional <>)))
            {
                var values = ComposeOptionalFromBase <Guid?>(typeof(IRefOptional <>),
                                                             typeof(EastFive.RefOptional <>), typeof(EastFive.Ref <>));
                return(onBound(values));
            }


            #endregion

            if (arrayType.IsArray)
            {
                var arrayElementType = arrayType.GetElementType();
                var values           = value.BinaryValue
                                       .FromByteArray()
                                       .Select(
                    bytes =>
                {
                    var ep          = new EntityProperty(bytes);
                    var arrayValues = ep.BindSingleValueToArray <object>(arrayElementType,
                                                                         v => v,
                                                                         () => Array.CreateInstance(arrayElementType, 0));
                    // var arrayValues = bytes.FromEdmTypedByteArray(arrayElementType);
                    return(arrayValues);
                })
                                       .ToArray();
                //var values = value.BinaryValue.FromEdmTypedByteArray(arrayElementType);
                return(onBound(values));
            }

            return(arrayType.IsNullable(
                       nulledType =>
            {
                if (typeof(Guid) == nulledType)
                {
                    var values = value.BinaryValue.ToNullablesFromByteArray <Guid>(
                        (byteArray) =>
                    {
                        if (byteArray.Length == 16)
                        {
                            return new Guid(byteArray);
                        }
                        return default(Guid);
                    });
                    return onBound(values);
                }
                if (typeof(decimal) == nulledType)
                {
                    var values = value.BinaryValue.ToNullablesFromByteArray <decimal>(
                        byteArray =>
                    {
                        if (byteArray.TryConvertToDecimal(out decimal decimalValue))
                        {
                            return decimalValue;
                        }
                        return default(decimal);
                    });
                    return onBound(values);
                }
                if (typeof(DateTime) == nulledType)
                {
                    var values = value.BinaryValue.ToNullableDateTimesFromByteArray();
                    return onBound(values);
                }
                var arrayOfObj = value.BinaryValue.FromEdmTypedByteArray(arrayType);
                var arrayOfType = arrayOfObj.CastArray(arrayType);
                return onBound(arrayOfType);

                throw new Exception($"Cannot serialize a nullable array of `{nulledType.FullName}`.");
            },
                       () =>
            {
                if (typeof(Guid) == arrayType)
                {
                    var values = value.BinaryValue.ToGuidsFromByteArray();
                    return onBound(values);
                }
                if (typeof(byte) == arrayType)
                {
                    return onBound(value.BinaryValue);
                }
                if (typeof(bool) == arrayType)
                {
                    var boolArray = value.BinaryValue
                                    .Select(b => b != 0)
                                    .ToArray();
                    return onBound(boolArray);
                }
                if (typeof(DateTime) == arrayType)
                {
                    var values = value.BinaryValue.ToDateTimesFromByteArray();
                    return onBound(values);
                }
                if (typeof(double) == arrayType)
                {
                    var values = value.BinaryValue.ToDoublesFromByteArray();
                    return onBound(values);
                }
                if (typeof(decimal) == arrayType)
                {
                    var values = value.BinaryValue.ToDecimalsFromByteArray();
                    return onBound(values);
                }
                if (typeof(int) == arrayType)
                {
                    var values = value.BinaryValue.ToIntsFromByteArray();
                    return onBound(values);
                }
                if (typeof(long) == arrayType)
                {
                    var values = value.BinaryValue.ToLongsFromByteArray();
                    return onBound(values);
                }
                if (typeof(string) == arrayType)
                {
                    var values = value.BinaryValue.ToStringNullOrEmptysFromUTF8ByteArray();
                    return onBound(values);
                }
                if (arrayType.IsEnum)
                {
                    var values = value.BinaryValue.ToEnumsFromByteArray(arrayType);
                    return onBound(values);
                }
                if (typeof(object) == arrayType)
                {
                    var values = value.BinaryValue.FromEdmTypedByteArray(arrayType);
                    return onBound(values);
                }

                return arrayType
                .GetAttributesInterface <IBind <EntityProperty> >(true)
                .First <IBind <EntityProperty>, TResult>(
                    (epSerializer, next) =>
                {
                    var values = value.BinaryValue.FromEdmTypedByteArray(typeof(byte[]));
                    var boundValues = values
                                      .Where(valueObject => valueObject is byte[])
                                      .Select(
                        valueObject =>
                    {
                        var valueBytes = valueObject as byte[];
                        var valueEp = new EntityProperty(valueBytes);
                        return epSerializer.Bind(valueEp, arrayType,
                                                 v => v,
                                                 () => arrayType.GetDefault());
                    })
                                      .CastArray(arrayType);
                    return onBound(boundValues);
                },
                    () =>
                {
                    throw new Exception($"Cannot serialize array of `{arrayType.FullName}`.");
                });
            }));
        }
        public static TResult Bind <TResult>(this EntityProperty value, Type type,
                                             Func <object, TResult> onBound,
                                             Func <TResult> onFailedToBind)
        {
            if (type.IsArray)
            {
                var arrayType = type.GetElementType();
                return(value.BindSingleValueToArray(arrayType,
                                                    onBound,
                                                    onFailedToBind));
            }

            #region Basic values

            if (typeof(Guid) == type)
            {
                if (value.PropertyType == EdmType.Guid)
                {
                    var guidValue = value.GuidValue;
                    return(onBound(guidValue));
                }
                return(onBound(default(Guid))); // This seems to be the best move in case of data migration
                // return onFailedToBind();
            }
            // TODO: Type check the rest of these like GUID
            if (typeof(long) == type)
            {
                var longValue = value.Int64Value;
                return(onBound(longValue));
            }
            if (typeof(int) == type)
            {
                var intValue = value.Int32Value;
                return(onBound(intValue));
            }
            if (typeof(float) == type)
            {
                var floatValue = (float)value.DoubleValue;
                return(onBound(floatValue));
            }
            if (typeof(double) == type)
            {
                var floatValue = value.DoubleValue;
                return(onBound(floatValue));
            }
            if (typeof(string) == type)
            {
                if (value.PropertyType != EdmType.String)
                {
                    return(onBound(default(string)));
                }
                var stringValue = value.StringValue;
                return(onBound(stringValue));
            }
            if (typeof(DateTime) == type)
            {
                var dtValue = value.DateTime;
                return(onBound(dtValue));
            }
            if (typeof(TimeZoneInfo) == type)
            {
                if (value.PropertyType != EdmType.String)
                {
                    return(onFailedToBind());
                }
                var stringValue = value.StringValue;
                try
                {
                    var tzi = TimeZoneInfo.FindSystemTimeZoneById(stringValue);
                    return(onBound(tzi));
                }
                catch (Exception)
                {
                    return(onFailedToBind());
                }
            }
            if (typeof(Uri) == type)
            {
                var strValue = value.StringValue;
                if (Uri.TryCreate(strValue, UriKind.RelativeOrAbsolute, out Uri uriValue))
                {
                    return(onBound(uriValue));
                }
                return(onBound(uriValue));
            }
            if (typeof(Type) == type)
            {
                var typeValueString = value.StringValue;
                var typeValue       = Type.GetType(typeValueString);
                return(onBound(typeValue));
            }
            if (type.IsEnum)
            {
                var enumNameString = value.StringValue;
                var enumValue      = Enum.Parse(type, enumNameString);
                return(onBound(enumValue));
            }
            if (typeof(bool) == type)
            {
                var boolValue = value.BooleanValue;
                return(onBound(boolValue));
            }

            #region refs

            object IRefInstance(Guid guidValue)
            {
                var resourceType = type.GenericTypeArguments.First();

                return(EntityPropertyExtensions.IRefInstance(guidValue, resourceType));
            }

            if (type.IsSubClassOfGeneric(typeof(IRef <>)))
            {
                var guidValue = value.GuidValue.Value;
                var instance  = IRefInstance(guidValue);
                return(onBound(instance));
            }

            if (type.IsSubClassOfGeneric(typeof(IRefOptional <>)))
            {
                Guid?GetIdMaybe()
                {
                    if (value.PropertyType == EdmType.String)
                    {
                        if (Guid.TryParse(value.StringValue, out Guid id))
                        {
                            return(id);
                        }
                        return(default(Guid?));
                    }

                    if (value.PropertyType == EdmType.Binary)
                    {
                        return(default(Guid?));
                    }

                    return(value.GuidValue);
                }

                var guidValueMaybe     = GetIdMaybe();
                var resourceType       = type.GenericTypeArguments.First();
                var instantiatableType = typeof(EastFive.RefOptional <>)
                                         .MakeGenericType(resourceType);
                if (!guidValueMaybe.HasValue)
                {
                    var refOpt = Activator.CreateInstance(instantiatableType, new object[] { });
                    return(onBound(refOpt));
                }
                var guidValue = guidValueMaybe.Value;
                var refValue  = IRefInstance(guidValue);
                var instance  = Activator.CreateInstance(instantiatableType, new object[] { refValue });
                return(onBound(instance));
            }

            if (type.IsSubClassOfGeneric(typeof(IRefs <>)))
            {
                var guidValues         = value.BinaryValue.ToGuidsFromByteArray();
                var resourceType       = type.GenericTypeArguments.First();
                var instantiatableType = typeof(EastFive.Refs <>).MakeGenericType(resourceType);
                var instance           = Activator.CreateInstance(instantiatableType, new object[] { guidValues });
                return(onBound(instance));
            }

            #endregion

            if (typeof(object) == type)
            {
                switch (value.PropertyType)
                {
                case EdmType.Binary:
                    return(onBound(value.BinaryValue));

                case EdmType.Boolean:
                    if (value.BooleanValue.HasValue)
                    {
                        return(onBound(value.BooleanValue.Value));
                    }
                    break;

                case EdmType.DateTime:
                    if (value.DateTime.HasValue)
                    {
                        return(onBound(value.DateTime.Value));
                    }
                    break;

                case EdmType.Double:
                    if (value.DoubleValue.HasValue)
                    {
                        return(onBound(value.DoubleValue.Value));
                    }
                    break;

                case EdmType.Guid:
                    if (value.GuidValue.HasValue)
                    {
                        var nullGuidKey = new Guid(EDMExtensions.NullGuidKey);
                        if (value.GuidValue.Value == nullGuidKey)
                        {
                            return(onBound(null));
                        }
                        return(onBound(value.GuidValue.Value));
                    }
                    break;

                case EdmType.Int32:
                    if (value.Int32Value.HasValue)
                    {
                        return(onBound(value.Int32Value.Value));
                    }
                    break;

                case EdmType.Int64:
                    if (value.Int64Value.HasValue)
                    {
                        return(onBound(value.Int64Value.Value));
                    }
                    break;

                case EdmType.String:
                    return(onBound(value.StringValue));
                }
                return(onBound(value.PropertyAsObject));
            }

            #endregion

            return(type.IsNullable(
                       nullableType =>
            {
                if (typeof(Guid) == nullableType)
                {
                    var guidValue = value.GuidValue;
                    return onBound(guidValue);
                }
                if (typeof(long) == nullableType)
                {
                    var longValue = value.Int64Value;
                    return onBound(longValue);
                }
                if (typeof(int) == nullableType)
                {
                    var intValue = value.Int32Value;
                    return onBound(intValue);
                }
                if (typeof(bool) == nullableType)
                {
                    var boolValue = value.BooleanValue;
                    return onBound(boolValue);
                }
                if (typeof(float) == nullableType)
                {
                    var floatValue = value.DoubleValue.HasValue ?
                                     (float)value.DoubleValue.Value
                            :
                                     default(float?);
                    return onBound(floatValue);
                }
                if (typeof(double) == nullableType)
                {
                    var floatValue = value.DoubleValue;
                    return onBound(floatValue);
                }
                if (typeof(decimal) == nullableType)
                {
                    var doubleValueMaybe = value.DoubleValue;
                    var decimalMaybeValue = doubleValueMaybe.HasValue ?
                                            (decimal)doubleValueMaybe.Value
                            :
                                            default(decimal?);
                    return onBound(decimalMaybeValue);
                }
                if (typeof(DateTime) == nullableType)
                {
                    var dateTimeValueMaybe = value.DateTime;
                    return onBound(dateTimeValueMaybe);
                }
                return onFailedToBind();
            },
                       () => onFailedToBind()));
        }