Repository: ignite Updated Branches: refs/heads/master 2bc234ed8 -> 5b31d83f3
http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbCache.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbCache.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbCache.cs new file mode 100644 index 0000000..a7ac2c9 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbCache.cs @@ -0,0 +1,295 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Apache.Ignite.EntityFramework.Impl +{ + using System; + using System.Collections.Generic; + using System.Data.Entity.Core.Metadata.Edm; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Linq; + using System.Runtime.Serialization.Formatters.Binary; + using Apache.Ignite.Core; + using Apache.Ignite.Core.Binary; + using Apache.Ignite.Core.Cache; + using Apache.Ignite.Core.Cache.Configuration; + using Apache.Ignite.Core.Cache.Expiry; + using Apache.Ignite.Core.Common; + using Apache.Ignite.Core.Impl.Cache; + using Apache.Ignite.Core.Impl.Common; + using Apache.Ignite.Core.Log; + + /// <summary> + /// Database query cache. + /// </summary> + internal class DbCache + { + /** Extension id. */ + private const int ExtensionId = 1; + + /** Invalidate sets extension operation. */ + private const int OpInvalidateSets = 1; + + /** Put data extension operation. */ + private const int OpPutItem = 2; + + /** Get data extension operation. */ + private const int OpGetItem = 3; + + /** Max number of cached expiry caches. */ + private const int MaxExpiryCaches = 1000; + + /** Main cache: stores SQL -> QueryResult mappings. */ + private readonly ICache<string, object> _cache; + + /** Entity set version cache. */ + private readonly ICache<string, long> _metaCache; + + /** Cached caches per (expiry_seconds * 10). */ + private volatile Dictionary<long, ICache<string, object>> _expiryCaches = + new Dictionary<long, ICache<string, object>>(); + + /** Sync object. */ + private readonly object _syncRoot = new object(); + + /// <summary> + /// Initializes a new instance of the <see cref="DbCache" /> class. + /// </summary> + /// <param name="ignite">The ignite.</param> + /// <param name="metaCacheConfiguration">The meta cache configuration.</param> + /// <param name="dataCacheConfiguration">The data cache configuration.</param> + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", + Justification = "Validation is present")] + public DbCache(IIgnite ignite, CacheConfiguration metaCacheConfiguration, + CacheConfiguration dataCacheConfiguration) + { + IgniteArgumentCheck.NotNull(ignite, "ignite"); + IgniteArgumentCheck.NotNull(metaCacheConfiguration, "metaCacheConfiguration"); + IgniteArgumentCheck.NotNull(dataCacheConfiguration, "metaCacheConfiguration"); + + IgniteArgumentCheck.Ensure(metaCacheConfiguration.Name != dataCacheConfiguration.Name, + "dataCacheConfiguration", "Meta and Data cache can't have the same name."); + + _metaCache = ignite.GetOrCreateCache<string, long>(metaCacheConfiguration); + _cache = ignite.GetOrCreateCache<string, object>(dataCacheConfiguration); + + var metaCfg = _metaCache.GetConfiguration(); + + if (metaCfg.AtomicityMode != CacheAtomicityMode.Transactional) + throw new IgniteException("EntityFramework meta cache should be Transactional."); + + if (metaCfg.CacheMode == CacheMode.Partitioned && metaCfg.Backups < 1) + ignite.Logger.Warn("EntityFramework meta cache is partitioned and has no backups. " + + "This can lead to data loss and incorrect query results."); + } + + /// <summary> + /// Gets the cache key to be used with GetItem and PutItem. + /// </summary> + public DbCacheKey GetCacheKey(string key, ICollection<EntitySetBase> dependentEntitySets, DbCachingMode mode) + { + if (mode == DbCachingMode.ReadWrite) + { + var versions = GetEntitySetVersions(dependentEntitySets); + + return new DbCacheKey(key, dependentEntitySets, versions); + } + + if (mode == DbCachingMode.ReadOnly) + return new DbCacheKey(key, null, null); + + throw new ArgumentOutOfRangeException("mode"); + } + + /// <summary> + /// Gets the item from cache. + /// </summary> + public bool GetItem(DbCacheKey key, out object value) + { + var valueBytes = ((ICacheInternal) _cache).DoOutInOpExtension(ExtensionId, OpGetItem, + w => WriteKey(key, w, false), r => r.ReadObject<byte[]>()); + + if (valueBytes == null) + { + value = null; + + return false; + } + + using (var ms = new MemoryStream(valueBytes)) + { + value = new BinaryFormatter().Deserialize(ms); + } + + return true; + } + + /// <summary> + /// Puts the item to cache. + /// </summary> + public void PutItem(DbCacheKey key, object value, TimeSpan absoluteExpiration) + { + using (var stream = new MemoryStream()) + { + new BinaryFormatter().Serialize(stream, value); + + var valueBytes = stream.ToArray(); + + var cache = GetCacheWithExpiry(absoluteExpiration); + + ((ICacheInternal)cache).DoOutInOpExtension<object>(ExtensionId, OpPutItem, w => + { + WriteKey(key, w, true); + + w.WriteByteArray(valueBytes); + }, null); + } + } + + /// <summary> + /// Invalidates the sets. + /// </summary> + public void InvalidateSets(ICollection<EntitySetBase> entitySets) + { + Debug.Assert(entitySets != null && entitySets.Count > 0); + + // Increase version for each dependent entity set and run a task to clean up old entries. + ((ICacheInternal) _metaCache).DoOutInOpExtension<object>(ExtensionId, OpInvalidateSets, w => + { + w.WriteString(_cache.Name); + + w.WriteInt(entitySets.Count); + + foreach (var set in entitySets) + w.WriteString(set.Name); + }, null); + } + + /// <summary> + /// Gets the cache with expiry policy according to provided expiration date. + /// </summary> + /// <returns>Cache with expiry policy.</returns> + // ReSharper disable once UnusedParameter.Local + private ICache<string, object> GetCacheWithExpiry(TimeSpan absoluteExpiration) + { + if (absoluteExpiration == TimeSpan.MaxValue) + return _cache; + + // Round up to 0.1 of a second so that we share expiry caches + var expirySeconds = GetSeconds(absoluteExpiration); + + ICache<string, object> expiryCache; + + if (_expiryCaches.TryGetValue(expirySeconds, out expiryCache)) + return expiryCache; + + lock (_syncRoot) + { + if (_expiryCaches.TryGetValue(expirySeconds, out expiryCache)) + return expiryCache; + + // Copy on write with size limit + _expiryCaches = _expiryCaches.Count > MaxExpiryCaches + ? new Dictionary<long, ICache<string, object>>() + : new Dictionary<long, ICache<string, object>>(_expiryCaches); + + expiryCache = + _cache.WithExpiryPolicy(GetExpiryPolicy(expirySeconds)); + + _expiryCaches[expirySeconds] = expiryCache; + + return expiryCache; + } + } + + /// <summary> + /// Gets the expiry policy. + /// </summary> + private static ExpiryPolicy GetExpiryPolicy(long absoluteSeconds) + { + var absolute = absoluteSeconds != long.MaxValue + ? TimeSpan.FromSeconds((double)absoluteSeconds / 10) + : (TimeSpan?) null; + + return new ExpiryPolicy(absolute, null, null); + } + + /// <summary> + /// Gets the seconds. + /// </summary> + private static long GetSeconds(TimeSpan ts) + { + if (ts == TimeSpan.MaxValue) + return long.MaxValue; + + var seconds = ts.TotalSeconds; + + if (seconds < 0) + seconds = 0; + + return (long) (seconds * 10); + } + + /// <summary> + /// Gets the entity set versions. + /// </summary> + private IDictionary<string, long> GetEntitySetVersions(ICollection<EntitySetBase> sets) + { + // LINQ Select allocates less that a new List<> will do. + var versions = _metaCache.GetAll(sets.Select(x => x.Name)); + + // Some versions may be missing, fill up with 0. + foreach (var set in sets) + { + if (!versions.ContainsKey(set.Name)) + versions[set.Name] = 0; + } + + Debug.Assert(sets.Count == versions.Count); + + return versions; + } + + /// <summary> + /// Writes the key. + /// </summary> + private static void WriteKey(DbCacheKey key, IBinaryRawWriter writer, bool includeNames) + { + writer.WriteString(key.Key); + + if (key.EntitySetVersions != null) + { + writer.WriteInt(key.EntitySetVersions.Count); + + // Versions should be in the same order, so we can't iterate over the dictionary. + foreach (var entitySet in key.EntitySets) + { + writer.WriteLong(key.EntitySetVersions[entitySet.Name]); + + if (includeNames) + writer.WriteString(entitySet.Name); + } + } + else + { + writer.WriteInt(-1); + } + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbCacheKey.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbCacheKey.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbCacheKey.cs new file mode 100644 index 0000000..7974ba9 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbCacheKey.cs @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Apache.Ignite.EntityFramework.Impl +{ + using System.Collections.Generic; + using System.Data.Entity.Core.Metadata.Edm; + using System.Diagnostics; + + /// <summary> + /// Represents a cache key, including dependent entity sets and their versions. + /// </summary> + internal class DbCacheKey + { + /** Original string key. */ + private readonly string _key; + + /** Ordered entity sets. */ + private readonly ICollection<EntitySetBase> _entitySets; + + /** Entity set versions. */ + private readonly IDictionary<string, long> _entitySetVersions; + + /// <summary> + /// Initializes a new instance of the <see cref="DbCacheKey"/> class. + /// </summary> + public DbCacheKey(string key, ICollection<EntitySetBase> entitySets, + IDictionary<string, long> entitySetVersions) + { + Debug.Assert(key != null); + + _key = key; + _entitySetVersions = entitySetVersions; + _entitySets = entitySets; + } + + /// <summary> + /// Gets the key. + /// </summary> + public string Key + { + get { return _key; } + } + + /// <summary> + /// Gets the entity sets. + /// </summary> + public ICollection<EntitySetBase> EntitySets + { + get { return _entitySets; } + } + + /// <summary> + /// Gets the entity set versions. + /// </summary> + public IDictionary<string, long> EntitySetVersions + { + get { return _entitySetVersions; } + } + + ///// <summary> + ///// Gets the versioned key. + ///// </summary> + //public void GetStringKey() + //{ + // if (_entitySetVersions == null) + // return _key; + + // var sb = new StringBuilder(_key); + + // // Versions should be in the same order, so we can't iterate over the dictionary. + // foreach (var entitySet in _entitySets) + // sb.AppendFormat("_{0}", _entitySetVersions[entitySet.Name]); + + // return sb.ToString(); + //} + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbCommandDefinitionProxy.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbCommandDefinitionProxy.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbCommandDefinitionProxy.cs new file mode 100644 index 0000000..7057628 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbCommandDefinitionProxy.cs @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Apache.Ignite.EntityFramework.Impl +{ + using System.Data.Common; + using System.Data.Entity.Core.Common; + using System.Diagnostics; + + internal class DbCommandDefinitionProxy : DbCommandDefinition + { + /** */ + private readonly DbCommandDefinition _definition; + + /** */ + private readonly DbCommandInfo _info; + + /// <summary> + /// Initializes a new instance of the <see cref="DbCommandDefinitionProxy"/> class. + /// </summary> + public DbCommandDefinitionProxy(DbCommandDefinition definition, DbCommandInfo info) + { + Debug.Assert(definition != null); + + var proxy = definition as DbCommandDefinitionProxy; + _definition = proxy != null ? proxy._definition : definition; + + _info = info; + } + + /** <inheritDoc /> */ + public override DbCommand CreateCommand() + { + return new DbCommandProxy(_definition.CreateCommand(), _info); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbCommandInfo.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbCommandInfo.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbCommandInfo.cs new file mode 100644 index 0000000..7f18170 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbCommandInfo.cs @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Apache.Ignite.EntityFramework.Impl +{ + using System.Collections.Generic; + using System.Data.Entity.Core.Common.CommandTrees; + using System.Data.Entity.Core.Metadata.Edm; + using System.Diagnostics; + using System.Linq; + + /// <summary> + /// Command info. + /// </summary> + internal class DbCommandInfo + { + /** */ + private readonly bool _isModification; + + /** */ + private readonly DbCache _cache; + + /** */ + private readonly EntitySetBase[] _affectedEntitySets; + + /** */ + private readonly IDbCachingPolicy _policy; + + /** */ + private readonly DbTransactionInterceptor _txHandler; + + /// <summary> + /// Initializes a new instance of the <see cref="DbCommandInfo"/> class. + /// </summary> + public DbCommandInfo(DbCommandTree tree, DbCache cache, IDbCachingPolicy policy, DbTransactionInterceptor txHandler) + { + Debug.Assert(tree != null); + Debug.Assert(cache != null); + Debug.Assert(txHandler != null); + + var qryTree = tree as DbQueryCommandTree; + + if (qryTree != null) + { + _isModification = false; + + _affectedEntitySets = GetAffectedEntitySets(qryTree.Query); + } + else + { + _isModification = true; + + var modify = tree as DbModificationCommandTree; + + if (modify != null) + _affectedEntitySets = GetAffectedEntitySets(modify.Target.Expression); + else + // Functions (stored procedures) are not supported. + Debug.Assert(tree is DbFunctionCommandTree); + } + + _cache = cache; + _policy = policy; + _txHandler = txHandler; + } + + /// <summary> + /// Gets a value indicating whether this command is a query and does not modify data. + /// </summary> + public bool IsModification + { + get { return _isModification; } + } + + /// <summary> + /// Gets or sets the cache. + /// </summary> + public DbCache Cache + { + get { return _cache; } + } + + /// <summary> + /// Gets the affected entity sets. + /// </summary> + public ICollection<EntitySetBase> AffectedEntitySets + { + get { return _affectedEntitySets; } + } + + /// <summary> + /// Gets the policy. + /// </summary> + public IDbCachingPolicy Policy + { + get { return _policy; } + } + + /// <summary> + /// Gets the tx handler. + /// </summary> + public DbTransactionInterceptor TxHandler + { + get { return _txHandler; } + } + + /// <summary> + /// Gets the affected entity sets. + /// </summary> + private static EntitySetBase[] GetAffectedEntitySets(DbExpression expression) + { + var visitor = new ScanExpressionVisitor(); + + expression.Accept(visitor); + + return visitor.EntitySets.ToArray(); + } + + /// <summary> + /// Visits Scan expressions and collects entity set names. + /// </summary> + private class ScanExpressionVisitor : BasicCommandTreeVisitor + { + /** */ + private readonly List<EntitySetBase> _entitySets = new List<EntitySetBase>(); + + /// <summary> + /// Gets the entity sets. + /// </summary> + public IEnumerable<EntitySetBase> EntitySets + { + get { return _entitySets; } + } + + /** <inheritdoc /> */ + public override void Visit(DbScanExpression expression) + { + _entitySets.Add(expression.Target); + + base.Visit(expression); + } + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbCommandProxy.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbCommandProxy.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbCommandProxy.cs new file mode 100644 index 0000000..e3353d5 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbCommandProxy.cs @@ -0,0 +1,263 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Apache.Ignite.EntityFramework.Impl +{ + using System; + using System.Data; + using System.Data.Common; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Text; + + /// <summary> + /// Command proxy. + /// </summary> + internal class DbCommandProxy : DbCommand + { + /** */ + private readonly DbCommand _command; + + /** */ + private readonly DbCommandInfo _commandInfo; + + /// <summary> + /// Initializes a new instance of the <see cref="DbCommandProxy"/> class. + /// </summary> + public DbCommandProxy(DbCommand command, DbCommandInfo info) + { + Debug.Assert(command != null); + Debug.Assert(info != null); + + _command = command; + _commandInfo = info; + } + + /// <summary> + /// Gets the inner command. + /// </summary> + [ExcludeFromCodeCoverage] + public DbCommand InnerCommand + { + get { return _command; } + } + + /// <summary> + /// Gets the command information. + /// </summary> + [ExcludeFromCodeCoverage] + public DbCommandInfo CommandInfo + { + get { return _commandInfo; } + } + + /** <inheritDoc /> */ + [ExcludeFromCodeCoverage] + public override void Prepare() + { + _command.Prepare(); + } + + /** <inheritDoc /> */ + public override string CommandText + { + get { return _command.CommandText; } + set { _command.CommandText = value; } + } + + /** <inheritDoc /> */ + public override int CommandTimeout + { + get { return _command.CommandTimeout; } + set { _command.CommandTimeout = value; } + } + + /** <inheritDoc /> */ + [ExcludeFromCodeCoverage] + public override CommandType CommandType + { + get { return _command.CommandType; } + set { _command.CommandType = value; } + } + + /** <inheritDoc /> */ + public override UpdateRowSource UpdatedRowSource + { + get { return _command.UpdatedRowSource; } + set { _command.UpdatedRowSource = value; } + } + + /** <inheritDoc /> */ + protected override DbConnection DbConnection + { + get { return _command.Connection; } + set { _command.Connection = value; } + } + + /** <inheritDoc /> */ + protected override DbParameterCollection DbParameterCollection + { + get { return _command.Parameters; } + } + + /** <inheritDoc /> */ + protected override DbTransaction DbTransaction + { + get { return _command.Transaction; } + set { _command.Transaction = value; } + } + + /** <inheritDoc /> */ + [ExcludeFromCodeCoverage] + public override bool DesignTimeVisible + { + get { return _command.DesignTimeVisible; } + set { _command.DesignTimeVisible = value; } + } + + /** <inheritDoc /> */ + [ExcludeFromCodeCoverage] + public override void Cancel() + { + _command.Cancel(); + } + + /** <inheritDoc /> */ + [ExcludeFromCodeCoverage] + protected override DbParameter CreateDbParameter() + { + return _command.CreateParameter(); + } + + /** <inheritDoc /> */ + protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) + { + if (_commandInfo.IsModification) + { + // Execute reader, then invalidate cached data. + var dbReader = _command.ExecuteReader(behavior); + + InvalidateCache(); + + return dbReader; + } + + if (Transaction != null) + { + return _command.ExecuteReader(behavior); + } + + var queryInfo = GetQueryInfo(); + var strategy = _commandInfo.Policy.GetCachingMode(queryInfo); + var cacheKey = _commandInfo.Cache.GetCacheKey(GetKey(), _commandInfo.AffectedEntitySets, strategy); + + object cachedRes; + if (_commandInfo.Cache.GetItem(cacheKey, out cachedRes)) + return ((DataReaderResult) cachedRes).CreateReader(); + + var reader = _command.ExecuteReader(behavior); + + if (reader.RecordsAffected > 0) + return reader; // Queries that modify anything are never cached. + + // Check if cacheable. + if (!_commandInfo.Policy.CanBeCached(queryInfo)) + return reader; + + // Read into memory. + var res = new DataReaderResult(reader); + + // Check if specific row count is cacheable. + if (!_commandInfo.Policy.CanBeCached(queryInfo, res.RowCount)) + return res.CreateReader(); + + PutResultToCache(cacheKey, res, queryInfo); + + return res.CreateReader(); + } + + /// <summary> + /// Invalidates the cache. + /// </summary> + private void InvalidateCache() + { + _commandInfo.TxHandler.InvalidateCache(_commandInfo.AffectedEntitySets, Transaction); + } + + /** <inheritDoc /> */ + public override int ExecuteNonQuery() + { + var res = _command.ExecuteNonQuery(); + + // Invalidate AFTER updating the data. + if (_commandInfo.IsModification) + { + InvalidateCache(); + } + + return res; + } + + /** <inheritDoc /> */ + [ExcludeFromCodeCoverage] + public override object ExecuteScalar() + { + // This method is never used by EntityFramework. + // Even EntityCommand.ExecuteScalar goes to ExecuteDbDataReader. + return _command.ExecuteScalar(); + } + + /// <summary> + /// Puts the result to cache. + /// </summary> + private void PutResultToCache(DbCacheKey key, object result, DbQueryInfo queryInfo) + { + var expiration = _commandInfo.Policy != null + ? _commandInfo.Policy.GetExpirationTimeout(queryInfo) + : TimeSpan.MaxValue; + + _commandInfo.Cache.PutItem(key, result, expiration); + } + + /// <summary> + /// Gets the cache key. + /// </summary> + private string GetKey() + { + if (string.IsNullOrEmpty(CommandText)) + throw new NotSupportedException("Ignite Entity Framework Caching " + + "requires non-empty DbCommand.CommandText."); + + var sb = new StringBuilder(); + + sb.AppendFormat("{0}:{1}|", Connection.Database, CommandText); + + foreach (DbParameter param in Parameters) + sb.AppendFormat("{0}={1},", param.ParameterName, param.Value); + + return sb.ToString(); + } + + /// <summary> + /// Gets the query information. + /// </summary> + private DbQueryInfo GetQueryInfo() + { + return new DbQueryInfo(_commandInfo.AffectedEntitySets, CommandText, DbParameterCollection); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbProviderServicesProxy.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbProviderServicesProxy.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbProviderServicesProxy.cs new file mode 100644 index 0000000..8e01295 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbProviderServicesProxy.cs @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma warning disable 618, 672 +namespace Apache.Ignite.EntityFramework.Impl +{ + using System; + using System.Collections.Generic; + using System.Data.Common; + using System.Data.Entity.Core.Common; + using System.Data.Entity.Core.Common.CommandTrees; + using System.Data.Entity.Core.Metadata.Edm; + using System.Data.Entity.Spatial; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + + /// <summary> + /// DbProviderServices proxy which substitutes custom commands. + /// </summary> + internal class DbProviderServicesProxy : DbProviderServices + { + /** */ + private static readonly DbCachingPolicy DefaultPolicy = new DbCachingPolicy(); + + /** */ + private readonly IDbCachingPolicy _policy; + + /** */ + private readonly DbProviderServices _services; + + /** */ + private readonly DbCache _cache; + + /** */ + private readonly DbTransactionInterceptor _txHandler; + + /// <summary> + /// Initializes a new instance of the <see cref="DbProviderServicesProxy"/> class. + /// </summary> + /// <param name="services">The services.</param> + /// <param name="policy">The policy.</param> + /// <param name="cache">The cache.</param> + /// <param name="txHandler">Transaction handler.</param> + public DbProviderServicesProxy(DbProviderServices services, IDbCachingPolicy policy, DbCache cache, + DbTransactionInterceptor txHandler) + { + Debug.Assert(services != null); + Debug.Assert(cache != null); + Debug.Assert(txHandler != null); + + var proxy = services as DbProviderServicesProxy; + _services = proxy != null ? proxy._services : services; + + _policy = policy ?? DefaultPolicy; + _cache = cache; + _txHandler = txHandler; + } + + /** <inheritDoc /> */ + [ExcludeFromCodeCoverage] + public override DbCommandDefinition CreateCommandDefinition(DbCommand prototype) + { + var proxy = prototype as DbCommandProxy; + + if (proxy == null) + return _services.CreateCommandDefinition(prototype); + + return new DbCommandDefinitionProxy(_services.CreateCommandDefinition(proxy.InnerCommand), + proxy.CommandInfo); + } + + /** <inheritDoc /> */ + protected override DbCommandDefinition CreateDbCommandDefinition(DbProviderManifest providerManifest, + DbCommandTree commandTree) + { + return new DbCommandDefinitionProxy(_services.CreateCommandDefinition(providerManifest, commandTree), + new DbCommandInfo(commandTree, _cache, _policy, _txHandler)); + } + + /** <inheritDoc /> */ + protected override string GetDbProviderManifestToken(DbConnection connection) + { + return _services.GetProviderManifestToken(connection); + } + + /** <inheritDoc /> */ + protected override DbProviderManifest GetDbProviderManifest(string manifestToken) + { + return _services.GetProviderManifest(manifestToken); + } + + /** <inheritDoc /> */ + [ExcludeFromCodeCoverage] + public override void RegisterInfoMessageHandler(DbConnection connection, Action<string> handler) + { + _services.RegisterInfoMessageHandler(connection, handler); + } + + /** <inheritDoc /> */ + [ExcludeFromCodeCoverage] + protected override DbSpatialDataReader GetDbSpatialDataReader(DbDataReader fromReader, string manifestToken) + { + return _services.GetSpatialDataReader(fromReader, manifestToken); + } + + /** <inheritDoc /> */ + [ExcludeFromCodeCoverage] + protected override DbSpatialServices DbGetSpatialServices(string manifestToken) + { + return _services.GetSpatialServices(manifestToken); + } + protected override void SetDbParameterValue(DbParameter parameter, TypeUsage parameterType, object value) + { + _services.SetParameterValue(parameter, parameterType, value); + } + + /** <inheritDoc /> */ + protected override string DbCreateDatabaseScript(string providerManifestToken, StoreItemCollection storeItemCollection) + { + return _services.CreateDatabaseScript(providerManifestToken, storeItemCollection); + } + + /** <inheritDoc /> */ + protected override void DbCreateDatabase(DbConnection connection, int? commandTimeout, StoreItemCollection storeItemCollection) + { + _services.CreateDatabase(connection, commandTimeout, storeItemCollection); + } + + /** <inheritDoc /> */ + protected override bool DbDatabaseExists(DbConnection connection, int? commandTimeout, StoreItemCollection storeItemCollection) + { + return _services.DatabaseExists(connection, commandTimeout, storeItemCollection); + } + + /** <inheritDoc /> */ + protected override void DbDeleteDatabase(DbConnection connection, int? commandTimeout, StoreItemCollection storeItemCollection) + { + _services.DeleteDatabase(connection, commandTimeout, storeItemCollection); + } + + /** <inheritDoc /> */ + [ExcludeFromCodeCoverage] + public override object GetService(Type type, object key) + { + return _services.GetService(type, key); + } + + /** <inheritDoc /> */ + [ExcludeFromCodeCoverage] + public override IEnumerable<object> GetServices(Type type, object key) + { + return _services.GetServices(type, key); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbTransactionInterceptor.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbTransactionInterceptor.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbTransactionInterceptor.cs new file mode 100644 index 0000000..601868e --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DbTransactionInterceptor.cs @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Apache.Ignite.EntityFramework.Impl +{ + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Data; + using System.Data.Common; + using System.Data.Entity.Core.Metadata.Edm; + using System.Data.Entity.Infrastructure.Interception; + using System.Diagnostics.CodeAnalysis; + + /// <summary> + /// Intercepts transaction events. + /// </summary> + internal class DbTransactionInterceptor : IDbTransactionInterceptor + { + /** Cache. */ + private readonly DbCache _cache; + + /** Map from tx to dependent sets. HashSet because same sets can be affected multiple times within a tx. */ + private readonly ConcurrentDictionary<DbTransaction, HashSet<EntitySetBase>> _entitySets + = new ConcurrentDictionary<DbTransaction, HashSet<EntitySetBase>>(); + + /// <summary> + /// Initializes a new instance of the <see cref="DbTransactionInterceptor"/> class. + /// </summary> + /// <param name="cache">The cache.</param> + public DbTransactionInterceptor(DbCache cache) + { + _cache = cache; + } + + /** <inheritDoc /> */ + public void InvalidateCache(ICollection<EntitySetBase> entitySets, DbTransaction transaction) + { + if (transaction == null) + { + // Invalidate immediately. + _cache.InvalidateSets(entitySets); + } + else + { + // Postpone until commit. + var sets = _entitySets.GetOrAdd(transaction, _ => new HashSet<EntitySetBase>()); + + foreach (var set in entitySets) + sets.Add(set); + } + } + + /** <inheritDoc /> */ + public void ConnectionGetting(DbTransaction transaction, DbTransactionInterceptionContext<DbConnection> interceptionContext) + { + // No-op + } + + /** <inheritDoc /> */ + public void ConnectionGot(DbTransaction transaction, DbTransactionInterceptionContext<DbConnection> interceptionContext) + { + // No-op + } + + /** <inheritDoc /> */ + [ExcludeFromCodeCoverage] + public void IsolationLevelGetting(DbTransaction transaction, DbTransactionInterceptionContext<IsolationLevel> interceptionContext) + { + // No-op + } + + /** <inheritDoc /> */ + [ExcludeFromCodeCoverage] + public void IsolationLevelGot(DbTransaction transaction, DbTransactionInterceptionContext<IsolationLevel> interceptionContext) + { + // No-op + } + + /** <inheritDoc /> */ + public void Committing(DbTransaction transaction, DbTransactionInterceptionContext interceptionContext) + { + // No-op + } + + /** <inheritDoc /> */ + public void Committed(DbTransaction transaction, DbTransactionInterceptionContext interceptionContext) + { + HashSet<EntitySetBase> entitySets; + if (_entitySets.TryGetValue(transaction, out entitySets)) + _cache.InvalidateSets(entitySets); + } + + /** <inheritDoc /> */ + public void Disposing(DbTransaction transaction, DbTransactionInterceptionContext interceptionContext) + { + // No-op + } + + /** <inheritDoc /> */ + public void Disposed(DbTransaction transaction, DbTransactionInterceptionContext interceptionContext) + { + HashSet<EntitySetBase> val; + _entitySets.TryRemove(transaction, out val); + } + + /** <inheritDoc /> */ + [ExcludeFromCodeCoverage] + public void RollingBack(DbTransaction transaction, DbTransactionInterceptionContext interceptionContext) + { + // No-op + } + + /** <inheritDoc /> */ + [ExcludeFromCodeCoverage] + public void RolledBack(DbTransaction transaction, DbTransactionInterceptionContext interceptionContext) + { + // No-op + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Properties/AssemblyInfo.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Properties/AssemblyInfo.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..7ce4c5f --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Properties/AssemblyInfo.cs @@ -0,0 +1,41 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Apache.Ignite.EntityFramework")] +[assembly: AssemblyDescription("Apache Ignite.NET EntityFramework integration")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Apache Software Foundation")] +[assembly: AssemblyProduct("Apache Ignite.NET")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] +[assembly: Guid("c558518a-c1a0-4224-aaa9-a8688474b4dc")] + +[assembly: AssemblyVersion("1.8.0.14218")] +[assembly: AssemblyFileVersion("1.8.0.14218")] +[assembly: AssemblyInformationalVersion("1.8.0")] + +[assembly: CLSCompliant(true)] + +[assembly: InternalsVisibleTo("Apache.Ignite.EntityFramework.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005f45ca91396d3bb682c38d96bdc6e9ac5855a2b8f7dd7434493c278ceb75cae29d452714a376221e5bfc26dfc7dadcdbe9d0a8bb04b1945f6c326089481fc65da5fa8fc728fa9dde5fa2e1599f89678c6b1b38c59d5deef7d012eced64941d5d065aff987ec0196f5b352213d5c04b982647d7fb3bfb2496b890afc5ef1391b0")] \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework/packages.config ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework/packages.config b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/packages.config new file mode 100644 index 0000000..c623cae --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/packages.config @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<packages> + <package id="EntityFramework" version="6.1.3" targetFramework="net40" /> +</packages> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.sln ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.sln b/modules/platforms/dotnet/Apache.Ignite.sln index de7cf19..fed0821 100644 --- a/modules/platforms/dotnet/Apache.Ignite.sln +++ b/modules/platforms/dotnet/Apache.Ignite.sln @@ -42,6 +42,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Apache.Ignite.AspNet.Tests" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Apache.Ignite.Log4Net", "Apache.Ignite.log4net\Apache.Ignite.Log4Net.csproj", "{6F82D669-382E-4435-8092-68C4440146D8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Apache.Ignite.EntityFramework", "Apache.Ignite.EntityFramework\Apache.Ignite.EntityFramework.csproj", "{C558518A-C1A0-4224-AAA9-A8688474B4DC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Apache.Ignite.EntityFramework.Tests", "Apache.Ignite.EntityFramework.Tests\Apache.Ignite.EntityFramework.Tests.csproj", "{CDA5700E-78F3-4A9E-A9B0-704CBE94651C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -216,6 +220,30 @@ Global {6F82D669-382E-4435-8092-68C4440146D8}.Release|x64.Build.0 = Release|Any CPU {6F82D669-382E-4435-8092-68C4440146D8}.Release|x86.ActiveCfg = Release|Any CPU {6F82D669-382E-4435-8092-68C4440146D8}.Release|x86.Build.0 = Release|Any CPU + {C558518A-C1A0-4224-AAA9-A8688474B4DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C558518A-C1A0-4224-AAA9-A8688474B4DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C558518A-C1A0-4224-AAA9-A8688474B4DC}.Debug|x64.ActiveCfg = Debug|Any CPU + {C558518A-C1A0-4224-AAA9-A8688474B4DC}.Debug|x64.Build.0 = Debug|Any CPU + {C558518A-C1A0-4224-AAA9-A8688474B4DC}.Debug|x86.ActiveCfg = Debug|Any CPU + {C558518A-C1A0-4224-AAA9-A8688474B4DC}.Debug|x86.Build.0 = Debug|Any CPU + {C558518A-C1A0-4224-AAA9-A8688474B4DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C558518A-C1A0-4224-AAA9-A8688474B4DC}.Release|Any CPU.Build.0 = Release|Any CPU + {C558518A-C1A0-4224-AAA9-A8688474B4DC}.Release|x64.ActiveCfg = Release|Any CPU + {C558518A-C1A0-4224-AAA9-A8688474B4DC}.Release|x64.Build.0 = Release|Any CPU + {C558518A-C1A0-4224-AAA9-A8688474B4DC}.Release|x86.ActiveCfg = Release|Any CPU + {C558518A-C1A0-4224-AAA9-A8688474B4DC}.Release|x86.Build.0 = Release|Any CPU + {CDA5700E-78F3-4A9E-A9B0-704CBE94651C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CDA5700E-78F3-4A9E-A9B0-704CBE94651C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDA5700E-78F3-4A9E-A9B0-704CBE94651C}.Debug|x64.ActiveCfg = Debug|Any CPU + {CDA5700E-78F3-4A9E-A9B0-704CBE94651C}.Debug|x64.Build.0 = Debug|Any CPU + {CDA5700E-78F3-4A9E-A9B0-704CBE94651C}.Debug|x86.ActiveCfg = Debug|Any CPU + {CDA5700E-78F3-4A9E-A9B0-704CBE94651C}.Debug|x86.Build.0 = Debug|Any CPU + {CDA5700E-78F3-4A9E-A9B0-704CBE94651C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CDA5700E-78F3-4A9E-A9B0-704CBE94651C}.Release|Any CPU.Build.0 = Release|Any CPU + {CDA5700E-78F3-4A9E-A9B0-704CBE94651C}.Release|x64.ActiveCfg = Release|Any CPU + {CDA5700E-78F3-4A9E-A9B0-704CBE94651C}.Release|x64.Build.0 = Release|Any CPU + {CDA5700E-78F3-4A9E-A9B0-704CBE94651C}.Release|x86.ActiveCfg = Release|Any CPU + {CDA5700E-78F3-4A9E-A9B0-704CBE94651C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE
