blob: 773e59fbf5fa6894b79d11eaf989bc22feb46d90 [file] [log] [blame]
/*
* 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;
using System.Configuration;
using System.Data.Entity;
using System.Data.Entity.Core.Common;
using System.Data.Entity.Infrastructure.DependencyResolution;
using System.Data.Entity.Infrastructure.Interception;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
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>
/// DbConfiguration.AddInterceptor method.
/// </summary>
private static readonly MethodInfo AddInterceptorMethodInfo =
typeof(DbConfiguration).GetMethod("AddInterceptor", BindingFlags.Instance | BindingFlags.NonPublic);
/// <summary>
/// DbConfiguration.AddInterceptor delegate.
/// </summary>
private static readonly Action<object, IDbInterceptor> AddInterceptorDelegate =
(dbConfig, interceptor) => AddInterceptorMethodInfo.Invoke(dbConfig, new object[] {interceptor});
/// <summary>
/// Initializes a new instance of the <see cref="IgniteDbConfiguration"/> class.
/// <para />
/// This constructor uses default Ignite instance (with null <see cref="IgniteConfiguration.IgniteInstanceName"/>)
/// 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");
InitializeIgniteCachingInternal(this, ignite, metaCacheConfiguration, dataCacheConfiguration, policy);
}
/// <summary>
/// Initializes Ignite caching for specified <see cref="DbConfiguration"/>.
/// This method should be used when it is not possible to use or inherit <see cref="IgniteDbConfiguration"/>.
/// </summary>
/// <param name="dbConfiguration"><see cref="DbConfiguration"/> instance to be initialized
/// for Ignite caching.</param>
/// <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>
public static void InitializeIgniteCaching(DbConfiguration dbConfiguration, IIgnite ignite,
CacheConfiguration metaCacheConfiguration, CacheConfiguration dataCacheConfiguration,
IDbCachingPolicy policy)
{
IgniteArgumentCheck.NotNull(ignite, "ignite");
IgniteArgumentCheck.NotNull(dbConfiguration, "configuration");
IgniteArgumentCheck.Ensure(!(dbConfiguration is IgniteDbConfiguration), "dbConfiguration",
"IgniteDbConfiguration.InitializeIgniteCaching should not be called for IgniteDbConfiguration " +
"instance. This method should be used only when IgniteDbConfiguration can't be inherited.");
InitializeIgniteCachingInternal(dbConfiguration, ignite, metaCacheConfiguration, dataCacheConfiguration,
policy);
}
/// <summary>
/// Initializes Ignite caching for specified <see cref="DbConfiguration"/>.
/// This method should be used when it is not possible to use or inherit <see cref="IgniteDbConfiguration"/>.
/// </summary>
/// <param name="dbConfiguration"><see cref="DbConfiguration"/> instance to be initialized
/// for Ignite caching.</param>
/// <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>
private static void InitializeIgniteCachingInternal(DbConfiguration dbConfiguration, IIgnite ignite,
CacheConfiguration metaCacheConfiguration, CacheConfiguration dataCacheConfiguration,
IDbCachingPolicy policy)
{
Debug.Assert(ignite != null);
Debug.Assert(dbConfiguration != null);
metaCacheConfiguration = metaCacheConfiguration ?? GetDefaultMetaCacheConfiguration();
dataCacheConfiguration = dataCacheConfiguration ?? GetDefaultDataCacheConfiguration();
var efCache = new DbCache(ignite, metaCacheConfiguration, dataCacheConfiguration);
var txHandler = new DbTransactionInterceptor(efCache);
AddInterceptorDelegate(dbConfiguration, txHandler);
RegisterProviderServicesReplacer(dbConfiguration, policy, efCache, txHandler);
}
/// <summary>
/// Registers the provider services replacer.
/// </summary>
private static void RegisterProviderServicesReplacer(DbConfiguration config,
IDbCachingPolicy policy, DbCache efCache, DbTransactionInterceptor txHandler)
{
EventHandler<DbConfigurationLoadedEventArgs> onLoaded = null;
onLoaded = (sender, args) =>
{
// Replace provider services for specific instance only and unsubscribe.
if (ReferenceEquals(config, sender))
{
// SetProviderServices is not suitable. We should replace whatever provider there is with our proxy.
args.ReplaceService<DbProviderServices>(
(services, a) => new DbProviderServicesProxy(services, policy, efCache, txHandler));
Loaded -= onLoaded;
}
};
Loaded += onLoaded;
}
/// <summary>
/// Gets the Ignite instance.
/// </summary>
private static IIgnite GetOrStartIgnite(IgniteConfiguration cfg)
{
cfg = cfg ?? new IgniteConfiguration();
return Ignition.TryGetIgnite(cfg.IgniteInstanceName) ?? 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
};
}
}
}