You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pt...@apache.org on 2016/11/09 15:12:46 UTC

[1/3] ignite git commit: IGNITE-1915 .NET: Ignite as Entity Framework Second-Level Cache

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 @@
+\ufeff/*
+ * 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 @@
+\ufeff/*
+ * 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 @@
+\ufeff/*
+ * 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 @@
+\ufeff/*
+ * 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 @@
+\ufeff/*
+ * 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 @@
+\ufeff/*
+ * 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 @@
+\ufeff/*
+ * 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 @@
+\ufeff/*
+* 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 @@
+\ufeff<?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


[2/3] ignite git commit: IGNITE-1915 .NET: Ignite as Entity Framework Second-Level Cache

Posted by pt...@apache.org.
http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/EntityFrameworkCacheTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/EntityFrameworkCacheTest.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/EntityFrameworkCacheTest.cs
new file mode 100644
index 0000000..cfc9f66
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/EntityFrameworkCacheTest.cs
@@ -0,0 +1,942 @@
+\ufeff/*
+ * 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.
+ */
+
+// ReSharper disable UnusedMember.Local
+// ReSharper disable UnusedAutoPropertyAccessor.Local
+// ReSharper disable ClassWithVirtualMembersNeverInherited.Local
+// ReSharper disable UnusedAutoPropertyAccessor.Global
+// ReSharper disable VirtualMemberNeverOverridden.Global
+
+namespace Apache.Ignite.EntityFramework.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Data;
+    using System.Data.Entity;
+    using System.Data.Entity.Core.EntityClient;
+    using System.Data.Entity.Infrastructure;
+    using System.IO;
+    using System.Linq;
+    using System.Threading;
+    using System.Transactions;
+    using Apache.Ignite.Core;
+    using Apache.Ignite.Core.Cache;
+    using Apache.Ignite.Core.Tests;
+    using Apache.Ignite.EntityFramework;
+    using Apache.Ignite.EntityFramework.Impl;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Integration test with temporary SQL CE database.
+    /// </summary>
+    public class EntityFrameworkCacheTest
+    {
+        /** */
+        private static readonly string TempFile = Path.GetTempFileName();
+
+        /** */
+        private static readonly string ConnectionString = "Datasource = " + TempFile;
+
+        /** */
+        private static readonly DelegateCachingPolicy Policy = new DelegateCachingPolicy();
+
+        /** */
+        private ICache<object, object> _cache;
+
+        /** */
+        private ICache<object, object> _metaCache;
+
+        /// <summary>
+        /// Fixture set up.
+        /// </summary>
+        [TestFixtureSetUp]
+        public void FixtureSetUp()
+        {
+            // Start 2 nodes.
+            var cfg = TestUtils.GetTestConfiguration();
+            var ignite = Ignition.Start(cfg);
+
+            Ignition.Start(new IgniteConfiguration(cfg) {GridName = "grid2"});
+
+            // Create SQL CE database in a temp file.
+            using (var ctx = GetDbContext())
+            {
+                File.Delete(TempFile);
+                ctx.Database.Create();
+            }
+
+            // Get the caches.
+            _cache = ignite.GetCache<object, object>("entityFrameworkQueryCache_data")
+                .WithKeepBinary<object, object>();
+
+            _metaCache = ignite.GetCache<object, object>("entityFrameworkQueryCache_metadata")
+                .WithKeepBinary<object, object>();
+        }
+
+        /// <summary>
+        /// Fixture tear down.
+        /// </summary>
+        [TestFixtureTearDown]
+        public void FixtureTearDown()
+        {
+            using (var ctx = GetDbContext())
+            {
+                ctx.Database.Delete();
+            }
+
+            Ignition.StopAll(true);
+            File.Delete(TempFile);
+        }
+
+        /// <summary>
+        /// Sets up the test.
+        /// </summary>
+        [SetUp]
+        public void TestSetUp()
+        {
+            // Reset the policy.
+            Policy.CanBeCachedFunc = null;
+            Policy.CanBeCachedRowsFunc = null;
+            Policy.GetExpirationTimeoutFunc = null;
+            Policy.GetCachingStrategyFunc = null;
+
+            // Clean up the db.
+            using (var ctx = GetDbContext())
+            {
+                ctx.Blogs.RemoveRange(ctx.Blogs);
+                ctx.Posts.RemoveRange(ctx.Posts);
+                ctx.Tests.RemoveRange(ctx.Tests);
+
+                ctx.SaveChanges();
+            }
+
+            using (var ctx = GetDbContext())
+            {
+                Assert.IsEmpty(ctx.Blogs);
+                Assert.IsEmpty(ctx.Posts);
+            }
+
+            // Clear the caches.
+            _cache.Clear();
+            _metaCache.Clear();
+        }
+
+        /// <summary>
+        /// Tests that caching actually happens.
+        /// </summary>
+        [Test]
+        public void TestResultFromCache()
+        {
+            using (var ctx = GetDbContext())
+            {
+                // Add data.
+                ctx.Posts.Add(new Post {Title = "Foo", Blog = new Blog(), PostId = 1});
+                ctx.Posts.Add(new Post {Title = "Bar", Blog = new Blog(), PostId = 2});
+                ctx.SaveChanges();
+
+                Assert.AreEqual(new[] {"Foo"}, ctx.Posts.Where(x => x.Title == "Foo").Select(x => x.Title).ToArray());
+                Assert.AreEqual(new[] {"Bar"}, ctx.Posts.Where(x => x.Title == "Bar").Select(x => x.Title).ToArray());
+
+                // Alter cached data: swap cached values.
+                
+                var cachedData = _cache.ToArray();
+
+                Assert.AreEqual(2, cachedData.Length);
+
+                _cache[cachedData[0].Key] = cachedData[1].Value;
+                _cache[cachedData[1].Key] = cachedData[0].Value;
+
+                // Verify.
+                Assert.AreEqual(new[] {"Bar"}, ctx.Posts.Where(x => x.Title == "Foo").Select(x => x.Title).ToArray());
+                Assert.AreEqual(new[] {"Foo"}, ctx.Posts.Where(x => x.Title == "Bar").Select(x => x.Title).ToArray());
+            }
+        }
+
+        /// <summary>
+        /// Tests the read-write strategy (default).
+        /// </summary>
+        [Test]
+        public void TestReadWriteStrategy()
+        {
+            using (var ctx = GetDbContext())
+            {
+                var blog = new Blog
+                {
+                    Name = "Foo",
+                    Posts = new List<Post>
+                    {
+                        new Post {Title = "My First Post", Content = "Hello World!"}
+                    }
+                };
+                ctx.Blogs.Add(blog);
+
+                Assert.AreEqual(2, ctx.SaveChanges());
+
+                // Check that query works.
+                Assert.AreEqual(1, ctx.Posts.Where(x => x.Title.StartsWith("My")).ToArray().Length);
+
+                // Add new post to check invalidation.
+                ctx.Posts.Add(new Post {BlogId = blog.BlogId, Title = "My Second Post", Content = "Foo bar."});
+                Assert.AreEqual(1, ctx.SaveChanges());
+
+                Assert.AreEqual(0, _cache.GetSize()); // No cached entries.
+
+                Assert.AreEqual(2, ctx.Posts.Where(x => x.Title.StartsWith("My")).ToArray().Length);
+
+                Assert.AreEqual(1, _cache.GetSize()); // Cached query added.
+
+                // Delete post.
+                ctx.Posts.Remove(ctx.Posts.First());
+                Assert.AreEqual(1, ctx.SaveChanges());
+
+                Assert.AreEqual(0, _cache.GetSize()); // No cached entries.
+                Assert.AreEqual(1, ctx.Posts.Where(x => x.Title.StartsWith("My")).ToArray().Length);
+
+                Assert.AreEqual(1, _cache.GetSize()); // Cached query added.
+
+                // Modify post.
+                Assert.AreEqual(0, ctx.Posts.Count(x => x.Title.EndsWith("updated")));
+
+                ctx.Posts.Single().Title += " - updated";
+                Assert.AreEqual(1, ctx.SaveChanges());
+
+                Assert.AreEqual(0, _cache.GetSize()); // No cached entries.
+                Assert.AreEqual(1, ctx.Posts.Count(x => x.Title.EndsWith("updated")));
+
+                Assert.AreEqual(1, _cache.GetSize()); // Cached query added.
+            }
+        }
+
+        /// <summary>
+        /// Tests the read only strategy.
+        /// </summary>
+        [Test]
+        public void TestReadOnlyStrategy()
+        {
+            // Set up a policy to cache Blogs as read-only and Posts as read-write.
+            Policy.GetCachingStrategyFunc = q =>
+                q.AffectedEntitySets.Count == 1 && q.AffectedEntitySets.Single().Name == "Blog"
+                    ? DbCachingMode.ReadOnly
+                    : DbCachingMode.ReadWrite;
+
+            using (var ctx = GetDbContext())
+            {
+                ctx.Blogs.Add(new Blog
+                {
+                    Name = "Foo",
+                    Posts = new List<Post>
+                    {
+                        new Post {Title = "Post"}
+                    }
+                });
+
+                ctx.SaveChanges();
+
+                // Update entities.
+                Assert.AreEqual("Foo", ctx.Blogs.Single().Name);
+                Assert.AreEqual("Post", ctx.Posts.Single().Title);
+
+                ctx.Blogs.Single().Name += " - updated";
+                ctx.Posts.Single().Title += " - updated";
+
+                ctx.SaveChanges();
+            }
+
+            // Verify that cached result is not changed for blogs, but changed for posts.
+            using (var ctx = GetDbContext())
+            {
+                // Raw SQL queries do not hit cache - verify that actual data is updated.
+                Assert.AreEqual("Foo - updated", ctx.Database.SqlQuery<string>("select name from blogs").Single());
+                Assert.AreEqual("Post - updated", ctx.Database.SqlQuery<string>("select title from posts").Single());
+
+                // Check EF queries that hit cache.
+                Assert.AreEqual("Foo", ctx.Blogs.Single().Name);
+                Assert.AreEqual("Post - updated", ctx.Posts.Single().Title);
+
+            }
+
+            // Clear the cache and verify that actual value in DB is changed.
+            _cache.Clear();
+
+            using (var ctx = GetDbContext())
+            {
+                Assert.AreEqual("Foo - updated", ctx.Blogs.Single().Name);
+                Assert.AreEqual("Post - updated", ctx.Posts.Single().Title);
+            }
+        }
+
+        /// <summary>
+        /// Tests the scalar queries.
+        /// </summary>
+        [Test]
+        public void TestScalars()
+        {
+            using (var ctx = GetDbContext())
+            {
+                var blog = new Blog
+                {
+                    Name = "Foo",
+                    Posts = new List<Post>
+                    {
+                        new Post {Title = "1"},
+                        new Post {Title = "2"},
+                        new Post {Title = "3"},
+                        new Post {Title = "4"}
+                    }
+                };
+                ctx.Blogs.Add(blog);
+
+                Assert.AreEqual(5, ctx.SaveChanges());
+
+                // Test sum and count.
+                const string esql = "SELECT COUNT(1) FROM [BloggingContext].Posts";
+
+                Assert.AreEqual(4, ctx.Posts.Count());
+                Assert.AreEqual(4, ctx.Posts.Count(x => x.Content == null));
+                Assert.AreEqual(4, GetEntityCommand(ctx, esql).ExecuteScalar());
+                Assert.AreEqual(blog.BlogId*4, ctx.Posts.Sum(x => x.BlogId));
+
+                ctx.Posts.Remove(ctx.Posts.First());
+                ctx.SaveChanges();
+
+                Assert.AreEqual(3, ctx.Posts.Count());
+                Assert.AreEqual(3, ctx.Posts.Count(x => x.Content == null));
+                Assert.AreEqual(3, GetEntityCommand(ctx, esql).ExecuteScalar());
+                Assert.AreEqual(blog.BlogId*3, ctx.Posts.Sum(x => x.BlogId));
+            }
+        }
+
+        /// <summary>
+        /// Tests transactions created with BeginTransaction.
+        /// </summary>
+        [Test]
+        public void TestTx()
+        {
+            // Check TX without commit.
+            using (var ctx = GetDbContext())
+            {
+                using (ctx.Database.BeginTransaction())
+                {
+                    ctx.Posts.Add(new Post {Title = "Foo", Blog = new Blog()});
+                    ctx.SaveChanges();
+
+                    Assert.AreEqual(1, ctx.Posts.ToArray().Length);
+                }
+            }
+
+            using (var ctx = GetDbContext())
+            {
+                Assert.AreEqual(0, ctx.Posts.ToArray().Length);
+            }
+
+            // Check TX with commit.
+            using (var ctx = GetDbContext())
+            {
+                using (var tx = ctx.Database.BeginTransaction())
+                {
+                    ctx.Posts.Add(new Post {Title = "Foo", Blog = new Blog()});
+                    ctx.SaveChanges();
+
+                    Assert.AreEqual(1, ctx.Posts.ToArray().Length);
+
+                    tx.Commit();
+
+                    Assert.AreEqual(1, ctx.Posts.ToArray().Length);
+                }
+            }
+
+            using (var ctx = GetDbContext())
+            {
+                Assert.AreEqual(1, ctx.Posts.ToArray().Length);
+            }
+        }
+
+        /// <summary>
+        /// Tests transactions created with TransactionScope.
+        /// </summary>
+        [Test]
+        public void TestTxScope()
+        {
+            // Check TX without commit.
+            using (new TransactionScope())
+            {
+                using (var ctx = GetDbContext())
+                {
+                    ctx.Posts.Add(new Post {Title = "Foo", Blog = new Blog()});
+                    ctx.SaveChanges();
+                }
+            }
+
+            using (var ctx = GetDbContext())
+            {
+                Assert.AreEqual(0, ctx.Posts.ToArray().Length);
+            }
+
+            // Check TX with commit.
+            using (var tx = new TransactionScope())
+            {
+                using (var ctx = GetDbContext())
+                {
+                    ctx.Posts.Add(new Post {Title = "Foo", Blog = new Blog()});
+                    ctx.SaveChanges();
+                }
+
+                tx.Complete();
+            }
+
+            using (var ctx = GetDbContext())
+            {
+                Assert.AreEqual(1, ctx.Posts.ToArray().Length);
+            }
+        }
+
+        /// <summary>
+        /// Tests the expiration.
+        /// </summary>
+        [Test]
+        public void TestExpiration()
+        {
+            Policy.GetExpirationTimeoutFunc = qry => TimeSpan.FromSeconds(0.3);
+
+            using (var ctx = GetDbContext())
+            {
+                ctx.Posts.Add(new Post {Title = "Foo", Blog = new Blog()});
+                ctx.SaveChanges();
+
+                Assert.AreEqual(1, ctx.Posts.ToArray().Length);
+                Assert.AreEqual(1, _cache.GetSize());
+
+                var key = _cache.Single().Key;
+                Assert.IsTrue(_cache.ContainsKey(key));
+
+                Thread.Sleep(300);
+
+                Assert.IsFalse(_cache.ContainsKey(key));
+                Assert.AreEqual(0, _cache.GetSize());
+                Assert.AreEqual(2, _metaCache.GetSize());
+            }
+        }
+
+        /// <summary>
+        /// Tests the caching policy.
+        /// </summary>
+        [Test]
+        public void TestCachingPolicy()
+        {
+            var funcs = new List<string>();
+
+            var checkQry = (Action<DbQueryInfo>) (qry =>
+                {
+                    var set = qry.AffectedEntitySets.Single();
+
+                    Assert.AreEqual("Post", set.Name);
+
+                    Assert.AreEqual(1, qry.Parameters.Count);
+                    Assert.AreEqual(-5, qry.Parameters[0].Value);
+                    Assert.AreEqual(DbType.Int32, qry.Parameters[0].DbType);
+
+                    Assert.IsTrue(qry.CommandText.EndsWith("WHERE [Extent1].[BlogId] > @p__linq__0"));
+                }
+            );
+
+            Policy.CanBeCachedFunc = qry =>
+            {
+                funcs.Add("CanBeCached");
+                checkQry(qry);
+                return true;
+            };
+
+            Policy.CanBeCachedRowsFunc = (qry, rows) =>
+            {
+                funcs.Add("CanBeCachedRows");
+                Assert.AreEqual(3, rows);
+                checkQry(qry);
+                return true;
+            };
+
+            Policy.GetCachingStrategyFunc = qry =>
+            {
+                funcs.Add("GetCachingStrategy");
+                checkQry(qry);
+                return DbCachingMode.ReadWrite;
+            };
+
+            Policy.GetExpirationTimeoutFunc = qry =>
+            {
+                funcs.Add("GetExpirationTimeout");
+                checkQry(qry);
+                return TimeSpan.MaxValue;
+            };
+
+            using (var ctx = GetDbContext())
+            {
+                var blog = new Blog();
+
+                ctx.Posts.Add(new Post {Title = "Foo", Blog = blog});
+                ctx.Posts.Add(new Post {Title = "Bar", Blog = blog});
+                ctx.Posts.Add(new Post {Title = "Baz", Blog = blog});
+
+                ctx.SaveChanges();
+
+                int minId = -5;
+                Assert.AreEqual(3, ctx.Posts.Where(x => x.BlogId > minId).ToArray().Length);
+
+                // Check that policy methods are called in correct order with correct params.
+                Assert.AreEqual(
+                    new[] {"GetCachingStrategy", "CanBeCached", "CanBeCachedRows", "GetExpirationTimeout"},
+                    funcs.ToArray());
+            }
+        }
+
+        /// <summary>
+        /// Tests the cache reader indirectly with an entity that has various field types.
+        /// </summary>
+        [Test]
+        public void TestCacheReader()
+        {
+            // Tests all kinds of entity field types to cover ArrayDbDataReader.
+            var test = GetTestEntity();
+
+            using (var ctx = new BloggingContext(ConnectionString))
+            {
+                ctx.Tests.Add(test);
+                ctx.SaveChanges();
+            }
+
+            // Use new context to ensure no first-level caching.
+            using (var ctx = new BloggingContext(ConnectionString))
+            {
+                // Check default deserialization.
+                var test0 = ctx.Tests.Single(x => x.Bool);
+                Assert.AreEqual(test, test0);
+            }
+        }
+
+        /// <summary>
+        /// Tests the cache reader by calling it directly.
+        /// These calls are (partly) delegated by EF to the <see cref="ArrayDbDataReader"/>.
+        /// </summary>
+        [Test]
+        public void TestCacheReaderRaw()
+        {
+            var test = GetTestEntity();
+
+            using (var ctx = new BloggingContext(ConnectionString))
+            {
+                ctx.Tests.Add(test);
+                ctx.SaveChanges();
+
+                test = ctx.Tests.Single();
+            }
+
+            using (var ctx = new BloggingContext(ConnectionString))
+            {
+                var cmd = GetEntityCommand(ctx, "SELECT VALUE Test FROM BloggingContext.Tests AS Test");
+
+                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
+                {
+                    // Check schema.
+                    Assert.Throws<NotSupportedException>(() => reader.GetSchemaTable());
+                    Assert.AreEqual(0, reader.Depth);
+                    Assert.AreEqual(-1, reader.RecordsAffected);
+                    Assert.IsTrue(reader.HasRows);
+                    Assert.IsFalse(reader.IsClosed);
+                    Assert.AreEqual(11, reader.FieldCount);
+                    Assert.AreEqual(11, reader.VisibleFieldCount);
+
+                    // Check field names.
+                    Assert.AreEqual("Edm.Int32", reader.GetDataTypeName(0));
+                    Assert.AreEqual("Edm.Byte", reader.GetDataTypeName(1));
+                    Assert.AreEqual("Edm.Int16", reader.GetDataTypeName(2));
+                    Assert.AreEqual("Edm.Int64", reader.GetDataTypeName(3));
+                    Assert.AreEqual("Edm.Single", reader.GetDataTypeName(4));
+                    Assert.AreEqual("Edm.Double", reader.GetDataTypeName(5));
+                    Assert.AreEqual("Edm.Decimal", reader.GetDataTypeName(6));
+                    Assert.AreEqual("Edm.Boolean", reader.GetDataTypeName(7));
+                    Assert.AreEqual("Edm.String", reader.GetDataTypeName(8));
+                    Assert.AreEqual("Edm.Guid", reader.GetDataTypeName(9));
+                    Assert.AreEqual("Edm.DateTime", reader.GetDataTypeName(10));
+
+                    // Check field types.
+                    Assert.AreEqual(typeof(int), reader.GetFieldType(0));
+                    Assert.AreEqual(typeof(byte), reader.GetFieldType(1));
+                    Assert.AreEqual(typeof(short), reader.GetFieldType(2));
+                    Assert.AreEqual(typeof(long), reader.GetFieldType(3));
+                    Assert.AreEqual(typeof(float), reader.GetFieldType(4));
+                    Assert.AreEqual(typeof(double), reader.GetFieldType(5));
+                    Assert.AreEqual(typeof(decimal), reader.GetFieldType(6));
+                    Assert.AreEqual(typeof(bool), reader.GetFieldType(7));
+                    Assert.AreEqual(typeof(string), reader.GetFieldType(8));
+                    Assert.AreEqual(typeof(Guid), reader.GetFieldType(9));
+                    Assert.AreEqual(typeof(DateTime), reader.GetFieldType(10));
+
+                    // Read.
+                    Assert.IsTrue(reader.Read());
+
+                    // Test values array.
+                    var vals = new object[reader.FieldCount];
+                    reader.GetValues(vals);
+
+                    Assert.AreEqual(test.Byte, vals[reader.GetOrdinal("Byte")]);
+                    Assert.AreEqual(test.Short, vals[reader.GetOrdinal("Short")]);
+                    Assert.AreEqual(test.ArrayReaderTestId, vals[reader.GetOrdinal("ArrayReaderTestId")]);
+                    Assert.AreEqual(test.Long, vals[reader.GetOrdinal("Long")]);
+                    Assert.AreEqual(test.Float, vals[reader.GetOrdinal("Float")]);
+                    Assert.AreEqual(test.Double, vals[reader.GetOrdinal("Double")]);
+                    Assert.AreEqual(test.Decimal, vals[reader.GetOrdinal("Decimal")]);
+                    Assert.AreEqual(test.Bool, vals[reader.GetOrdinal("Bool")]);
+                    Assert.AreEqual(test.String, vals[reader.GetOrdinal("String")]);
+                    Assert.AreEqual(test.Guid, vals[reader.GetOrdinal("Guid")]);
+                    Assert.AreEqual(test.DateTime, vals[reader.GetOrdinal("DateTime")]);
+                }
+
+                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
+                {
+                    // Read.
+                    Assert.IsTrue(reader.Read());
+
+                    // Test separate values.
+                    Assert.AreEqual(test.ArrayReaderTestId, reader.GetInt32(0));
+                    Assert.AreEqual(test.Byte, reader.GetByte(1));
+                    Assert.AreEqual(test.Short, reader.GetInt16(2));
+                    Assert.AreEqual(test.Long, reader.GetInt64(3));
+                    Assert.AreEqual(test.Float, reader.GetFloat(4));
+                    Assert.AreEqual(test.Double, reader.GetDouble(5));
+                    Assert.AreEqual(test.Decimal, reader.GetDecimal(6));
+                    Assert.AreEqual(test.Bool, reader.GetBoolean(7));
+                    Assert.AreEqual(test.String, reader.GetString(8));
+                    Assert.AreEqual(test.Guid, reader.GetGuid(9));
+                    Assert.AreEqual(test.DateTime, reader.GetDateTime(10));
+                }
+            }
+        }
+
+        /// <summary>
+        /// Tests the database context.
+        /// </summary>
+        [Test]
+        public void TestDbContext()
+        {
+            using (var ctx = GetDbContext())
+            {
+                var objCtx = ((IObjectContextAdapter) ctx).ObjectContext;
+
+                var script = objCtx.CreateDatabaseScript();
+                Assert.IsTrue(script.StartsWith("CREATE TABLE \"Blogs\""));
+            }
+        }
+
+        /// <summary>
+        /// Tests that old versions of caches entries are cleaned up.
+        /// </summary>
+        [Test]
+        public void TestOldEntriesCleanup()
+        {
+            // Run in a loop to generate a bunch of outdated cache entries.
+            for (var i = 0; i < 100; i++)
+                CreateRemoveBlog();
+
+            // Only one version of data is in the cache.
+            Assert.AreEqual(1, _cache.GetSize());
+            Assert.AreEqual(1, _metaCache.GetSize());
+        }
+
+        /// <summary>
+        /// Tests the old entries cleanup in multi threaded scenario.
+        /// </summary>
+        [Test]
+        [Category(TestUtils.CategoryIntensive)]
+        public void TestOldEntriesCleanupMultithreaded()
+        {
+            TestUtils.RunMultiThreaded(CreateRemoveBlog, 4, 20);
+
+            // Wait for the cleanup to complete.
+            Thread.Sleep(200);
+
+            // Only one version of data is in the cache.
+            Assert.AreEqual(1, _cache.GetSize());
+            Assert.AreEqual(1, _metaCache.GetSize());
+        }
+
+        /// <summary>
+        /// Tests the entity set version increment in multi-threaded scenario.
+        /// </summary>
+        [Test]
+        [Category(TestUtils.CategoryIntensive)]
+        public void TestIncrementMultithreaded()
+        {
+            var opCnt = 0;
+
+            TestUtils.RunMultiThreaded(() =>
+            {
+                var blog = new Blog {Name = "my blog"};
+                using (var ctx = GetDbContext())
+                {
+                    ctx.Blogs.Add(blog);
+                    ctx.SaveChanges();
+                }
+
+                Interlocked.Increment(ref opCnt);
+
+                using (var ctx = GetDbContext())
+                {
+                    ctx.Blogs.Attach(blog);
+                    ctx.Blogs.Remove(blog);
+                    ctx.SaveChanges();
+                }
+
+                Interlocked.Increment(ref opCnt);
+            }, 4, 10);
+
+            var setVersion = _metaCache["Blog"];
+
+            Assert.AreEqual(opCnt, setVersion);
+        }
+
+        /// <summary>
+        /// Creates and removes a blog.
+        /// </summary>
+        private void CreateRemoveBlog()
+        {
+            try
+            {
+                CreateRemoveBlog0();
+            }
+            catch (Exception ex)
+            {
+                // Ignore SQL CE glitch.
+                if (!ex.ToString().Contains("The current row was deleted."))
+                    throw;
+            }
+        }
+
+        /// <summary>
+        /// Creates and removes a blog.
+        /// </summary>
+        private void CreateRemoveBlog0()
+        {
+            var blog = new Blog {Name = "my blog"};
+            var threadId = Thread.CurrentThread.ManagedThreadId;
+
+            Func<object> getMeta = () => _metaCache.Where(x => x.Key.Equals("Blog"))
+                .Select(x => x.Value).SingleOrDefault() ?? "null";
+
+            var meta1 = getMeta();
+
+            using (var ctx = GetDbContext())
+            {
+                ctx.Blogs.Add(blog);
+                ctx.SaveChanges();
+            }
+
+            var meta2 = getMeta();
+
+            using (var ctx = GetDbContext())
+            {
+                // Use ToArray so that there is always the same DB query.
+                Assert.AreEqual(1, ctx.Blogs.ToArray().Count(x => x.BlogId == blog.BlogId),
+                    string.Format("Existing blog not found: {0} = {1}, {2} | {3}", blog.BlogId, meta1, meta2, 
+                    threadId));
+            }
+
+            var meta3 = getMeta();
+
+            using (var ctx = GetDbContext())
+            {
+                ctx.Blogs.Attach(blog);
+                ctx.Blogs.Remove(blog);
+                ctx.SaveChanges();
+            }
+
+            var meta4 = getMeta();
+
+            using (var ctx = GetDbContext())
+            {
+                // Use ToArray so that there is always the same DB query.
+                Assert.AreEqual(0, ctx.Blogs.ToArray().Count(x => x.BlogId == blog.BlogId),
+                    string.Format("Found removed blog: {0} = {1}, {2}, {3}, {4} | {5}", blog.BlogId, meta1, 
+                    meta2, meta3, meta4, threadId));
+            }
+        }
+
+        /// <summary>
+        /// Executes the entity SQL.
+        /// </summary>
+        private static EntityCommand GetEntityCommand(IObjectContextAdapter ctx, string esql)
+        {
+            var objCtx = ctx.ObjectContext;
+
+            var conn = objCtx.Connection;
+            conn.Open();
+
+            var cmd = (EntityCommand) conn.CreateCommand();
+            cmd.CommandText = esql;
+
+            return cmd;
+        }
+
+        /// <summary>
+        /// Gets the test entity.
+        /// </summary>
+        private static ArrayReaderTest GetTestEntity()
+        {
+            return new ArrayReaderTest
+            {
+                DateTime = DateTime.Today,
+                Bool = true,
+                Byte = 56,
+                String = "z",
+                Decimal = (decimal)5.6,
+                Double = 7.8d,
+                Float = -4.5f,
+                Guid = Guid.NewGuid(),
+                ArrayReaderTestId = -8,
+                Long = 3,
+                Short = 5
+            };
+        }
+
+        /// <summary>
+        /// Gets the database context.
+        /// </summary>
+        private static BloggingContext GetDbContext()
+        {
+            return new BloggingContext(ConnectionString);
+        }
+
+        private class MyDbConfiguration : IgniteDbConfiguration
+        {
+            public MyDbConfiguration() : base(Ignition.GetIgnite(), null, null, Policy)
+            {
+                // No-op.
+            }
+        }
+
+        [DbConfigurationType(typeof(MyDbConfiguration))]
+        private class BloggingContext : DbContext
+        {
+            public BloggingContext(string nameOrConnectionString) : base(nameOrConnectionString)
+            {
+                // No-op.
+            }
+
+            public virtual DbSet<Blog> Blogs { get; set; }
+            public virtual DbSet<Post> Posts { get; set; }
+            public virtual DbSet<ArrayReaderTest> Tests { get; set; }
+        }
+
+        private class Blog
+        {
+            public int BlogId { get; set; }
+            public string Name { get; set; }
+
+            public virtual List<Post> Posts { get; set; }
+        }
+
+        private class Post
+        {
+            public int PostId { get; set; }
+            public string Title { get; set; }
+            public string Content { get; set; }
+
+            public int BlogId { get; set; }
+            public virtual Blog Blog { get; set; }
+        }
+
+        private class ArrayReaderTest
+        {
+            public byte Byte { get; set; }
+            public short Short { get; set; }
+            public int ArrayReaderTestId { get; set; }
+            public long Long { get; set; }
+            public float Float { get; set; }
+            public double Double { get; set; }
+            public decimal Decimal { get; set; }
+            public bool Bool { get; set; }
+            public string String { get; set; }
+            public Guid Guid { get; set; }
+            public DateTime DateTime { get; set; }
+
+            private bool Equals(ArrayReaderTest other)
+            {
+                return Byte == other.Byte && Short == other.Short &&
+                       ArrayReaderTestId == other.ArrayReaderTestId && Long == other.Long && 
+                       Float.Equals(other.Float) && Double.Equals(other.Double) && 
+                       Decimal == other.Decimal && Bool == other.Bool && String == other.String && 
+                       Guid.Equals(other.Guid) && DateTime.Equals(other.DateTime);
+            }
+
+            public override bool Equals(object obj)
+            {
+                if (ReferenceEquals(null, obj)) return false;
+                if (ReferenceEquals(this, obj)) return true;
+                if (obj.GetType() != GetType()) return false;
+                return Equals((ArrayReaderTest) obj);
+            }
+
+            public override int GetHashCode()
+            {
+                unchecked
+                {
+                    var hashCode = Byte.GetHashCode();
+                    hashCode = (hashCode*397) ^ Short.GetHashCode();
+                    hashCode = (hashCode*397) ^ ArrayReaderTestId;
+                    hashCode = (hashCode*397) ^ Long.GetHashCode();
+                    hashCode = (hashCode*397) ^ Float.GetHashCode();
+                    hashCode = (hashCode*397) ^ Double.GetHashCode();
+                    hashCode = (hashCode*397) ^ Decimal.GetHashCode();
+                    hashCode = (hashCode*397) ^ Bool.GetHashCode();
+                    hashCode = (hashCode*397) ^ String.GetHashCode();
+                    hashCode = (hashCode*397) ^ Guid.GetHashCode();
+                    hashCode = (hashCode*397) ^ DateTime.GetHashCode();
+                    return hashCode;
+                }
+            }
+        }
+
+        private class DelegateCachingPolicy : IDbCachingPolicy
+        {
+            public Func<DbQueryInfo, bool> CanBeCachedFunc { get; set; }
+
+            public Func<DbQueryInfo, int, bool> CanBeCachedRowsFunc { get; set; }
+
+            public Func<DbQueryInfo, TimeSpan> GetExpirationTimeoutFunc { get; set; }
+
+            public Func<DbQueryInfo, DbCachingMode> GetCachingStrategyFunc { get; set; }
+
+            public bool CanBeCached(DbQueryInfo queryInfo)
+            {
+                return CanBeCachedFunc == null || CanBeCachedFunc(queryInfo);
+            }
+
+            public bool CanBeCached(DbQueryInfo queryInfo, int rowCount)
+            {
+                return CanBeCachedRowsFunc == null || CanBeCachedRowsFunc(queryInfo, rowCount);
+            }
+
+            public TimeSpan GetExpirationTimeout(DbQueryInfo queryInfo)
+            {
+                return GetExpirationTimeoutFunc == null ? TimeSpan.MaxValue : GetExpirationTimeoutFunc(queryInfo);
+            }
+
+            public DbCachingMode GetCachingMode(DbQueryInfo queryInfo)
+            {
+                return GetCachingStrategyFunc == null ? DbCachingMode.ReadWrite : GetCachingStrategyFunc(queryInfo);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/Properties/AssemblyInfo.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/Properties/AssemblyInfo.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..fe5e7ce
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,39 @@
+\ufeff/*
+* 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.InteropServices;
+
+[assembly: AssemblyTitle("Apache.Ignite.EntityFramework.Tests")]
+[assembly: AssemblyDescription("")]
+[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("cda5700e-78f3-4a9e-a9b0-704cbe94651c")]
+
+[assembly: AssemblyVersion("1.8.0.14218")]
+[assembly: AssemblyFileVersion("1.8.0.14218")]
+[assembly: AssemblyInformationalVersion("1.8.0")]
+
+[assembly: CLSCompliant(true)]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/packages.config
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/packages.config b/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/packages.config
new file mode 100644
index 0000000..42a3b73
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/packages.config
@@ -0,0 +1,23 @@
+\ufeff<?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="NUnit.Runners" version="2.6.3" targetFramework="net40" />
+  <package id="EntityFramework" version="6.1.3" targetFramework="net40" />
+  <package id="EntityFramework.SqlServerCompact" version="6.1.3" targetFramework="net40" />
+  <package id="Microsoft.SqlServer.Compact" version="4.0.8876.1" 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.EntityFramework/Apache.Ignite.EntityFramework.csproj
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Apache.Ignite.EntityFramework.csproj b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Apache.Ignite.EntityFramework.csproj
new file mode 100644
index 0000000..8b3c651
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Apache.Ignite.EntityFramework.csproj
@@ -0,0 +1,93 @@
+\ufeff<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{C558518A-C1A0-4224-AAA9-A8688474B4DC}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>Apache.Ignite.EntityFramework</RootNamespace>
+    <AssemblyName>Apache.Ignite.EntityFramework</AssemblyName>
+    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <RunCodeAnalysis>true</RunCodeAnalysis>
+    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup>
+    <SignAssembly>true</SignAssembly>
+  </PropertyGroup>
+  <PropertyGroup>
+    <AssemblyOriginatorKeyFile>Apache.Ignite.EntityFramework.snk</AssemblyOriginatorKeyFile>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
+      <HintPath>..\packages\EntityFramework.6.1.3\lib\net40\EntityFramework.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Configuration" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="DbCachingMode.cs" />
+    <Compile Include="DbQueryInfo.cs" />
+    <Compile Include="IDbCachingPolicy.cs" />
+    <Compile Include="Impl\ArrayDbDataReader.cs" />
+    <Compile Include="Impl\DataReaderResult.cs" />
+    <Compile Include="Impl\DbCacheKey.cs" />
+    <Compile Include="Impl\DbCommandDefinitionProxy.cs" />
+    <Compile Include="Impl\DbCommandInfo.cs" />
+    <Compile Include="Impl\DbCommandProxy.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="Impl\DbProviderServicesProxy.cs" />
+    <Compile Include="Impl\DataReaderField.cs" />
+    <Compile Include="DbCachingPolicy.cs" />
+    <Compile Include="IgniteDbConfiguration.cs" />
+    <Compile Include="Impl\DbCache.cs" />
+    <Compile Include="Impl\DbTransactionInterceptor.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="Apache.Ignite.EntityFramework.snk" />
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Apache.Ignite.Core\Apache.Ignite.Core.csproj">
+      <Project>{4CD2F726-7E2B-46C4-A5BA-057BB82EECB6}</Project>
+      <Name>Apache.Ignite.Core</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="Apache.Ignite.EntityFramework.nuspec" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Apache.Ignite.EntityFramework.nuspec
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Apache.Ignite.EntityFramework.nuspec b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Apache.Ignite.EntityFramework.nuspec
new file mode 100644
index 0000000..b8bcd46
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Apache.Ignite.EntityFramework.nuspec
@@ -0,0 +1,57 @@
+\ufeff<?xml version="1.0"?>
+
+<!--
+  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.
+-->
+
+<!-- 
+
+Creating NuGet package:
+1) Build Apache.Ignite.sln (AnyCPU configuration)
+2) Create package (use csproj instead of nuspec so that template substitution works): 
+   nuget pack Apache.Ignite.EntityFramework.csproj -Prop Configuration=Release -Prop Platform=AnyCPU
+
+-->
+
+<package >
+    <metadata>
+        <id>Apache.Ignite.EntityFramework</id>
+        <title>Apache Ignite Entity Framework Integration</title>
+        <!-- -->
+        <version>$version$</version>
+        <authors>Apache Ignite</authors>
+        <owners>Apache Software Foundation</owners>
+        <licenseUrl>http://www.apache.org/licenses/LICENSE-2.0</licenseUrl>
+        <projectUrl>https://ignite.apache.org/</projectUrl>
+        <iconUrl>https://ignite.apache.org/images/logo_ignite_32_32.png</iconUrl>
+        <requireLicenseAcceptance>false</requireLicenseAcceptance>
+        <description>
+Apache Ignite EntityFramework Second Level Cache: caches EF query results in a distributed in-memory cache.
+            
+More info: https://apacheignite-net.readme.io/
+        </description>
+        <summary>
+            Apache Ignite EntityFramework Integration
+        </summary>
+        <releaseNotes></releaseNotes>
+        <copyright>Copyright 2016</copyright>
+        <tags>EntityFramework Second-Level Apache Ignite In-Memory Distributed Computing SQL NoSQL Grid Map Reduce Cache</tags>
+        <dependencies>
+            <dependency id="Apache.Ignite" version="[$version$]" />
+            <dependency id="EntityFramework" version="[6.1.0,7.0.0)" />
+        </dependencies>    
+    </metadata>
+</package>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Apache.Ignite.EntityFramework.snk
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Apache.Ignite.EntityFramework.snk b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Apache.Ignite.EntityFramework.snk
new file mode 100644
index 0000000..799e742
Binary files /dev/null and b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Apache.Ignite.EntityFramework.snk differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework/DbCachingMode.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework/DbCachingMode.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/DbCachingMode.cs
new file mode 100644
index 0000000..b38400c
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/DbCachingMode.cs
@@ -0,0 +1,48 @@
+\ufeff/*
+ * 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
+{
+    using System.Data.Entity;
+
+    /// <summary>
+    /// Represents a second-level caching strategy.
+    /// </summary>
+    public enum DbCachingMode
+    {
+        /// <summary>
+        /// Read-only mode, never invalidates.
+        /// <para />
+        /// Database updates are ignored in this mode. Once query results have been cached, they are kept in cache 
+        /// until expired (forever when no expiration is specified).
+        /// <para />
+        /// This mode is suitable for data that is not expected to change 
+        /// (like a list of countries and other dictionary data).
+        /// </summary>
+        ReadOnly,
+
+        /// <summary>
+        /// Read-write mode. Cached data is invalidated when underlying entity set changes.
+        /// <para />
+        /// This is "normal" cache mode which always provides correct query results.
+        /// <para />
+        /// Keep in mind that this mode works correctly only when all database changes are performed 
+        /// via <see cref="DbContext"/> with Ignite caching configured. Other database updates are not tracked.
+        /// </summary>
+        ReadWrite
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework/DbCachingPolicy.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework/DbCachingPolicy.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/DbCachingPolicy.cs
new file mode 100644
index 0000000..9e05ca9
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/DbCachingPolicy.cs
@@ -0,0 +1,71 @@
+\ufeff/*
+ * 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
+{
+    using System;
+
+    /// <summary>
+    /// Default caching policy implementation: everything is cached with <see cref="DbCachingMode.ReadWrite"/>, 
+    /// no expiration.
+    /// </summary>
+    public class DbCachingPolicy : IDbCachingPolicy
+    {
+        /// <summary>
+        /// Determines whether the specified query can be cached.
+        /// </summary>
+        /// <param name="queryInfo">The query information.</param>
+        /// <returns>
+        ///   <c>true</c> if the specified query can be cached; otherwise, <c>false</c>.
+        /// </returns>
+        public virtual bool CanBeCached(DbQueryInfo queryInfo)
+        {
+            return true;
+        }
+
+        /// <summary>
+        /// Determines whether specified number of rows should be cached.
+        /// </summary>
+        /// <param name="queryInfo">The query information.</param>
+        /// <param name="rowCount">The count of fetched rows.</param>
+        /// <returns></returns>
+        public virtual bool CanBeCached(DbQueryInfo queryInfo, int rowCount)
+        {
+            return true;
+        }
+
+        /// <summary>
+        /// Gets the absolute expiration timeout for a given query.
+        /// </summary>
+        /// <param name="queryInfo">The query information.</param>
+        /// <returns>Expiration timeout. <see cref="TimeSpan.MaxValue"/> for no expiration.</returns>
+        public virtual TimeSpan GetExpirationTimeout(DbQueryInfo queryInfo)
+        {
+            return TimeSpan.MaxValue;
+        }
+
+        /// <summary>
+        /// Gets the caching strategy for a give query.
+        /// </summary>
+        /// <param name="queryInfo">The query information.</param>
+        /// <returns>Caching strategy for the query.</returns>
+        public virtual DbCachingMode GetCachingMode(DbQueryInfo queryInfo)
+        {
+            return DbCachingMode.ReadWrite;
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework/DbQueryInfo.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework/DbQueryInfo.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/DbQueryInfo.cs
new file mode 100644
index 0000000..5ec5446
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/DbQueryInfo.cs
@@ -0,0 +1,78 @@
+\ufeff/*
+ * 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
+{
+    using System.Collections.Generic;
+    using System.Data.Common;
+    using System.Data.Entity.Core.Metadata.Edm;
+    using System.Diagnostics;
+
+    /// <summary>
+    /// Query info.
+    /// </summary>
+    public class DbQueryInfo
+    {
+        /** */
+        private readonly ICollection<EntitySetBase> _affectedEntitySets;
+
+        /** */
+        private readonly string _commandText;
+        
+        /** */
+        private readonly DbParameterCollection _parameters;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="DbQueryInfo"/> class.
+        /// </summary>
+        internal DbQueryInfo(ICollection<EntitySetBase> affectedEntitySets, string commandText, 
+            DbParameterCollection parameters)
+        {
+            Debug.Assert(affectedEntitySets != null);
+            Debug.Assert(commandText != null);
+            Debug.Assert(parameters != null);
+
+            _affectedEntitySets = affectedEntitySets;
+            _commandText = commandText;
+            _parameters = parameters;
+        }
+
+        /// <summary>
+        /// Gets the affected entity sets.
+        /// </summary>
+        public ICollection<EntitySetBase> AffectedEntitySets
+        {
+            get { return _affectedEntitySets; }
+        }
+
+        /// <summary>
+        /// Gets the command text.
+        /// </summary>
+        public string CommandText
+        {
+            get { return _commandText; }
+        }
+
+        /// <summary>
+        /// Gets the parameters.
+        /// </summary>
+        public DbParameterCollection Parameters
+        {
+            get { return _parameters; }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework/IDbCachingPolicy.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework/IDbCachingPolicy.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/IDbCachingPolicy.cs
new file mode 100644
index 0000000..504ab5e
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/IDbCachingPolicy.cs
@@ -0,0 +1,58 @@
+\ufeff/*
+ * 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
+{
+    using System;
+
+    /// <summary>
+    /// Caching policy: defines which queries should be cached.
+    /// </summary>
+    public interface IDbCachingPolicy
+    {
+        /// <summary>
+        /// Determines whether the specified query can be cached.
+        /// </summary>
+        /// <param name="queryInfo">The query information.</param>
+        /// <returns>
+        ///   <c>true</c> if the specified query can be cached; otherwise, <c>false</c>.
+        /// </returns>
+        bool CanBeCached(DbQueryInfo queryInfo);
+
+        /// <summary>
+        /// Determines whether specified number of rows should be cached.
+        /// </summary>
+        /// <param name="queryInfo">The query information.</param>
+        /// <param name="rowCount">The count of fetched rows.</param>
+        /// <returns></returns>
+        bool CanBeCached(DbQueryInfo queryInfo, int rowCount);
+
+        /// <summary>
+        /// Gets the absolute expiration timeout for a given query.
+        /// </summary>
+        /// <param name="queryInfo">The query information.</param>
+        /// <returns>Expiration timeout. <see cref="TimeSpan.MaxValue"/> for no expiration.</returns>
+        TimeSpan GetExpirationTimeout(DbQueryInfo queryInfo);
+
+        /// <summary>
+        /// Gets the caching strategy for a give query.
+        /// </summary>
+        /// <param name="queryInfo">The query information.</param>
+        /// <returns>Caching strategy for the query.</returns>
+        DbCachingMode GetCachingMode(DbQueryInfo queryInfo);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework/IgniteDbConfiguration.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework/IgniteDbConfiguration.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/IgniteDbConfiguration.cs
new file mode 100644
index 0000000..c467f94
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/IgniteDbConfiguration.cs
@@ -0,0 +1,240 @@
+\ufeff/*
+ * 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
+{
+    using System.Configuration;
+    using System.Data.Entity;
+    using System.Data.Entity.Core.Common;
+    using System.Diagnostics.CodeAnalysis;
+    using System.Globalization;
+    using Apache.Ignite.Core;
+    using Apache.Ignite.Core.Cache.Configuration;
+    using Apache.Ignite.Core.Common;
+    using Apache.Ignite.Core.Impl.Common;
+    using Apache.Ignite.EntityFramework.Impl;
+
+    /// <summary>
+    /// <see cref="DbConfiguration"/> implementation that uses Ignite as a second-level cache 
+    /// for Entity Framework queries.
+    /// </summary>
+    public class IgniteDbConfiguration : DbConfiguration
+    {
+        /// <summary>
+        /// The configuration section name to be used when starting Ignite.
+        /// </summary>
+        private const string ConfigurationSectionName = "igniteConfiguration";
+
+        /// <summary>
+        /// The default cache name to be used for cached EF data.
+        /// </summary>
+        public const string DefaultCacheNamePrefix = "entityFrameworkQueryCache";
+
+        /// <summary>
+        /// Suffix for the meta cache name.
+        /// </summary>
+        private const string MetaCacheSuffix = "_metadata";
+
+        /// <summary>
+        /// Suffix for the data cache name.
+        /// </summary>
+        private const string DataCacheSuffix = "_data";
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="IgniteDbConfiguration"/> class.
+        /// <para />
+        /// This constructor uses default Ignite instance (with null <see cref="IgniteConfiguration.GridName"/>) 
+        /// and a cache with <see cref="DefaultCacheNamePrefix"/> name.
+        /// <para />
+        /// Ignite instance will be started automatically, if it is not started yet.
+        /// <para /> 
+        /// <see cref="IgniteConfigurationSection"/> with name 
+        /// <see cref="ConfigurationSectionName"/> will be picked up when starting Ignite, if present.
+        /// </summary>
+        public IgniteDbConfiguration() 
+            : this(GetConfiguration(ConfigurationSectionName, false), 
+                  GetDefaultMetaCacheConfiguration(DefaultCacheNamePrefix), 
+                  GetDefaultDataCacheConfiguration(DefaultCacheNamePrefix), null)
+        {
+            // No-op.
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="IgniteDbConfiguration" /> class.
+        /// </summary>
+        /// <param name="configurationSectionName">Name of the configuration section.</param>
+        /// <param name="cacheNamePrefix">The cache name prefix for Data and Metadata caches.</param>
+        /// <param name="policy">The caching policy. Null for default <see cref="DbCachingPolicy" />.</param>
+        public IgniteDbConfiguration(string configurationSectionName, string cacheNamePrefix, IDbCachingPolicy policy)
+            : this(configurationSectionName,
+                GetDefaultMetaCacheConfiguration(cacheNamePrefix),
+                GetDefaultDataCacheConfiguration(cacheNamePrefix), policy)
+
+        {
+            // No-op.
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="IgniteDbConfiguration"/> class.
+        /// </summary>
+        /// <param name="configurationSectionName">Name of the configuration section.</param>
+        /// <param name="metaCacheConfiguration">
+        /// Configuration of the metadata cache which holds entity set information. Null for default configuration.
+        /// <para />
+        /// This cache holds small amount of data, but should not lose entries. At least one backup recommended.
+        /// </param>
+        /// <param name="dataCacheConfiguration">
+        /// Configuration of the data cache which holds query results. Null for default configuration.
+        /// <para />
+        /// This cache tolerates lost data and can have no backups.
+        /// </param>
+        /// <param name="policy">The caching policy. Null for default <see cref="DbCachingPolicy"/>.</param>
+        public IgniteDbConfiguration(string configurationSectionName, CacheConfiguration metaCacheConfiguration,
+            CacheConfiguration dataCacheConfiguration, IDbCachingPolicy policy)
+            : this(GetConfiguration(configurationSectionName, true), 
+                  metaCacheConfiguration, dataCacheConfiguration, policy)
+        {
+            // No-op.
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="IgniteDbConfiguration" /> class.
+        /// </summary>
+        /// <param name="igniteConfiguration">The ignite configuration to use for starting Ignite instance.</param>
+        /// <param name="metaCacheConfiguration">
+        /// Configuration of the metadata cache which holds entity set information. Null for default configuration. 
+        /// <para />
+        /// This cache holds small amount of data, but should not lose entries. At least one backup recommended.
+        /// </param>
+        /// <param name="dataCacheConfiguration">
+        /// Configuration of the data cache which holds query results. Null for default configuration.
+        /// <para />
+        /// This cache tolerates lost data and can have no backups.
+        /// </param>
+        /// <param name="policy">The caching policy. Null for default <see cref="DbCachingPolicy"/>.</param>
+        public IgniteDbConfiguration(IgniteConfiguration igniteConfiguration,
+            CacheConfiguration metaCacheConfiguration, CacheConfiguration dataCacheConfiguration,
+            IDbCachingPolicy policy)
+            : this(GetOrStartIgnite(igniteConfiguration), metaCacheConfiguration, dataCacheConfiguration, policy)
+        {
+            // No-op.
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="IgniteDbConfiguration" /> class.
+        /// </summary>
+        /// <param name="ignite">The ignite instance to use.</param>
+        /// <param name="metaCacheConfiguration">
+        /// Configuration of the metadata cache which holds entity set information. Null for default configuration. 
+        /// <para />
+        /// This cache holds small amount of data, but should not lose entries. At least one backup recommended.
+        /// </param>
+        /// <param name="dataCacheConfiguration">
+        /// Configuration of the data cache which holds query results. Null for default configuration.
+        /// <para />
+        /// This cache tolerates lost data and can have no backups.
+        /// </param>
+        /// <param name="policy">The caching policy. Null for default <see cref="DbCachingPolicy" />.</param>
+        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", 
+            Justification = "Validation is present")]
+        public IgniteDbConfiguration(IIgnite ignite, CacheConfiguration metaCacheConfiguration,
+            CacheConfiguration dataCacheConfiguration, IDbCachingPolicy policy)
+        {
+            IgniteArgumentCheck.NotNull(ignite, "ignite");
+
+            metaCacheConfiguration = metaCacheConfiguration ?? GetDefaultMetaCacheConfiguration();
+            dataCacheConfiguration = dataCacheConfiguration ?? GetDefaultDataCacheConfiguration();
+
+            var efCache = new DbCache(ignite, metaCacheConfiguration, dataCacheConfiguration);
+
+            var txHandler = new DbTransactionInterceptor(efCache);
+
+            AddInterceptor(txHandler);
+
+            // SetProviderServices is not suitable. We should replace whatever provider there is with our proxy.
+            Loaded += (sender, args) => args.ReplaceService<DbProviderServices>(
+                (services, a) => new DbProviderServicesProxy(services, policy, efCache, txHandler));
+        }
+
+        /// <summary>
+        /// Gets the Ignite instance.
+        /// </summary>
+        private static IIgnite GetOrStartIgnite(IgniteConfiguration cfg)
+        {
+            cfg = cfg ?? new IgniteConfiguration();
+
+            return Ignition.TryGetIgnite(cfg.GridName) ?? Ignition.Start(cfg);
+        }
+
+        /// <summary>
+        /// Gets the configuration.
+        /// </summary>
+        private static IgniteConfiguration GetConfiguration(string sectionName, bool throwIfAbsent)
+        {
+            IgniteArgumentCheck.NotNull(sectionName, "sectionName");
+
+            var section = ConfigurationManager.GetSection(sectionName) as IgniteConfigurationSection;
+
+            if (section != null)
+            {
+                if (section.IgniteConfiguration == null)
+                    throw new IgniteException(string.Format(CultureInfo.InvariantCulture,
+                        "Failed to initialize {0}. {1} with name {2} is defined in <configSections>, " +
+                        "but not present in configuration.",
+                        typeof(IgniteDbConfiguration), typeof(IgniteConfigurationSection), sectionName));
+
+
+                return section.IgniteConfiguration;
+            }
+
+            if (!throwIfAbsent)
+                return null;
+
+            throw new IgniteException(string.Format(CultureInfo.InvariantCulture,
+                "Failed to initialize {0}. Could not find {1} with name {2} in application configuration.",
+                typeof (IgniteDbConfiguration), typeof (IgniteConfigurationSection), sectionName));
+        }
+
+        /// <summary>
+        /// Gets the default meta cache configuration.
+        /// </summary>
+        private static CacheConfiguration GetDefaultMetaCacheConfiguration(string namePrefix = null)
+        {
+            return new CacheConfiguration((namePrefix ?? DefaultCacheNamePrefix) + MetaCacheSuffix)
+            {
+                CacheMode = CacheMode.Partitioned,
+                Backups = 1,
+                AtomicityMode = CacheAtomicityMode.Transactional,  // Required due to IGNITE-3955
+                WriteSynchronizationMode = CacheWriteSynchronizationMode.PrimarySync
+            };
+        }
+
+        /// <summary>
+        /// Gets the default data cache configuration.
+        /// </summary>
+        private static CacheConfiguration GetDefaultDataCacheConfiguration(string namePrefix = null)
+        {
+            return new CacheConfiguration((namePrefix ?? DefaultCacheNamePrefix) + DataCacheSuffix)
+            {
+                CacheMode = CacheMode.Partitioned,
+                Backups = 0,
+                AtomicityMode = CacheAtomicityMode.Atomic,
+                WriteSynchronizationMode = CacheWriteSynchronizationMode.PrimarySync
+            };
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/ArrayDbDataReader.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/ArrayDbDataReader.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/ArrayDbDataReader.cs
new file mode 100644
index 0000000..89523f4
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/ArrayDbDataReader.cs
@@ -0,0 +1,305 @@
+\ufeff/*
+ * 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;
+    using System.Data;
+    using System.Data.Common;
+    using System.Diagnostics;
+    using System.Diagnostics.CodeAnalysis;
+
+    /// <summary>
+    /// Reads the data from array.
+    /// </summary>
+    internal class ArrayDbDataReader : DbDataReader
+    {
+        /** */
+        private readonly object[][] _data;
+
+        /** */
+        private readonly DataReaderField[] _schema;
+
+        /** */
+        private int _pos = -1;
+
+        /** */
+        private bool _closed;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ArrayDbDataReader"/> class.
+        /// </summary>
+        /// <param name="data">The data.</param>
+        /// <param name="schema">The schema.</param>
+        public ArrayDbDataReader(object[][] data, DataReaderField[] schema)
+        {
+            Debug.Assert(data != null);
+            Debug.Assert(schema != null);
+
+            _data = data;
+            _schema = schema;
+        }
+
+        /** <inheritDoc /> */
+        public override void Close()
+        {
+            _closed = true;
+        }
+
+        /** <inheritDoc /> */
+        [ExcludeFromCodeCoverage]
+        public override DataTable GetSchemaTable()
+        {
+            throw new NotSupportedException();
+        }
+
+        /** <inheritDoc /> */
+        [ExcludeFromCodeCoverage]
+        public override bool NextResult()
+        {
+            return false;  // multiple result sets are not supported
+        }
+
+        /** <inheritDoc /> */
+        public override bool Read()
+        {
+            if (_pos >= _data.Length - 1)
+                return false;
+
+            _pos++;
+
+            return true;
+        }
+
+        /** <inheritDoc /> */
+        public override int Depth
+        {
+            get { return 0; }
+        }
+
+        /** <inheritDoc /> */
+        public override bool IsClosed
+        {
+            get { return _closed; }
+        }
+
+        /** <inheritDoc /> */
+        public override int RecordsAffected
+        {
+            get { return -1; }
+        }
+
+        /** <inheritDoc /> */
+        public override bool GetBoolean(int ordinal)
+        {
+            return (bool) GetValue(ordinal);
+        }
+
+        /** <inheritDoc /> */
+        public override byte GetByte(int ordinal)
+        {
+            return (byte) GetValue(ordinal);
+        }
+
+        /** <inheritDoc /> */
+        public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length)
+        {
+            Debug.Assert(buffer != null);
+
+            var data = (byte[]) GetValue(ordinal);
+
+            var size = Math.Min(buffer.Length - bufferOffset, data.Length - dataOffset);
+
+            Array.Copy(data, dataOffset, buffer, bufferOffset, size);
+
+            return size;
+        }
+
+        /** <inheritDoc /> */
+        public override char GetChar(int ordinal)
+        {
+            return (char) GetValue(ordinal);
+        }
+
+        /** <inheritDoc /> */
+        public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length)
+        {
+            Debug.Assert(buffer != null);
+
+            var data = (char[]) GetValue(ordinal);
+
+            var size = Math.Min(buffer.Length - bufferOffset, data.Length - dataOffset);
+
+            Array.Copy(data, dataOffset, buffer, bufferOffset, size);
+
+            return size;
+        }
+
+        /** <inheritDoc /> */
+        public override Guid GetGuid(int ordinal)
+        {
+            return (Guid) GetValue(ordinal);
+        }
+
+        /** <inheritDoc /> */
+        public override short GetInt16(int ordinal)
+        {
+            return (short) GetValue(ordinal);
+        }
+
+        /** <inheritDoc /> */
+        public override int GetInt32(int ordinal)
+        {
+            return (int) GetValue(ordinal);
+        }
+
+        /** <inheritDoc /> */
+        public override long GetInt64(int ordinal)
+        {
+            return (long) GetValue(ordinal);
+        }
+
+        /** <inheritDoc /> */
+        public override DateTime GetDateTime(int ordinal)
+        {
+            return (DateTime) GetValue(ordinal);
+        }
+
+        /** <inheritDoc /> */
+        public override string GetString(int ordinal)
+        {
+            return (string) GetValue(ordinal);
+        }
+
+        /** <inheritDoc /> */
+        public override object GetValue(int ordinal)
+        {
+            return GetRow()[ordinal];
+        }
+
+        /** <inheritDoc /> */
+        public override int GetValues(object[] values)
+        {
+            var row = GetRow();
+
+            var size = Math.Min(row.Length, values.Length);
+
+            Array.Copy(row, values, size);
+
+            return size;
+        }
+
+        /** <inheritDoc /> */
+        public override bool IsDBNull(int ordinal)
+        {
+            var val = GetValue(ordinal);
+
+            return val == null || val == DBNull.Value;
+        }
+
+        /** <inheritDoc /> */
+        public override int FieldCount
+        {
+            get { return _schema.Length; }
+        }
+
+        /** <inheritDoc /> */
+        public override object this[int ordinal]
+        {
+            get { return GetValue(ordinal); }
+        }
+
+        /** <inheritDoc /> */
+        public override object this[string name]
+        {
+            get { return GetValue(GetOrdinal(name)); }
+        }
+
+        /** <inheritDoc /> */
+        public override bool HasRows
+        {
+            get { return _data.Length > 0; }
+        }
+
+        /** <inheritDoc /> */
+        public override decimal GetDecimal(int ordinal)
+        {
+            return (decimal) GetValue(ordinal);
+        }
+
+        /** <inheritDoc /> */
+        public override double GetDouble(int ordinal)
+        {
+            return (double) GetValue(ordinal);
+        }
+
+        /** <inheritDoc /> */
+        public override float GetFloat(int ordinal)
+        {
+            return (float) GetValue(ordinal);
+        }
+
+        /** <inheritDoc /> */
+        public override string GetName(int ordinal)
+        {
+            return _schema[ordinal].Name;
+        }
+
+        /** <inheritDoc /> */
+        public override int GetOrdinal(string name)
+        {
+            for (int i = 0; i < _schema.Length; i++)
+            {
+                if (_schema[i].Name == name)
+                    return i;
+            }
+
+            throw new InvalidOperationException("Field not found: " + name);
+        }
+
+        /** <inheritDoc /> */
+        public override string GetDataTypeName(int ordinal)
+        {
+            return _schema[ordinal].DataType;
+        }
+
+        /** <inheritDoc /> */
+        public override Type GetFieldType(int ordinal)
+        {
+            return _schema[ordinal].FieldType;
+        }
+
+        /** <inheritDoc /> */
+        [ExcludeFromCodeCoverage]
+        public override IEnumerator GetEnumerator()
+        {
+            throw new NotSupportedException();
+        }
+
+        /// <summary>
+        /// Gets the row.
+        /// </summary>
+        private object[] GetRow()
+        {
+            if (_pos < 0)
+                throw new InvalidOperationException("Data reading has not started.");
+
+            return _data[_pos];
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DataReaderField.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DataReaderField.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DataReaderField.cs
new file mode 100644
index 0000000..0e7baf0
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DataReaderField.cs
@@ -0,0 +1,74 @@
+\ufeff/*
+ * 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;
+
+    /// <summary>
+    /// Represents a data reader field.
+    /// </summary>
+    [Serializable]
+    internal class DataReaderField
+    {
+        /** */
+        private readonly string _name;
+
+        /** */
+        private readonly Type _fieldType;
+
+        /** */
+        private readonly string _dataType;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="DataReaderField"/> class.
+        /// </summary>
+        /// <param name="name">The name.</param>
+        /// <param name="fieldType">The type.</param>
+        /// <param name="dataType">Type of the data.</param>
+        public DataReaderField(string name, Type fieldType, string dataType)
+        {
+            _name = name;
+            _fieldType = fieldType;
+            _dataType = dataType;
+        }
+
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        public string Name
+        {
+            get { return _name; }
+        }
+
+        /// <summary>
+        /// Gets the type of the field.
+        /// </summary>
+        public Type FieldType
+        {
+            get { return _fieldType; }
+        }
+
+        /// <summary>
+        /// Gets the type of the data.
+        /// </summary>
+        public string DataType
+        {
+            get { return _dataType; }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DataReaderResult.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DataReaderResult.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DataReaderResult.cs
new file mode 100644
index 0000000..48f763c
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework/Impl/DataReaderResult.cs
@@ -0,0 +1,93 @@
+\ufeff/*
+ * 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;
+    using System.Data.Common;
+    using System.Linq;
+
+    /// <summary>
+    /// Cacheable result of a DbDataReader.
+    /// </summary>
+    [Serializable]
+    internal class DataReaderResult
+    {
+        /** */
+        private readonly object[][] _data;
+
+        /** */
+        private readonly DataReaderField[] _schema;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="DataReaderResult"/> class.
+        /// </summary>
+        public DataReaderResult(IDataReader reader)
+        {
+            try
+            {
+                _data = ReadAll(reader).ToArray();
+
+                _schema = new DataReaderField[reader.FieldCount];
+
+                for (int i = 0; i < reader.FieldCount; i++)
+                {
+                    _schema[i] = new DataReaderField(reader.GetName(i), reader.GetFieldType(i), 
+                        reader.GetDataTypeName(i));
+                }
+            }
+            finally 
+            {
+                reader.Close();
+                reader.Dispose();
+            }
+        }
+
+        /// <summary>
+        /// Creates the reader over this instance.
+        /// </summary>
+        public DbDataReader CreateReader()
+        {
+            return new ArrayDbDataReader(_data, _schema);
+        }
+
+        /// <summary>
+        /// Gets the row count.
+        /// </summary>
+        public int RowCount
+        {
+            get { return _data.Length; }
+        }
+
+        /// <summary>
+        /// Reads all data from the reader.
+        /// </summary>
+        private static IEnumerable<object[]> ReadAll(IDataReader reader)
+        {
+            while (reader.Read())
+            {
+                var vals = new object[reader.FieldCount];
+
+                reader.GetValues(vals);
+
+                yield return vals;
+            }
+        }
+    }
+}


[3/3] ignite git commit: IGNITE-1915 .NET: Ignite as Entity Framework Second-Level Cache

Posted by pt...@apache.org.
IGNITE-1915 .NET: Ignite as Entity Framework Second-Level Cache


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/5b31d83f
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/5b31d83f
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/5b31d83f

Branch: refs/heads/master
Commit: 5b31d83f38732c8e92807000be858e19387108fd
Parents: 2bc234e
Author: Pavel Tupitsyn <pt...@apache.org>
Authored: Wed Nov 9 18:12:35 2016 +0300
Committer: Pavel Tupitsyn <pt...@apache.org>
Committed: Wed Nov 9 18:12:35 2016 +0300

----------------------------------------------------------------------
 .../dotnet/PlatformDotNetConfigurationEx.java   |  16 +-
 ...PlatformDotNetEntityFrameworkCacheEntry.java | 102 ++
 ...formDotNetEntityFrameworkCacheExtension.java | 353 +++++++
 .../PlatformDotNetEntityFrameworkCacheKey.java  | 164 ++++
 ...EntityFrameworkIncreaseVersionProcessor.java |  45 +
 .../Apache.Ignite.Core.Tests.NuGet.csproj       |  11 +
 .../EntityFrameworkCacheTest.cs                 |  62 ++
 .../packages.config                             |   2 +
 .../Cache/CacheAbstractTest.cs                  |  11 +-
 .../Apache.Ignite.Core.Tests/TestUtils.cs       |   2 +
 .../Impl/Binary/BinaryObjectHeader.cs           |   2 +-
 .../Apache.Ignite.EntityFramework.Tests.csproj  |  96 ++
 .../Apache.Ignite.EntityFramework.Tests.snk     | Bin 0 -> 596 bytes
 .../App.config                                  |  71 ++
 .../ArrayDbDataReaderTests.cs                   | 192 ++++
 .../DbCachingPolicyTest.cs                      |  43 +
 .../EntityFrameworkCacheInitializationTest.cs   | 137 +++
 .../EntityFrameworkCacheTest.cs                 | 942 +++++++++++++++++++
 .../Properties/AssemblyInfo.cs                  |  39 +
 .../packages.config                             |  23 +
 .../Apache.Ignite.EntityFramework.csproj        |  93 ++
 .../Apache.Ignite.EntityFramework.nuspec        |  57 ++
 .../Apache.Ignite.EntityFramework.snk           | Bin 0 -> 596 bytes
 .../DbCachingMode.cs                            |  48 +
 .../DbCachingPolicy.cs                          |  71 ++
 .../DbQueryInfo.cs                              |  78 ++
 .../IDbCachingPolicy.cs                         |  58 ++
 .../IgniteDbConfiguration.cs                    | 240 +++++
 .../Impl/ArrayDbDataReader.cs                   | 305 ++++++
 .../Impl/DataReaderField.cs                     |  74 ++
 .../Impl/DataReaderResult.cs                    |  93 ++
 .../Impl/DbCache.cs                             | 295 ++++++
 .../Impl/DbCacheKey.cs                          |  92 ++
 .../Impl/DbCommandDefinitionProxy.cs            |  51 +
 .../Impl/DbCommandInfo.cs                       | 158 ++++
 .../Impl/DbCommandProxy.cs                      | 263 ++++++
 .../Impl/DbProviderServicesProxy.cs             | 169 ++++
 .../Impl/DbTransactionInterceptor.cs            | 134 +++
 .../Properties/AssemblyInfo.cs                  |  41 +
 .../packages.config                             |  20 +
 modules/platforms/dotnet/Apache.Ignite.sln      |  28 +
 41 files changed, 4673 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/dotnet/PlatformDotNetConfigurationEx.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/dotnet/PlatformDotNetConfigurationEx.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/dotnet/PlatformDotNetConfigurationEx.java
index 34e7ce2..8448733 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/dotnet/PlatformDotNetConfigurationEx.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/dotnet/PlatformDotNetConfigurationEx.java
@@ -21,14 +21,15 @@ import org.apache.ignite.internal.logger.platform.PlatformLogger;
 import org.apache.ignite.internal.processors.platform.PlatformConfigurationEx;
 import org.apache.ignite.internal.processors.platform.cache.PlatformCacheExtension;
 import org.apache.ignite.internal.processors.platform.callback.PlatformCallbackGateway;
+import org.apache.ignite.internal.processors.platform.entityframework.PlatformDotNetEntityFrameworkCacheExtension;
 import org.apache.ignite.internal.processors.platform.memory.PlatformMemoryManagerImpl;
 import org.apache.ignite.internal.processors.platform.utils.PlatformUtils;
 import org.apache.ignite.internal.processors.platform.websession.PlatformDotNetSessionCacheExtension;
 import org.apache.ignite.platform.dotnet.PlatformDotNetConfiguration;
 import org.jetbrains.annotations.Nullable;
 
+import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 
 /**
  * Extended .Net configuration.
@@ -83,13 +84,18 @@ public class PlatformDotNetConfigurationEx extends PlatformDotNetConfiguration i
     }
 
     /** {@inheritDoc} */
-    @Override public PlatformLogger logger() {
-        return logger;
+    @Nullable @Override public Collection<PlatformCacheExtension> cacheExtensions() {
+        Collection<PlatformCacheExtension> exts = new ArrayList<>(2);
+
+        exts.add(new PlatformDotNetSessionCacheExtension());
+        exts.add(new PlatformDotNetEntityFrameworkCacheExtension());
+
+        return exts;
     }
 
     /** {@inheritDoc} */
-    @Nullable @Override public Collection<PlatformCacheExtension> cacheExtensions() {
-        return Collections.<PlatformCacheExtension>singleton(new PlatformDotNetSessionCacheExtension());
+    @Override public PlatformLogger logger() {
+        return logger;
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/entityframework/PlatformDotNetEntityFrameworkCacheEntry.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/entityframework/PlatformDotNetEntityFrameworkCacheEntry.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/entityframework/PlatformDotNetEntityFrameworkCacheEntry.java
new file mode 100644
index 0000000..676b411
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/entityframework/PlatformDotNetEntityFrameworkCacheEntry.java
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.platform.entityframework;
+
+import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.binary.BinaryRawReader;
+import org.apache.ignite.binary.BinaryRawWriter;
+import org.apache.ignite.binary.BinaryReader;
+import org.apache.ignite.binary.BinaryWriter;
+import org.apache.ignite.binary.Binarylizable;
+
+/**
+ * EntityFramework cache entry.
+ */
+public class PlatformDotNetEntityFrameworkCacheEntry implements Binarylizable {
+    /** Dependent entity set names. */
+    private String[] entitySets;
+
+    /** Cached data bytes. */
+    private byte[] data;
+
+    /**
+     * Ctor.
+     */
+    public PlatformDotNetEntityFrameworkCacheEntry() {
+        // No-op.
+    }
+
+    /**
+     * Ctor.
+     *
+     * @param entitySets Entity set names.
+     * @param data Data bytes.
+     */
+    PlatformDotNetEntityFrameworkCacheEntry(String[] entitySets, byte[] data) {
+        this.entitySets = entitySets;
+        this.data = data;
+    }
+
+    /**
+     * @return Dependent entity sets with versions.
+     */
+    public String[] entitySets() {
+        return entitySets;
+    }
+
+    /**
+     * @return Cached data bytes.
+     */
+    public byte[] data() {
+        return data;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
+        final BinaryRawWriter raw = writer.rawWriter();
+
+        if (entitySets != null) {
+            raw.writeInt(entitySets.length);
+
+            for (String entitySet : entitySets)
+                raw.writeString(entitySet);
+        }
+        else
+            raw.writeInt(-1);
+
+        raw.writeByteArray(data);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
+        BinaryRawReader raw = reader.rawReader();
+
+        int cnt = raw.readInt();
+
+        if (cnt >= 0) {
+            entitySets = new String[cnt];
+
+            for (int i = 0; i < cnt; i++)
+                entitySets[i] = raw.readString();
+        }
+        else
+            entitySets = null;
+
+        data = raw.readByteArray();
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/entityframework/PlatformDotNetEntityFrameworkCacheExtension.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/entityframework/PlatformDotNetEntityFrameworkCacheExtension.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/entityframework/PlatformDotNetEntityFrameworkCacheExtension.java
new file mode 100644
index 0000000..d4755de
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/entityframework/PlatformDotNetEntityFrameworkCacheExtension.java
@@ -0,0 +1,353 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.platform.entityframework;
+
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteCompute;
+import org.apache.ignite.cache.CachePeekMode;
+import org.apache.ignite.cluster.ClusterGroup;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.internal.binary.BinaryRawReaderEx;
+import org.apache.ignite.internal.processors.platform.cache.PlatformCache;
+import org.apache.ignite.internal.processors.platform.cache.PlatformCacheExtension;
+import org.apache.ignite.internal.processors.platform.memory.PlatformMemory;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.lang.IgniteFuture;
+import org.apache.ignite.lang.IgniteInClosure;
+import org.apache.ignite.lang.IgniteRunnable;
+import org.apache.ignite.resources.IgniteInstanceResource;
+
+import javax.cache.Cache;
+import javax.cache.processor.EntryProcessorResult;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * EntityFramework cache extension.
+ */
+@SuppressWarnings("unchecked")
+public class PlatformDotNetEntityFrameworkCacheExtension implements PlatformCacheExtension {
+    /** Extension ID. */
+    private static final int EXT_ID = 1;
+
+    /** Operation: increment entity set versions. */
+    private static final int OP_INVALIDATE_SETS = 1;
+
+    /** Operation: put item async. */
+    private static final int OP_PUT_ITEM = 2;
+
+    /** Operation: get item. */
+    private static final int OP_GET_ITEM = 3;
+
+    /** Cache key for cleanup node ID. */
+    private static final CleanupNodeId CLEANUP_NODE_ID = new CleanupNodeId();
+
+    /** Indicates whether local cleanup is in progress, per cache name. */
+    private final Map<String, Boolean> cleanupFlags = new ConcurrentHashMap<>();
+
+    /** {@inheritDoc} */
+    @Override public int id() {
+        return EXT_ID;
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override public long processInOutStreamLong(PlatformCache target, int type, BinaryRawReaderEx reader,
+        PlatformMemory mem) throws IgniteCheckedException {
+        switch (type) {
+            case OP_INVALIDATE_SETS: {
+                final IgniteCache<String, Long> metaCache = target.rawCache();
+                final String dataCacheName = reader.readString();
+
+                int cnt = reader.readInt();
+
+                assert cnt > 0;
+
+                final Set<String> entitySetNames = new HashSet(cnt);
+
+                for (int i = 0; i < cnt; i++)
+                    entitySetNames.add(reader.readString());
+
+                final Map<String, EntryProcessorResult<Long>> curVers =
+                    metaCache.invokeAll(entitySetNames, new PlatformDotNetEntityFrameworkIncreaseVersionProcessor());
+
+                if (curVers.size() != cnt)
+                    throw new IgniteCheckedException("Failed to update entity set versions [expected=" + cnt +
+                        ", actual=" + curVers.size() + ']');
+
+                Ignite grid = target.platformContext().kernalContext().grid();
+
+                startBackgroundCleanup(grid, (IgniteCache<CleanupNodeId, UUID>)(IgniteCache)metaCache,
+                    dataCacheName, curVers);
+
+                return target.writeResult(mem, null);
+            }
+
+            case OP_PUT_ITEM: {
+                String query = reader.readString();
+
+                long[] versions = null;
+                String[] entitySets = null;
+
+                int cnt = reader.readInt();
+
+                if (cnt >= 0) {
+                    versions = new long[cnt];
+                    entitySets = new String[cnt];
+
+                    for (int i = 0; i < cnt; i++) {
+                        versions[i] = reader.readLong();
+                        entitySets[i] = reader.readString();
+                    }
+                }
+
+                byte[] data = reader.readByteArray();
+
+                PlatformDotNetEntityFrameworkCacheEntry efEntry =
+                    new PlatformDotNetEntityFrameworkCacheEntry(entitySets, data);
+
+                IgniteCache<PlatformDotNetEntityFrameworkCacheKey, PlatformDotNetEntityFrameworkCacheEntry> dataCache
+                    = target.rawCache();
+
+                PlatformDotNetEntityFrameworkCacheKey key = new PlatformDotNetEntityFrameworkCacheKey(query, versions);
+
+                dataCache.put(key, efEntry);
+
+                return target.writeResult(mem, null);
+            }
+
+            case OP_GET_ITEM: {
+                String query = reader.readString();
+
+                long[] versions = null;
+
+                int cnt = reader.readInt();
+
+                if (cnt >= 0) {
+                    versions = new long[cnt];
+
+                    for (int i = 0; i < cnt; i++)
+                        versions[i] = reader.readLong();
+                }
+
+                IgniteCache<PlatformDotNetEntityFrameworkCacheKey, PlatformDotNetEntityFrameworkCacheEntry> dataCache
+                    = target.rawCache();
+
+                PlatformDotNetEntityFrameworkCacheKey key = new PlatformDotNetEntityFrameworkCacheKey(query, versions);
+
+                PlatformDotNetEntityFrameworkCacheEntry entry = dataCache.get(key);
+
+                byte[] data = entry == null ? null : entry.data();
+
+                return target.writeResult(mem, data);
+            }
+        }
+
+        throw new IgniteCheckedException("Unsupported operation type: " + type);
+    }
+
+    /**
+     * Starts the background cleanup of old cache entries.
+     *
+     * @param grid Grid.
+     * @param metaCache Meta cache.
+     * @param dataCacheName Data cache name.
+     * @param currentVersions Current versions.
+     */
+    private void startBackgroundCleanup(Ignite grid, final Cache<CleanupNodeId, UUID> metaCache,
+        final String dataCacheName, final Map<String, EntryProcessorResult<Long>> currentVersions) {
+        if (cleanupFlags.containsKey(dataCacheName))
+            return;  // Current node already performs cleanup.
+
+        if (!trySetGlobalCleanupFlag(grid, metaCache))
+            return;
+
+        cleanupFlags.put(dataCacheName, true);
+
+        final ClusterGroup dataNodes = grid.cluster().forDataNodes(dataCacheName);
+
+        IgniteCompute asyncCompute = grid.compute(dataNodes).withAsync();
+
+        asyncCompute.broadcast(new RemoveOldEntriesRunnable(dataCacheName, currentVersions));
+
+        asyncCompute.future().listen(new CleanupCompletionListener(metaCache, dataCacheName));
+    }
+
+    /**
+     * Tries to set the global cleanup node id to current node.
+     *
+     * @param grid Grid.
+     * @param metaCache Meta cache.
+     *
+     * @return True if successfully set the flag indicating that current node performs the cleanup; otherwise false.
+     */
+    private boolean trySetGlobalCleanupFlag(Ignite grid, final Cache<CleanupNodeId, UUID> metaCache) {
+        final UUID localNodeId = grid.cluster().localNode().id();
+
+        while (true) {
+            // Get the node performing cleanup.
+            UUID nodeId = metaCache.get(CLEANUP_NODE_ID);
+
+            if (nodeId == null) {
+                if (metaCache.putIfAbsent(CLEANUP_NODE_ID, localNodeId))
+                    return true;  // Successfully reserved cleanup to local node.
+
+                // Failed putIfAbsent: someone else may have started cleanup. Retry the check.
+                continue;
+            }
+
+            if (nodeId.equals(localNodeId))
+                return false;  // Current node already performs cleanup.
+
+            if (grid.cluster().node(nodeId) != null)
+                return false;  // Another node already performs cleanup and is alive.
+
+            // Node that performs cleanup has disconnected.
+            if (metaCache.replace(CLEANUP_NODE_ID, nodeId, localNodeId))
+                return true;  // Successfully replaced disconnected node id with our id.
+
+            // Replace failed: someone else started cleanup.
+            return false;
+        }
+    }
+
+    /**
+     * Removes old cache entries locally.
+     *
+     * @param ignite Ignite.
+     * @param dataCacheName Cache name.
+     * @param currentVersions Current versions.
+     */
+    private static void removeOldEntries(final Ignite ignite, final String dataCacheName,
+        final Map<String, EntryProcessorResult<Long>> currentVersions) {
+
+        IgniteCache<PlatformDotNetEntityFrameworkCacheKey, PlatformDotNetEntityFrameworkCacheEntry> cache =
+            ignite.cache(dataCacheName);
+
+        Set<PlatformDotNetEntityFrameworkCacheKey> keysToRemove = new TreeSet<>();
+
+        ClusterNode localNode = ignite.cluster().localNode();
+
+        for (Cache.Entry<PlatformDotNetEntityFrameworkCacheKey, PlatformDotNetEntityFrameworkCacheEntry> cacheEntry :
+            cache.localEntries(CachePeekMode.ALL)) {
+            // Check if we are on a primary node for the key, since we use CachePeekMode.ALL
+            // and we don't want to process backup entries.
+            if (!ignite.affinity(dataCacheName).isPrimary(localNode, cacheEntry.getKey()))
+                continue;
+
+            long[] versions = cacheEntry.getKey().versions();
+            String[] entitySets = cacheEntry.getValue().entitySets();
+
+            for (int i = 0; i < entitySets.length; i++) {
+                EntryProcessorResult<Long> curVer = currentVersions.get(entitySets[i]);
+
+                if (curVer != null && versions[i] < curVer.get())
+                    keysToRemove.add(cacheEntry.getKey());
+            }
+        }
+
+        cache.removeAll(keysToRemove);
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(PlatformDotNetEntityFrameworkCacheExtension.class, this);
+    }
+
+    /**
+     * Cache key for cleanup node id.
+     */
+    private static class CleanupNodeId {
+        // No-op.
+    }
+
+    /**
+     * Old entries remover.
+     */
+    private static class RemoveOldEntriesRunnable implements IgniteRunnable {
+        /** */
+        private static final long serialVersionUID = 0L;
+
+        /** */
+        private final String dataCacheName;
+
+        /** */
+        private final Map<String, EntryProcessorResult<Long>> currentVersions;
+
+        /** Inject Ignite. */
+        @IgniteInstanceResource
+        private Ignite ignite;
+
+        /**
+         * Ctor.
+         *
+         * @param dataCacheName Name of the cache to clean up.
+         * @param currentVersions Map of current entity set versions.
+         */
+        private RemoveOldEntriesRunnable(String dataCacheName,
+            Map<String, EntryProcessorResult<Long>> currentVersions) {
+            this.dataCacheName = dataCacheName;
+            this.currentVersions = currentVersions;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void run() {
+            removeOldEntries(ignite, dataCacheName, currentVersions);
+        }
+    }
+
+    /**
+     * Cleanup completion listener.
+     */
+    private class CleanupCompletionListener implements IgniteInClosure<IgniteFuture<Object>> {
+        /** */
+        private static final long serialVersionUID = 0L;
+
+        /** */
+        private final Cache<CleanupNodeId, UUID> metaCache;
+
+        /** */
+        private final String dataCacheName;
+
+        /**
+         * Ctor.
+         *
+         * @param metaCache Metadata cache.
+         * @param dataCacheName Data cache name.
+         */
+        private CleanupCompletionListener(Cache<CleanupNodeId, UUID> metaCache, String dataCacheName) {
+            this.metaCache = metaCache;
+            this.dataCacheName = dataCacheName;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void apply(IgniteFuture<Object> future) {
+            // Reset distributed cleanup flag.
+            metaCache.remove(CLEANUP_NODE_ID);
+
+            // Reset local cleanup flag.
+            cleanupFlags.remove(dataCacheName);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/entityframework/PlatformDotNetEntityFrameworkCacheKey.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/entityframework/PlatformDotNetEntityFrameworkCacheKey.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/entityframework/PlatformDotNetEntityFrameworkCacheKey.java
new file mode 100644
index 0000000..60fdaec
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/entityframework/PlatformDotNetEntityFrameworkCacheKey.java
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.platform.entityframework;
+
+import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.binary.BinaryRawReader;
+import org.apache.ignite.binary.BinaryRawWriter;
+import org.apache.ignite.binary.BinaryReader;
+import org.apache.ignite.binary.BinaryWriter;
+import org.apache.ignite.binary.Binarylizable;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+
+/**
+ * EntityFramework cache key: query + versions.
+ */
+@SuppressWarnings("WeakerAccess")
+public class PlatformDotNetEntityFrameworkCacheKey
+    implements Binarylizable, Comparable<PlatformDotNetEntityFrameworkCacheKey> {
+    /** Query text. */
+    private String query;
+
+    /** Entity set versions. */
+    private long[] versions;
+
+    /**
+     * Ctor.
+     */
+    public PlatformDotNetEntityFrameworkCacheKey() {
+        // No-op.
+    }
+
+    /**
+     * Ctor.
+     *
+     * @param query Query text.
+     * @param versions Versions.
+     */
+    PlatformDotNetEntityFrameworkCacheKey(String query, long[] versions) {
+        assert query != null;
+
+        this.query = query;
+        this.versions = versions;
+    }
+
+    /**
+     * Gets the query text.
+     *
+     * @return Query text.
+     */
+    public String query() {
+        return query;
+    }
+
+    /**
+     * Gets the entity set versions.
+     *
+     * @return Entity set versions.
+     */
+    public long[] versions() {
+        return versions;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+
+        if (o == null || getClass() != o.getClass())
+            return false;
+
+        PlatformDotNetEntityFrameworkCacheKey key = (PlatformDotNetEntityFrameworkCacheKey)o;
+
+        //noinspection SimplifiableIfStatement
+        if (!query.equals(key.query))
+            return false;
+
+        return Arrays.equals(versions, key.versions);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        int result = query.hashCode();
+
+        result = 31 * result + Arrays.hashCode(versions);
+
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
+        final BinaryRawWriter raw = writer.rawWriter();
+
+        raw.writeString(query);
+
+        if (versions != null) {
+            raw.writeInt(versions.length);
+
+            for (long ver : versions)
+                raw.writeLong(ver);
+        }
+        else
+            raw.writeInt(-1);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
+        BinaryRawReader raw = reader.rawReader();
+
+        query = raw.readString();
+
+        int cnt = raw.readInt();
+
+        if (cnt >= 0) {
+            versions = new long[cnt];
+
+            for (int i = 0; i < cnt; i++)
+                versions[i] = raw.readLong();
+        }
+        else
+            versions = null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int compareTo(@NotNull PlatformDotNetEntityFrameworkCacheKey o) {
+        int cmpQuery = query.compareTo(o.query);
+
+        if (cmpQuery != 0)
+            return cmpQuery;
+
+        if (versions == null) {
+            return o.versions == null ? 0 : -1;
+        }
+
+        if (o.versions == null)
+            return 1;
+
+        assert versions.length == o.versions.length;
+
+        for (int i = 0; i < versions.length; i++) {
+            if (versions[i] != o.versions[i]) {
+                return versions[i] > o.versions[i] ? 1 : -1;
+            }
+        }
+
+        return 0;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/entityframework/PlatformDotNetEntityFrameworkIncreaseVersionProcessor.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/entityframework/PlatformDotNetEntityFrameworkIncreaseVersionProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/entityframework/PlatformDotNetEntityFrameworkIncreaseVersionProcessor.java
new file mode 100644
index 0000000..f10138a
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/entityframework/PlatformDotNetEntityFrameworkIncreaseVersionProcessor.java
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.platform.entityframework;
+
+import org.apache.ignite.cache.CacheEntryProcessor;
+
+import javax.cache.processor.EntryProcessorException;
+import javax.cache.processor.MutableEntry;
+
+/**
+ * Entry processor that increments entity set version number.
+ */
+public class PlatformDotNetEntityFrameworkIncreaseVersionProcessor implements CacheEntryProcessor<String, Long, Long> {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** {@inheritDoc} */
+    @Override public Long process(MutableEntry<String, Long> entry, Object... args) throws EntryProcessorException {
+        Long val = entry.getValue();
+
+        if (val == null)
+            val = 0L;
+
+        val++;
+
+        entry.setValue(val);
+
+        return val;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/Apache.Ignite.Core.Tests.NuGet.csproj
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/Apache.Ignite.Core.Tests.NuGet.csproj b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/Apache.Ignite.Core.Tests.NuGet.csproj
index a71d1d8..4452ac7 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/Apache.Ignite.Core.Tests.NuGet.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/Apache.Ignite.Core.Tests.NuGet.csproj
@@ -95,6 +95,16 @@
       <HintPath>packages\Apache.Ignite.Log4Net.1.8.0\lib\net40\Apache.Ignite.Log4Net.dll</HintPath>
       <Private>True</Private>
     </Reference>
+    <Reference Include="Apache.Ignite.EntityFramework">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>packages\Apache.Ignite.EntityFramework.1.8.0\lib\net40\Apache.Ignite.EntityFramework.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
+      <HintPath>packages\EntityFramework.6.1.0\lib\net40\EntityFramework.dll</HintPath>
+      <SpecificVersion>False</SpecificVersion>
+      <Private>True</Private>
+    </Reference>
     <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
       <HintPath>packages\NLog.4.3.7\lib\net40\NLog.dll</HintPath>
       <Private>True</Private>
@@ -127,6 +137,7 @@
     <Compile Include="AspNetTest.cs" />
     <Compile Include="ComputeTest.cs" />
     <Compile Include="SchemaTest.cs" />
+    <Compile Include="EntityFrameworkCacheTest.cs" />
     <Compile Include="StartupTest.cs" />
     <Compile Include="CacheTest.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/EntityFrameworkCacheTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/EntityFrameworkCacheTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/EntityFrameworkCacheTest.cs
new file mode 100644
index 0000000..b4781ce
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/EntityFrameworkCacheTest.cs
@@ -0,0 +1,62 @@
+\ufeff/*
+ * 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.Core.Tests.NuGet
+{
+    using Apache.Ignite.Core.Cache.Configuration;
+    using Apache.Ignite.EntityFramework;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Tests the EntityFramework integration.
+    /// </summary>
+    public class EntityFrameworkCacheTest
+    {
+        /// <summary>
+        /// Tests cache startup and basic operation.
+        /// </summary>
+        [Test]
+        public void TestStartupPutGet()
+        {
+            var cfg = new IgniteConfiguration
+            {
+                DiscoverySpi = TestUtil.GetLocalDiscoverySpi(),
+                GridName = "myGrid"
+            };
+            
+            // ReSharper disable once ObjectCreationAsStatement
+            new IgniteDbConfiguration(cfg,
+                new CacheConfiguration("efMetaCache") {AtomicityMode = CacheAtomicityMode.Transactional},
+                new CacheConfiguration("efDataCache"), null);
+
+            var ignite = Ignition.GetIgnite(cfg.GridName);
+            Assert.IsNotNull(ignite);
+
+            Assert.IsNotNull(ignite.GetCache<string, object>("efMetaCache"));
+            Assert.IsNotNull(ignite.GetCache<string, object>("efDataCache"));
+        }
+
+        /// <summary>
+        /// Test teardown.
+        /// </summary>
+        [TearDown]
+        public void TearDown()
+        {
+            Ignition.StopAll(true);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/packages.config
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/packages.config b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/packages.config
index 80454e0..a7c48f3 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/packages.config
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/packages.config
@@ -22,8 +22,10 @@
   <package id="Apache.Ignite.NLog" version="1.8.0" targetFramework="net40" />
   <package id="Apache.Ignite.Log4Net" version="1.8.0" targetFramework="net40" />
   <package id="Apache.Ignite.Schema" version="1.8.0" targetFramework="net40" />
+  <package id="Apache.Ignite.EntityFramework" version="1.8.0" targetFramework="net40" />
   <package id="NLog" version="4.3.7" targetFramework="net40" />
   <package id="NUnit.Runners" version="2.6.3" targetFramework="net40" />
   <package id="Remotion.Linq" version="2.0.1" targetFramework="net40" />
   <package id="log4net" version="2.0.5" targetFramework="net40" />
+  <package id="EntityFramework" version="6.1.0" 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.Core.Tests/Cache/CacheAbstractTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/CacheAbstractTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/CacheAbstractTest.cs
index 26c1096..2a2d588 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/CacheAbstractTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/CacheAbstractTest.cs
@@ -956,10 +956,17 @@ namespace Apache.Ignite.Core.Tests.Cache
             Assert.IsFalse(cache0.ContainsKey(key0));
             Assert.IsFalse(cache0.ContainsKey(key1));
 
+            // Test sliding expiration
             cache0.Put(key0, key0);
             cache0.Put(key1, key1);
-            cache.Get(key0); 
-            cache.Get(key1);
+            for (var i = 0; i < 3; i++)
+            {
+                Thread.Sleep(50);
+
+                // Prolong expiration by touching the entry
+                cache.Get(key0);
+                cache.Get(key1);
+            }
             Assert.IsTrue(cache0.ContainsKey(key0));
             Assert.IsTrue(cache0.ContainsKey(key1));
             Thread.Sleep(200);

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestUtils.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestUtils.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestUtils.cs
index 4ff3fea..5a9c824 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestUtils.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestUtils.cs
@@ -22,6 +22,7 @@ namespace Apache.Ignite.Core.Tests
     using System.Collections.Concurrent;
     using System.Collections.Generic;
     using System.Diagnostics;
+    using System.Diagnostics.CodeAnalysis;
     using System.Linq;
     using System.Threading;
     using Apache.Ignite.Core.Discovery.Tcp;
@@ -346,6 +347,7 @@ namespace Apache.Ignite.Core.Tests
         /// <summary>
         /// Runs the test in new process.
         /// </summary>
+        [SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")]
         public static void RunTestInNewProcess(string fixtureName, string testName)
         {
             var procStart = new ProcessStartInfo

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryObjectHeader.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryObjectHeader.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryObjectHeader.cs
index bb5c207..0e5ad2a 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryObjectHeader.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryObjectHeader.cs
@@ -265,7 +265,7 @@ namespace Apache.Ignite.Core.Impl.Binary
 
                 Debug.Assert(hdr.Version == BinaryUtils.ProtoVer);
                 Debug.Assert(hdr.SchemaOffset <= hdr.Length);
-                Debug.Assert(hdr.SchemaOffset >= Size);
+                Debug.Assert(hdr.SchemaOffset >= Size || !hdr.HasSchema);
 
             }
             else

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/Apache.Ignite.EntityFramework.Tests.csproj
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/Apache.Ignite.EntityFramework.Tests.csproj b/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/Apache.Ignite.EntityFramework.Tests.csproj
new file mode 100644
index 0000000..9711087
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/Apache.Ignite.EntityFramework.Tests.csproj
@@ -0,0 +1,96 @@
+\ufeff<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{CDA5700E-78F3-4A9E-A9B0-704CBE94651C}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>Apache.Ignite.EntityFramework.Tests</RootNamespace>
+    <AssemblyName>Apache.Ignite.EntityFramework.Tests</AssemblyName>
+    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup>
+    <SignAssembly>true</SignAssembly>
+  </PropertyGroup>
+  <PropertyGroup>
+    <AssemblyOriginatorKeyFile>Apache.Ignite.EntityFramework.Tests.snk</AssemblyOriginatorKeyFile>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="EntityFramework">
+      <HintPath>..\packages\EntityFramework.6.1.3\lib\net40\EntityFramework.dll</HintPath>
+    </Reference>
+    <Reference Include="EntityFramework.SqlServerCompact">
+      <HintPath>..\packages\EntityFramework.SqlServerCompact.6.1.3\lib\net40\EntityFramework.SqlServerCompact.dll</HintPath>
+    </Reference>
+    <Reference Include="nunit.framework">
+      <HintPath>..\packages\NUnit.Runners.2.6.3\tools\nunit.framework.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\Microsoft.SqlServer.Compact.4.0.8876.1\lib\net40\System.Data.SqlServerCe.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Transactions" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="ArrayDbDataReaderTests.cs" />
+    <Compile Include="DbCachingPolicyTest.cs" />
+    <Compile Include="EntityFrameworkCacheInitializationTest.cs" />
+    <Compile Include="EntityFrameworkCacheTest.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Apache.Ignite.Core.Tests\Apache.Ignite.Core.Tests.csproj">
+      <Project>{6a62f66c-da5b-4fbb-8ce7-a95f740fdc7a}</Project>
+      <Name>Apache.Ignite.Core.Tests</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\Apache.Ignite.Core\Apache.Ignite.Core.csproj">
+      <Project>{4cd2f726-7e2b-46c4-a5ba-057bb82eecb6}</Project>
+      <Name>Apache.Ignite.Core</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\Apache.Ignite.EntityFramework\Apache.Ignite.EntityFramework.csproj">
+      <Project>{c558518a-c1a0-4224-aaa9-a8688474b4dc}</Project>
+      <Name>Apache.Ignite.EntityFramework</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="Apache.Ignite.EntityFramework.Tests.snk" />
+    <None Include="App.config" />
+    <None Include="packages.config">
+      <SubType>Designer</SubType>
+    </None>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <PropertyGroup>
+    <PostBuildEvent>
+		if not exist "$(TargetDir)x86" md "$(TargetDir)x86"
+		xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\x86\*.*" "$(TargetDir)x86"
+		if not exist "$(TargetDir)amd64" md "$(TargetDir)amd64"
+		xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\amd64\*.*" "$(TargetDir)amd64"
+	</PostBuildEvent>
+  </PropertyGroup>
+</Project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/Apache.Ignite.EntityFramework.Tests.snk
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/Apache.Ignite.EntityFramework.Tests.snk b/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/Apache.Ignite.EntityFramework.Tests.snk
new file mode 100644
index 0000000..5ef85a6
Binary files /dev/null and b/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/Apache.Ignite.EntityFramework.Tests.snk differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/App.config
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/App.config b/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/App.config
new file mode 100644
index 0000000..3527920
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/App.config
@@ -0,0 +1,71 @@
+\ufeff<?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.
+-->
+<configuration>
+    <configSections>
+        <section name="igniteConfiguration" type="Apache.Ignite.Core.IgniteConfigurationSection, Apache.Ignite.Core" />
+        <section name="igniteConfiguration2" type="Apache.Ignite.Core.IgniteConfigurationSection, Apache.Ignite.Core" />
+        <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
+    </configSections>
+    <runtime>
+        <gcServer enabled="true" />
+    </runtime>
+
+    <igniteConfiguration xmlns="http://ignite.apache.org/schema/dotnet/IgniteConfigurationSection" gridName="myGrid1">
+        <discoverySpi type="TcpDiscoverySpi">
+            <ipFinder type="TcpDiscoveryStaticIpFinder">
+                <endpoints>
+                    <string>127.0.0.1:47500</string>
+                </endpoints>
+            </ipFinder>
+        </discoverySpi>
+        <cacheConfiguration>
+            <cacheConfiguration name="cacheName" />
+        </cacheConfiguration>
+    </igniteConfiguration>
+
+    <igniteConfiguration2 gridName="myGrid2" localhost="127.0.0.1">
+        <discoverySpi type="TcpDiscoverySpi">
+            <ipFinder type="TcpDiscoveryStaticIpFinder">
+                <endpoints>
+                    <string>127.0.0.1:47500</string>
+                </endpoints>
+            </ipFinder>
+        </discoverySpi>
+        <cacheConfiguration>
+            <cacheConfiguration name="cacheName2" atomicityMode="Transactional" />
+        </cacheConfiguration>
+    </igniteConfiguration2>
+
+    <entityFramework>
+        <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlCeConnectionFactory, EntityFramework">
+            <parameters>
+                <parameter value="System.Data.SqlServerCe.4.0" />
+            </parameters>
+        </defaultConnectionFactory>
+        <providers>
+            <provider invariantName="System.Data.SqlServerCe.4.0" type="System.Data.Entity.SqlServerCompact.SqlCeProviderServices, EntityFramework.SqlServerCompact" />
+        </providers>
+    </entityFramework>
+
+    <system.data>
+        <DbProviderFactories>
+            <remove invariant="System.Data.SqlServerCe.4.0" />
+            <add name="Microsoft SQL Server Compact Data Provider 4.0" invariant="System.Data.SqlServerCe.4.0" description=".NET Framework Data Provider for Microsoft SQL Server Compact" type="System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" />
+        </DbProviderFactories>
+    </system.data>
+</configuration>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/ArrayDbDataReaderTests.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/ArrayDbDataReaderTests.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/ArrayDbDataReaderTests.cs
new file mode 100644
index 0000000..f67fed4
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/ArrayDbDataReaderTests.cs
@@ -0,0 +1,192 @@
+\ufeff/*
+ * 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.Tests
+{
+    using System;
+    using System.Linq;
+    using Apache.Ignite.EntityFramework.Impl;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Tests for <see cref="ArrayDbDataReader"/>.
+    /// </summary>
+    public class ArrayDbDataReaderTests
+    {
+        /// <summary>
+        /// Tests the reader.
+        /// </summary>
+        [Test]
+        public void TestReader()
+        {
+            var dateTime = DateTime.Now;
+            var guid = Guid.NewGuid();
+
+            var data = new[]
+            {
+                new object[]
+                {
+                    (byte) 1, (short) 2, 3, (long) 4, (float) 5, (double) 6, (decimal) 7, "8", '9', dateTime,
+                    guid, false, new byte[] {1,2}, new[] {'a','b'}
+                }
+            };
+
+            var schema = new []
+            {
+                new DataReaderField("fbyte", typeof(byte), "by"),
+                new DataReaderField("fshort", typeof(short), "sh"),
+                new DataReaderField("fint", typeof(int), "in"),
+                new DataReaderField("flong", typeof(long), "lo"),
+                new DataReaderField("ffloat", typeof(float), "fl"),
+                new DataReaderField("fdouble", typeof(double), "do"),
+                new DataReaderField("fdecimal", typeof(decimal), "de"),
+                new DataReaderField("fstring", typeof(string), "st"),
+                new DataReaderField("fchar", typeof(char), "ch"),
+                new DataReaderField("fDateTime", typeof(DateTime), "Da"),
+                new DataReaderField("fGuid", typeof(Guid), "Gu"),
+                new DataReaderField("fbool", typeof(bool), "bo"),
+                new DataReaderField("fbytes", typeof(byte[]), "bb"),
+                new DataReaderField("fchars", typeof(char[]), "cc"),
+            };
+
+            // Create reader,
+            var reader = new ArrayDbDataReader(data, schema);
+
+            // Check basic props.
+            Assert.IsTrue(reader.Read());
+            Assert.AreEqual(0, reader.Depth);
+            Assert.AreEqual(-1, reader.RecordsAffected);
+            Assert.AreEqual(14, reader.FieldCount);
+            Assert.AreEqual(14, reader.VisibleFieldCount);
+            Assert.IsFalse(reader.IsClosed);
+            Assert.IsTrue(reader.HasRows);
+
+            // Check reading.
+            var data2 = new object[14];
+            Assert.AreEqual(14, reader.GetValues(data2));
+            Assert.AreEqual(data[0], data2);
+
+            Assert.AreEqual(1, reader.GetByte(reader.GetOrdinal("fbyte")));
+            Assert.AreEqual("by", reader.GetDataTypeName(0));
+            Assert.AreEqual(typeof(byte), reader.GetFieldType(0));
+            Assert.AreEqual("fbyte", reader.GetName(0));
+            Assert.AreEqual(1, reader["fbyte"]);
+            Assert.AreEqual(1, reader[0]);
+
+            Assert.AreEqual(2, reader.GetInt16(reader.GetOrdinal("fshort")));
+            Assert.AreEqual("sh", reader.GetDataTypeName(1));
+            Assert.AreEqual(typeof(short), reader.GetFieldType(1));
+            Assert.AreEqual("fshort", reader.GetName(1));
+            Assert.AreEqual(2, reader["fshort"]);
+            Assert.AreEqual(2, reader[1]);
+
+            Assert.AreEqual(3, reader.GetInt32(reader.GetOrdinal("fint")));
+            Assert.AreEqual("in", reader.GetDataTypeName(2));
+            Assert.AreEqual(typeof(int), reader.GetFieldType(2));
+            Assert.AreEqual("fint", reader.GetName(2));
+            Assert.AreEqual(3, reader["fint"]);
+            Assert.AreEqual(3, reader[2]);
+
+            Assert.AreEqual(4, reader.GetInt64(reader.GetOrdinal("flong")));
+            Assert.AreEqual("lo", reader.GetDataTypeName(3));
+            Assert.AreEqual(typeof(long), reader.GetFieldType(3));
+            Assert.AreEqual("flong", reader.GetName(3));
+            Assert.AreEqual(4, reader["flong"]);
+            Assert.AreEqual(4, reader[3]);
+
+            Assert.AreEqual(5, reader.GetFloat(reader.GetOrdinal("ffloat")));
+            Assert.AreEqual("fl", reader.GetDataTypeName(4));
+            Assert.AreEqual(typeof(float), reader.GetFieldType(4));
+            Assert.AreEqual("ffloat", reader.GetName(4));
+            Assert.AreEqual(5, reader["ffloat"]);
+            Assert.AreEqual(5, reader[4]);
+
+            Assert.AreEqual(6, reader.GetDouble(reader.GetOrdinal("fdouble")));
+            Assert.AreEqual("do", reader.GetDataTypeName(5));
+            Assert.AreEqual(typeof(double), reader.GetFieldType(5));
+            Assert.AreEqual("fdouble", reader.GetName(5));
+            Assert.AreEqual(6, reader["fdouble"]);
+            Assert.AreEqual(6, reader[5]);
+
+            Assert.AreEqual(7, reader.GetDecimal(reader.GetOrdinal("fdecimal")));
+            Assert.AreEqual("de", reader.GetDataTypeName(6));
+            Assert.AreEqual(typeof(decimal), reader.GetFieldType(6));
+            Assert.AreEqual("fdecimal", reader.GetName(6));
+            Assert.AreEqual(7, reader["fdecimal"]);
+            Assert.AreEqual(7, reader[6]);
+
+            Assert.AreEqual("8", reader.GetString(reader.GetOrdinal("fstring")));
+            Assert.AreEqual("st", reader.GetDataTypeName(7));
+            Assert.AreEqual(typeof(string), reader.GetFieldType(7));
+            Assert.AreEqual("fstring", reader.GetName(7));
+            Assert.AreEqual("8", reader["fstring"]);
+            Assert.AreEqual("8", reader[7]);
+
+            Assert.AreEqual('9', reader.GetChar(reader.GetOrdinal("fchar")));
+            Assert.AreEqual("ch", reader.GetDataTypeName(8));
+            Assert.AreEqual(typeof(char), reader.GetFieldType(8));
+            Assert.AreEqual("fchar", reader.GetName(8));
+            Assert.AreEqual('9', reader["fchar"]);
+            Assert.AreEqual('9', reader[8]);
+
+            Assert.AreEqual(dateTime, reader.GetDateTime(reader.GetOrdinal("fDateTime")));
+            Assert.AreEqual("Da", reader.GetDataTypeName(9));
+            Assert.AreEqual(typeof(DateTime), reader.GetFieldType(9));
+            Assert.AreEqual("fDateTime", reader.GetName(9));
+            Assert.AreEqual(dateTime, reader["fDateTime"]);
+            Assert.AreEqual(dateTime, reader[9]);
+
+            Assert.AreEqual(guid, reader.GetGuid(reader.GetOrdinal("fGuid")));
+            Assert.AreEqual("Gu", reader.GetDataTypeName(10));
+            Assert.AreEqual(typeof(Guid), reader.GetFieldType(10));
+            Assert.AreEqual("fGuid", reader.GetName(10));
+            Assert.AreEqual(guid, reader["fGuid"]);
+            Assert.AreEqual(guid, reader[10]);
+
+            Assert.AreEqual(false, reader.GetBoolean(reader.GetOrdinal("fbool")));
+            Assert.AreEqual("bo", reader.GetDataTypeName(11));
+            Assert.AreEqual(typeof(bool), reader.GetFieldType(11));
+            Assert.AreEqual("fbool", reader.GetName(11));
+            Assert.AreEqual(false, reader["fbool"]);
+            Assert.AreEqual(false, reader[11]);
+
+            var bytes = new byte[2];
+            Assert.AreEqual(2, reader.GetBytes(reader.GetOrdinal("fbytes"),0, bytes, 0, 2));
+            Assert.AreEqual(data[0][12], bytes);
+            Assert.AreEqual("bb", reader.GetDataTypeName(12));
+            Assert.AreEqual(typeof(byte[]), reader.GetFieldType(12));
+            Assert.AreEqual("fbytes", reader.GetName(12));
+            Assert.AreEqual(data[0][12], reader["fbytes"]);
+            Assert.AreEqual(data[0][12], reader[12]);
+
+            var chars = new char[2];
+            Assert.AreEqual(2, reader.GetChars(reader.GetOrdinal("fchars"),0, chars, 0, 2));
+            Assert.AreEqual(data[0][13], chars);
+            Assert.AreEqual("cc", reader.GetDataTypeName(13));
+            Assert.AreEqual(typeof(char[]), reader.GetFieldType(13));
+            Assert.AreEqual("fchars", reader.GetName(13));
+            Assert.AreEqual(data[0][13], reader["fchars"]);
+            Assert.AreEqual(data[0][13], reader[13]);
+
+            Assert.IsFalse(Enumerable.Range(0, 14).Any(x => reader.IsDBNull(x)));
+
+            // Close.
+            reader.Close();
+            Assert.IsTrue(reader.IsClosed);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/DbCachingPolicyTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/DbCachingPolicyTest.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/DbCachingPolicyTest.cs
new file mode 100644
index 0000000..c9456b6
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/DbCachingPolicyTest.cs
@@ -0,0 +1,43 @@
+\ufeff/*
+ * 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.Tests
+{
+    using System;
+    using Apache.Ignite.EntityFramework;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Tests for <see cref="DbCachingPolicy"/>.
+    /// </summary>
+    public class DbCachingPolicyTest
+    {
+        /// <summary>
+        /// Tests the default implementation.
+        /// </summary>
+        [Test]
+        public void TestDefaultImpl()
+        {
+            var plc = new DbCachingPolicy();
+
+            Assert.IsTrue(plc.CanBeCached(null));
+            Assert.IsTrue(plc.CanBeCached(null, 0));
+            Assert.AreEqual(TimeSpan.MaxValue, plc.GetExpirationTimeout(null));
+            Assert.AreEqual(DbCachingMode.ReadWrite, plc.GetCachingMode(null));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b31d83f/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/EntityFrameworkCacheInitializationTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/EntityFrameworkCacheInitializationTest.cs b/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/EntityFrameworkCacheInitializationTest.cs
new file mode 100644
index 0000000..36b1c2b
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.EntityFramework.Tests/EntityFrameworkCacheInitializationTest.cs
@@ -0,0 +1,137 @@
+\ufeff/*
+ * 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.Tests
+{
+    using System;
+    using Apache.Ignite.Core;
+    using Apache.Ignite.Core.Cache.Configuration;
+    using Apache.Ignite.Core.Common;
+    using Apache.Ignite.Core.Tests;
+    using Apache.Ignite.EntityFramework;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Tests the EF cache provider.
+    /// </summary>
+    public class EntityFrameworkCacheInitializationTest
+    {
+        /// <summary>
+        /// Fixture tear down.
+        /// </summary>
+        [TestFixtureTearDown]
+        public void TestFixtureTearDown()
+        {
+            Ignition.StopAll(true);
+        }
+
+        /// <summary>
+        /// Tests the IgniteDbConfiguration.
+        /// </summary>
+        [Test]
+        public void TestConfigurationAndStartup()
+        {
+            Environment.SetEnvironmentVariable("IGNITE_NATIVE_TEST_CLASSPATH", "true");
+
+            Assert.IsNull(Ignition.TryGetIgnite());
+
+            // Test default config (picks up app.config section).
+            CheckCacheAndStop("myGrid1", IgniteDbConfiguration.DefaultCacheNamePrefix, new IgniteDbConfiguration());
+
+            // Specific config section.
+            CheckCacheAndStop("myGrid2", "cacheName2",
+                new IgniteDbConfiguration("igniteConfiguration2", "cacheName2", null));
+
+            // Specific config section, nonexistent cache.
+            CheckCacheAndStop("myGrid2", "newCache",
+                new IgniteDbConfiguration("igniteConfiguration2", "newCache", null));
+
+            // In-code configuration.
+            CheckCacheAndStop("myGrid3", "myCache",
+                new IgniteDbConfiguration(new IgniteConfiguration
+                    {
+                        GridName = "myGrid3",
+                    }, new CacheConfiguration("myCache_metadata")
+                    {
+                        CacheMode = CacheMode.Replicated,
+                        AtomicityMode = CacheAtomicityMode.Transactional
+                    },
+                    new CacheConfiguration("myCache_data") {CacheMode = CacheMode.Replicated}, null),
+                CacheMode.Replicated);
+
+            // Existing instance.
+            var ignite = Ignition.Start(TestUtils.GetTestConfiguration());
+            CheckCacheAndStop(null, "123", new IgniteDbConfiguration(ignite,
+                new CacheConfiguration("123_metadata")
+                {
+                    Backups = 1,
+                    AtomicityMode = CacheAtomicityMode.Transactional
+                },
+                new CacheConfiguration("123_data"), null));
+
+            // Non-tx meta cache.
+            var ex = Assert.Throws<IgniteException>(() => CheckCacheAndStop(null, "123",
+                new IgniteDbConfiguration(TestUtils.GetTestConfiguration(), 
+                    new CacheConfiguration("123_metadata"),
+                    new CacheConfiguration("123_data"), null)));
+
+            Assert.AreEqual("EntityFramework meta cache should be Transactional.", ex.Message);
+
+            // Same cache names.
+            var ex2 = Assert.Throws<ArgumentException>(() => CheckCacheAndStop(null, "abc",
+                new IgniteDbConfiguration(TestUtils.GetTestConfiguration(),
+                    new CacheConfiguration("abc"),
+                    new CacheConfiguration("abc"), null)));
+
+            Assert.IsTrue(ex2.Message.Contains("Meta and Data cache can't have the same name."));
+        }
+
+        /// <summary>
+        /// Checks that specified cache exists and stops all Ignite instances.
+        /// </summary>
+        // ReSharper disable once UnusedParameter.Local
+        private static void CheckCacheAndStop(string gridName, string cacheName, IgniteDbConfiguration cfg,
+            CacheMode cacheMode = CacheMode.Partitioned)
+        {
+            try
+            {
+                Assert.IsNotNull(cfg);
+
+                var ignite = Ignition.TryGetIgnite(gridName);
+                Assert.IsNotNull(ignite);
+
+                var metaCache = ignite.GetCache<object, object>(cacheName + "_metadata");
+                Assert.IsNotNull(metaCache);
+                Assert.AreEqual(cacheMode, metaCache.GetConfiguration().CacheMode);
+
+                if (cacheMode == CacheMode.Partitioned)
+                    Assert.AreEqual(1, metaCache.GetConfiguration().Backups);
+
+                var dataCache = ignite.GetCache<object, object>(cacheName + "_data");
+                Assert.IsNotNull(dataCache);
+                Assert.AreEqual(cacheMode, dataCache.GetConfiguration().CacheMode);
+
+                if (cacheMode == CacheMode.Partitioned)
+                    Assert.AreEqual(0, dataCache.GetConfiguration().Backups);
+            }
+            finally
+            {
+                Ignition.StopAll(true);
+            }
+        }
+    }
+}