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 @@ +/* + * 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 @@ +/* +* 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 @@ +<?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 @@ +<?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 @@ +<?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 @@ +/* + * 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 @@ +/* + * 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 @@ +/* + * 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 @@ +/* + * 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 @@ +/* + * 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 @@ +/* + * 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 @@ +/* + * 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 @@ +/* + * 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; + } + } + } +}
