using System; using System.Collections; using System.Collections.Generic; using System.Linq; namespace Dapper { partial class SqlMapper { private sealed class DapperRow : System.Dynamic.IDynamicMetaObjectProvider , IDictionary { readonly DapperTable table; object[] values; public DapperRow(DapperTable table, object[] values) { if (table == null) throw new ArgumentNullException(nameof(table)); if (values == null) throw new ArgumentNullException(nameof(values)); this.table = table; this.values = values; } private sealed class DeadValue { public static readonly DeadValue Default = new DeadValue(); private DeadValue() { } } int ICollection>.Count { get { int count = 0; for (int i = 0; i < values.Length; i++) { if (!(values[i] is DeadValue)) count++; } return count; } } public bool TryGetValue(string name, out object value) { var index = table.IndexOfName(name); if (index < 0) { // doesn't exist value = null; return false; } // exists, **even if** we don't have a value; consider table rows heterogeneous value = index < values.Length ? values[index] : null; if (value is DeadValue) { // pretend it isn't here value = null; return false; } return true; } public override string ToString() { var sb = GetStringBuilder().Append("{DapperRow"); foreach (var kv in this) { var value = kv.Value; sb.Append(", ").Append(kv.Key); if (value != null) { sb.Append(" = '").Append(kv.Value).Append('\''); } else { sb.Append(" = NULL"); } } return sb.Append('}').__ToStringRecycle(); } System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject( System.Linq.Expressions.Expression parameter) { return new DapperRowMetaObject(parameter, System.Dynamic.BindingRestrictions.Empty, this); } public IEnumerator> GetEnumerator() { var names = table.FieldNames; for (var i = 0; i < names.Length; i++) { object value = i < values.Length ? values[i] : null; if (!(value is DeadValue)) { yield return new KeyValuePair(names[i], value); } } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #region Implementation of ICollection> void ICollection>.Add(KeyValuePair item) { IDictionary dic = this; dic.Add(item.Key, item.Value); } void ICollection>.Clear() { // removes values for **this row**, but doesn't change the fundamental table for (int i = 0; i < values.Length; i++) values[i] = DeadValue.Default; } bool ICollection>.Contains(KeyValuePair item) { object value; return TryGetValue(item.Key, out value) && Equals(value, item.Value); } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { foreach (var kv in this) { array[arrayIndex++] = kv; // if they didn't leave enough space; not our fault } } bool ICollection>.Remove(KeyValuePair item) { IDictionary dic = this; return dic.Remove(item.Key); } bool ICollection>.IsReadOnly => false; #endregion #region Implementation of IDictionary bool IDictionary.ContainsKey(string key) { int index = table.IndexOfName(key); if (index < 0 || index >= values.Length || values[index] is DeadValue) return false; return true; } void IDictionary.Add(string key, object value) { SetValue(key, value, true); } bool IDictionary.Remove(string key) { int index = table.IndexOfName(key); if (index < 0 || index >= values.Length || values[index] is DeadValue) return false; values[index] = DeadValue.Default; return true; } object IDictionary.this[string key] { get { object val; TryGetValue(key, out val); return val; } set { SetValue(key, value, false); } } public object SetValue(string key, object value) { return SetValue(key, value, false); } private object SetValue(string key, object value, bool isAdd) { if (key == null) throw new ArgumentNullException(nameof(key)); int index = table.IndexOfName(key); if (index < 0) { index = table.AddField(key); } else if (isAdd && index < values.Length && !(values[index] is DeadValue)) { // then semantically, this value already exists var tip = string.Format("key:{0}, index:{1} lenth:{2}", key, index, values.Length); throw new ArgumentException("An item with the same key has already been added " + tip, nameof(key)); } int oldLength = values.Length; if (oldLength <= index) { // we'll assume they're doing lots of things, and // grow it to the full width of the table Array.Resize(ref values, table.FieldCount); for (int i = oldLength; i < values.Length; i++) { values[i] = DeadValue.Default; } } return values[index] = value; } ICollection IDictionary.Keys { get { return this.Select(kv => kv.Key).ToArray(); } } ICollection IDictionary.Values { get { return this.Select(kv => kv.Value).ToArray(); } } #endregion } } }