Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/EFCache/CachedResults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace EFCache
using System.Collections.Generic;

[Serializable]
internal class CachedResults
public class CachedResults
{
private readonly ColumnMetadata[] _tableMetadata;
private readonly List<object[]> _results;
Expand Down
237 changes: 57 additions & 180 deletions src/EFCache/CachingCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@ namespace EFCache
using System.Threading;
using System.Threading.Tasks;

internal class CachingCommand : DbCommand, ICloneable
internal class CachingCommand : DbCommand, ICloneable, ICachingCommandMetadata
{
private readonly DbCommand _command;
private readonly CommandTreeFacts _commandTreeFacts;
private readonly CacheTransactionHandler _cacheTransactionHandler;
private readonly CachingPolicy _cachingPolicy;
private readonly CachingCommandStrategyFactory _cachingCommandStrategyFactory;
private readonly ICachingCommandStrategy _cachingCommandStrategy;

public CachingCommand(DbCommand command, CommandTreeFacts commandTreeFacts, CacheTransactionHandler cacheTransactionHandler, CachingPolicy cachingPolicy)
public CachingCommand(DbCommand command,
CommandTreeFacts commandTreeFacts,
CacheTransactionHandler cacheTransactionHandler,
CachingPolicy cachingPolicy)
{
Debug.Assert(command != null, "command is null");
Debug.Assert(commandTreeFacts != null, "commandTreeFacts is null");
Expand All @@ -30,6 +35,24 @@ public CachingCommand(DbCommand command, CommandTreeFacts commandTreeFacts, Cach
_commandTreeFacts = commandTreeFacts;
_cacheTransactionHandler = cacheTransactionHandler;
_cachingPolicy = cachingPolicy;
_cachingCommandStrategyFactory = DefaultCachingCommandFactory.Create;
_cachingCommandStrategy = _cachingCommandStrategyFactory(_cachingPolicy,
_commandTreeFacts,
_cacheTransactionHandler,
this);
}

public CachingCommand(DbCommand command,
CommandTreeFacts commandTreeFacts,
CacheTransactionHandler cacheTransactionHandler,
CachingPolicy cachingPolicy,
CachingCommandStrategyFactory cachingCommandStrategyFactory) : this(command, commandTreeFacts, cacheTransactionHandler, cachingPolicy)
{
_cachingCommandStrategyFactory = cachingCommandStrategyFactory;
_cachingCommandStrategy = _cachingCommandStrategyFactory(_cachingPolicy,
_commandTreeFacts,
_cacheTransactionHandler,
this);
}

internal CommandTreeFacts CommandTreeFacts
Expand All @@ -52,38 +75,6 @@ internal DbCommand WrappedCommand
get { return _command; }
}

private bool IsCacheable
{
get
{
return _commandTreeFacts.IsQuery &&
(IsQueryAlwaysCached ||
!_commandTreeFacts.UsesNonDeterministicFunctions &&
!IsQueryBlocked &&
_cachingPolicy.CanBeCached(_commandTreeFacts.AffectedEntitySets, CommandText,
Parameters.Cast<DbParameter>()
.Select(p => new KeyValuePair<string, object>(p.ParameterName, p.Value))));
}
}

private bool IsQueryBlocked
{
get
{
return BlockedQueriesRegistrar.Instance.IsQueryBlocked(
_commandTreeFacts.MetadataWorkspace, CommandText);
}
}

private bool IsQueryAlwaysCached
{
get
{
return AlwaysCachedQueriesRegistrar.Instance.IsQueryCached(
_commandTreeFacts.MetadataWorkspace, CommandText);
}
}

public override void Cancel()
{
_command.Cancel();
Expand Down Expand Up @@ -173,137 +164,71 @@ public override bool DesignTimeVisible

protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
{
if (!IsCacheable)
if (!_cachingCommandStrategy.IsCacheable())
{
var result = _command.ExecuteReader(behavior);

if (!_commandTreeFacts.IsQuery)
{
_cacheTransactionHandler.InvalidateSets(Transaction, _commandTreeFacts.AffectedEntitySets.Select(s => s.Name),
DbConnection);
_cachingCommandStrategy.InvalidateSets(result.RecordsAffected);
}

return result;
}

var key = _cachingCommandStrategy.CreateKey();

var key = CreateKey();

object value;
if (_cacheTransactionHandler.GetItem(Transaction, key, DbConnection, out value))
if (_cachingCommandStrategy.GetCachedDbDataReader(key, out DbDataReader cachedReader))
{
return new CachingReader((CachedResults)value);
return cachedReader;
}

using (var reader = _command.ExecuteReader(behavior))
{
var queryResults = new List<object[]>();

while (reader.Read())
{
var values = new object[reader.FieldCount];
reader.GetValues(values);
queryResults.Add(values);
}

return HandleCaching(reader, key, queryResults);
return _cachingCommandStrategy.SetCacheFromDbDataReader(key, reader);
}
}

#if !NET40
protected async override Task<DbDataReader> ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
{
if (!IsCacheable)
if (!_cachingCommandStrategy.IsCacheable())
{
var result = await _command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false);

if (!_commandTreeFacts.IsQuery)
{
_cacheTransactionHandler.InvalidateSets(Transaction, _commandTreeFacts.AffectedEntitySets.Select(s => s.Name), DbConnection);
_cachingCommandStrategy.InvalidateSets(result.RecordsAffected);
}

return result;
}

var key = CreateKey();

object value;
if (_cacheTransactionHandler.GetItem(Transaction, key, DbConnection, out value))
var key = _cachingCommandStrategy.CreateKey();

if (_cachingCommandStrategy.GetCachedDbDataReader(key, out DbDataReader cachedReader))
{
return new CachingReader((CachedResults)value);
return cachedReader;
}

using (var reader = await _command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false))
{
var queryResults = new List<object[]>();

while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
{
var values = new object[reader.FieldCount];
reader.GetValues(values);
queryResults.Add(values);
}

return HandleCaching(reader, key, queryResults);
return await _cachingCommandStrategy.SetCacheFromDbDataReaderAsync(key, reader, cancellationToken);
}
}
#endif

private DbDataReader HandleCaching(DbDataReader reader, string key, List<object[]> queryResults)
{
var cachedResults =
new CachedResults(
GetTableMetadata(reader), queryResults, reader.RecordsAffected);

int minCacheableRows, maxCachableRows;
_cachingPolicy.GetCacheableRows(_commandTreeFacts.AffectedEntitySets, out minCacheableRows,
out maxCachableRows);

if (IsQueryAlwaysCached || (queryResults.Count >= minCacheableRows && queryResults.Count <= maxCachableRows))
{
TimeSpan slidingExpiration;
DateTimeOffset absoluteExpiration;
_cachingPolicy.GetExpirationTimeout(_commandTreeFacts.AffectedEntitySets, out slidingExpiration,
out absoluteExpiration);

_cacheTransactionHandler.PutItem(
Transaction,
key,
cachedResults,
_commandTreeFacts.AffectedEntitySets.Select(s => s.Name),
slidingExpiration,
absoluteExpiration,
DbConnection);
}

return new CachingReader(cachedResults);
}

protected override void Dispose(bool disposing)
{
_command?.GetType()
.GetMethod("Dispose", BindingFlags.Instance | BindingFlags.NonPublic)
.Invoke(_command, new object[] { disposing });
}

private static ColumnMetadata[] GetTableMetadata(DbDataReader reader)
{
var columnMetadata = new ColumnMetadata[reader.FieldCount];

for (var i = 0; i < reader.FieldCount; i++)
{
columnMetadata[i] =
new ColumnMetadata(
reader.GetName(i), reader.GetDataTypeName(i), reader.GetFieldType(i));
}

return columnMetadata;
}

public override int ExecuteNonQuery()
{
var recordsAffected = _command.ExecuteNonQuery();

InvalidateSetsForNonQuery(recordsAffected);
_cachingCommandStrategy.InvalidateSets(recordsAffected);

return recordsAffected;
}
Expand All @@ -313,86 +238,51 @@ public override async Task<int> ExecuteNonQueryAsync(CancellationToken cancellat
{
var recordsAffected = await _command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);

InvalidateSetsForNonQuery(recordsAffected);
_cachingCommandStrategy.InvalidateSets(recordsAffected);

return recordsAffected;
}
#endif

private void InvalidateSetsForNonQuery(int recordsAffected)
{
if (recordsAffected > 0 && _commandTreeFacts.AffectedEntitySets.Any())
{
_cacheTransactionHandler.InvalidateSets(Transaction, _commandTreeFacts.AffectedEntitySets.Select(s => s.Name),
DbConnection);
}
}

public override object ExecuteScalar()
{
if (!IsCacheable)
if (!_cachingCommandStrategy.IsCacheable())
{
return _command.ExecuteScalar();
}

var key = CreateKey();
var key = _cachingCommandStrategy.CreateKey();

object value;

if (_cacheTransactionHandler.GetItem(Transaction, key, DbConnection, out value))
if (_cachingCommandStrategy.GetCachedScalarObject(key, out object cachedObject))
{
return value;
return cachedObject;
}

var value = _command.ExecuteScalar();

value = _command.ExecuteScalar();

TimeSpan slidingExpiration;
DateTimeOffset absoluteExpiration;
_cachingPolicy.GetExpirationTimeout(_commandTreeFacts.AffectedEntitySets, out slidingExpiration, out absoluteExpiration);

_cacheTransactionHandler.PutItem(
Transaction,
key,
value,
_commandTreeFacts.AffectedEntitySets.Select(s => s.Name),
slidingExpiration,
absoluteExpiration,
DbConnection);

_cachingCommandStrategy.SetCacheFromScalarObject(key, value);

return value;
}

#if !NET40
public async override Task<object> ExecuteScalarAsync(CancellationToken cancellationToken)
{
if (!IsCacheable)
if (!_cachingCommandStrategy.IsCacheable())
{
return await _command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
}

var key = CreateKey();
var key = _cachingCommandStrategy.CreateKey();

object value;

if (_cacheTransactionHandler.GetItem(Transaction, key, DbConnection, out value))
if (_cachingCommandStrategy.GetCachedScalarObject(key, out object cachedObject))
{
return value;
return cachedObject;
}

value = await _command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);

TimeSpan slidingExpiration;
DateTimeOffset absoluteExpiration;
_cachingPolicy.GetExpirationTimeout(_commandTreeFacts.AffectedEntitySets, out slidingExpiration, out absoluteExpiration);
var value = await _command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);

_cacheTransactionHandler.PutItem(
Transaction,
key,
value,
_commandTreeFacts.AffectedEntitySets.Select(s => s.Name),
slidingExpiration,
absoluteExpiration,
DbConnection);
_cachingCommandStrategy.SetCacheFromScalarObject(key, value);

return value;
}
Expand All @@ -415,19 +305,6 @@ public override UpdateRowSource UpdatedRowSource
}
}

private string CreateKey()
{
return
string.Format(
"{0}_{1}_{2}",
Connection.Database,
CommandText,
string.Join(
"_",
Parameters.Cast<DbParameter>()
.Select(p => string.Format("{0}={1}", p.ParameterName, p.Value))));
}

public object Clone()
{
var cloneableCommand = _command as ICloneable;
Expand All @@ -437,7 +314,7 @@ public object Clone()
}

var clonedCommand = (DbCommand)cloneableCommand.Clone();
return new CachingCommand(clonedCommand, _commandTreeFacts, _cacheTransactionHandler, _cachingPolicy);
return new CachingCommand(clonedCommand, _commandTreeFacts, _cacheTransactionHandler, _cachingPolicy, _cachingCommandStrategyFactory);
}
}
}
Loading