You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by vo...@apache.org on 2016/11/17 10:51:57 UTC
[34/50] [abbrv] ignite git commit: IGNITE-1915 .NET: Ignite as Entity
Framework Second-Level Cache
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;
+ }
+ }
+ }
+}