public override (IReadOnlyCollection <Row>, IReadOnlyCollection <Row>, IReadOnlyCollection <Row>) CalculateChanges()
        {
            List <Row> inserted = new List <Row>();
            List <Row> updated  = new List <Row>();
            List <Row> deleted  = new List <Row>();

            Db.Config.DatabaseConnectionString.WithReader(Query, reader =>
            {
                Dictionary <object[], Row> oldState = new Dictionary <object[], Row>(_data, Schema.RowKeyEqualityComparer);
                while (reader.Read())
                {
                    var row = Schema.ReadRow(reader);
                    if (_data.TryGetValue(row.Key, out Row oldRow))
                    {
                        oldState.Remove(row.Key);
                        if (Schema.RowEqualityComparer.Equals(row, oldRow) == false)
                        {
                            updated.Add(row);
                            _data[row.Key] = row;
                        }
                    }
                    else
                    {
                        inserted.Add(row);
                        _data.Add(row.Key, row);
                    }
                }

                deleted = oldState.Values.ToList();
                _data.RemoveAll(oldState.Keys);
            });
            return(inserted, updated, deleted);
        }
 public override IReadOnlyCollection <Row> Initialize()
 {
     Db.Config.DatabaseConnectionString.WithReader(Query, reader =>
     {
         Schema = new RowSchema(reader, PrimaryKeyColumns);
         _data  = new ReducableDictionary <object[], Row>(Schema.RowKeyEqualityComparer);
         while (reader.Read())
         {
             var row = Schema.ReadRow(reader);
             _data.Add(row.Key, row);
         }
     });
     return(_data.Values.ToArray());
 }
        /// <summary>
        /// Create a new instance of <see cref="SqlCachedQuery"/>
        /// </summary>
        /// <param name="db">Database this query belongs to</param>
        /// <param name="query">UserQuery that is encapsulated by this query</param>
        /// <param name="keyExtractor">Function to extract <typeparamref name="TKey"/> from <typeparamref name="TRow"/></param>
        /// <param name="keyComparerFactory">Comparer to compare <typeparamref name="TKey"/></param>
        /// <param name="cachingType">Type of cache used to detect changes</param>
        /// <param name="primaryKeyColumn">The column used as primary key</param>
        /// <param name="additionalPrimaryKeyColumns">Additional column names for the primary key</param>
        /// <param name="rowFactory">Function to transform <see cref="Row"/> into <typeparamref name="TRow"/></param>
        internal MappedSqlCachedQuery(Database db, string query, Func <Row, TRow> rowFactory, Func <TRow, TKey> keyExtractor, Func <RowSchema, IEqualityComparer <TKey> > keyComparerFactory, CachingType cachingType, string primaryKeyColumn, params string[] additionalPrimaryKeyColumns) : base(Guid.NewGuid())
        {
            _rowFactory   = rowFactory;
            _keyExtractor = keyExtractor;

            switch (cachingType)
            {
            case CachingType.InMemory:
                _view = new InMemoryCache(db, query, new[] { primaryKeyColumn }.Union(additionalPrimaryKeyColumns).ToArray());
                break;

            case CachingType.SqlTable:
                _view = new SqlTableCache(db, query, new[] { primaryKeyColumn }.Union(additionalPrimaryKeyColumns).ToArray());
                break;

            case CachingType.SqlInMemoryTable:
                _view = new SqlMemoryTableCache(db, query, new[] { primaryKeyColumn }.Union(additionalPrimaryKeyColumns).ToArray());
                break;

            default:
                throw new ArgumentOutOfRangeException(nameof(cachingType), cachingType, null);
            }


            lock (_dataAccessLock)
            {
                _view.Invalidated += ViewOnInvalidated;
                _disposeHelper.Attach(() => _view.Invalidated -= ViewOnInvalidated);
                var data = _view.Initialize();

                _rows = new ReducableDictionary <TKey, TRow>(data.Count, keyComparerFactory(_view.Schema));
                foreach (var row in data)
                {
                    var entry = rowFactory(row);
                    var key   = keyExtractor(entry);
                    _rows.Add(key, entry);
                }

                _disposeHelper.Attach(_view);
            }
        }
        private void ViewOnInvalidated()
        {
            var changes = _view.CalculateChanges();

            lock (_dataAccessLock)
            {
                foreach (var row in changes.Item1)
                {
                    var entry = _rowFactory(row);
                    var key   = _keyExtractor(entry);
                    _rows.Add(key, entry);
                }
                foreach (var row in changes.Item2)
                {
                    var entry = _rowFactory(row);
                    var key   = _keyExtractor(entry);
                    _rows[key] = entry;
                }

                _rows.RemoveAll(changes.Item3.Select(_rowFactory).Select(_keyExtractor));
            }
        }