/// <summary> /// Optimistic synchronization using a timestamp property /// Works like an UpdateIf that checks the previous value of a property of type DateTime named "Timestamp" /// It also updates this property withe DateTime.Now /// If you use this you should never modify the timestamp manually /// </summary> /// <param name="newValue"></param> public void UpdateWithTimestampSynchronization(T newValue) { var prop = newValue.GetType().GetProperty("Timestamp"); if (prop == null) { throw new CacheException($"No Timestamp property found on type {typeof(T).Name}"); } if (!prop.CanWrite) { throw new CacheException($"The Timestamp property of type {typeof(T).Name} is not writable"); } var oldTimestamp = prop.GetValue(newValue); var kv = KeyInfo.ValueToKeyValue(oldTimestamp, new KeyInfo(KeyDataType.IntKey, KeyType.ScalarIndex, "Timestamp")); var q = new AtomicQuery(kv); var andQuery = new AndQuery(); andQuery.Elements.Add(q); var orq = new OrQuery(typeof(T)); orq.Elements.Add(andQuery); var now = DateTime.Now; var newTimestamp = now.AddTicks(1); // add one to be sure its different prop.SetValue(newValue, newTimestamp); _client.UpdateIf(newValue, orq); }
/// <summary> /// Create a <see cref="KeyValue" />. Ensures conversion from all integer types and <see cref="DateTime" /> to long int /// The default DateTime conversion ignores the time /// </summary> /// <param name="instance"> the object containing the key </param> /// <returns> /// value encoded as <see cref="KeyValue" /> /// </returns> public KeyValue GetValue(object instance) { if (instance == null) { throw new ArgumentNullException(nameof(instance)); } var value = _propertyInfo.GetValue(instance, null); return(KeyInfo.ValueToKeyValue(value, AsKeyInfo)); }
/// <summary> /// Create a <see cref="KeyValue" />. Ensures conversion from all integer types and <see cref="DateTime" /> to long int /// The default DateTime conversion ignores the time /// </summary> /// <param name="instance"> the object containing the key </param> /// <returns> /// value encoded as <see cref="KeyValue" /> /// </returns> public KeyValue GetValue(object instance) { //TODO use precompiled accessors if (instance == null) { throw new ArgumentNullException(nameof(instance)); } var value = Info.GetValue(instance, null); return(KeyInfo.ValueToKeyValue(value, AsKeyInfo)); }
/// <summary> /// Get the values in a an IEnumerable property /// </summary> /// <param name="instance"></param> /// <returns></returns> public IEnumerable <KeyValue> GetCollectionValues(object instance) { if (instance == null) { throw new ArgumentNullException(nameof(instance)); } if (!(Info.GetValue(instance, null) is IEnumerable values)) { throw new NotSupportedException($"Property {Info.Name} can not be converted to IEnumerable"); } return((from object value in values select KeyInfo.ValueToKeyValue(value, AsKeyInfo)).ToList()); }
/// <summary> /// Parse a string like "key == value" or "key > value" /// </summary> /// <param name="queryString"></param> /// <returns></returns> private AtomicQuery StringToQuery(string queryString) { var expression = new Regex("(\\w+)\\s*(==|=|<=|<|>|>=|CONTAINS)\\s*((\\w|-|\\.)+)"); var match = expression.Match(queryString); if (!match.Success || match.Captures.Count != 1 || match.Groups.Count != 5) { throw new ArgumentException($"Invalid query string {queryString}"); } var left = match.Groups[1].Value; left = left.Trim(); var oper = match.Groups[2].Value; oper = oper.Trim(); var right = match.Groups[3].Value; right = right.Trim(); KeyInfo keyInfo = null; if (_typeDescription.PrimaryKeyField.Name.ToUpper() == left.ToUpper()) { keyInfo = _typeDescription.PrimaryKeyField; } if (keyInfo == null) { foreach (var uniqueField in _typeDescription.UniqueKeyFields) { if (uniqueField.Name.ToUpper() == left.ToUpper()) { keyInfo = uniqueField; } } } if (keyInfo == null) { if (_typeDescription.IndexFields != null) { foreach (var indexField in _typeDescription.IndexFields) { if (indexField.Name.ToUpper() == left.ToUpper()) { keyInfo = indexField; } } } } if (keyInfo == null) { foreach (var indexField in _typeDescription.ListFields) { if (indexField.Name.ToUpper() == left.ToUpper()) { keyInfo = indexField; } } } if (keyInfo == null) { throw new ArgumentException(left + " is not an index field"); } KeyValue keyValue; if (keyInfo.KeyDataType == KeyDataType.IntKey) { // special processing for dates(must be in yyyy-mm-dd format) var parts = right.Split('-'); if (parts.Length == 3) { var date = DateTime.Parse(right); keyValue = new KeyValue(date.Ticks, keyInfo); } else { if (right.Contains(".")) // floating point value { if (!decimal.TryParse(right, out var floatValue)) { throw new ArgumentException(right + " can not be converted to float"); } keyValue = KeyInfo.ValueToKeyValue(floatValue, keyInfo); } else // integer value { if (!long.TryParse(right, out var longValue)) { throw new ArgumentException(right + " can not be converted to long"); } keyValue = KeyInfo.ValueToKeyValue(longValue, keyInfo); } } } else { keyValue = new KeyValue(right, keyInfo); } QueryOperator op; switch (oper.ToLower()) { case "=": case "==": op = QueryOperator.Eq; break; case "<": op = QueryOperator.Lt; break; case "<=": op = QueryOperator.Le; break; case ">": op = QueryOperator.Gt; break; case ">=": op = QueryOperator.Ge; break; case "CONTAINS": op = QueryOperator.In; break; default: throw new ArgumentException("unknown operator: ", oper); } var result = new AtomicQuery(keyValue, op); return(result); }