This is an automated email from the ASF dual-hosted git repository. dmagda pushed a commit to branch IGNITE-7595 in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/IGNITE-7595 by this push: new 0dd5605 ported all .NET-specific pages from readme.io to the new documentation engine 0dd5605 is described below commit 0dd5605de364e068decf3a75c7277c5bdf69f9ad Author: Denis Magda <dma...@gridgain.com> AuthorDate: Tue Sep 29 15:54:29 2020 -0700 ported all .NET-specific pages from readme.io to the new documentation engine --- docs/_data/toc.yaml | 16 +- docs/_docs/SQL/indexes.adoc | 8 +- docs/_docs/images/net-view-details.png | Bin 0 -> 56828 bytes .../_docs/net-specific/asp-net-output-caching.adoc | 79 ++++++ .../asp-net-session-state-caching.adoc | 67 +++++ .../_docs/net-specific/cross-platform-support.adoc | 51 ++++ .../_docs/net-specific/entity-framework-cache.adoc | 184 +++++++++++++ .../net-specific/java-services-execution.adoc | 102 +++++++ docs/_docs/net-specific/linq.adoc | 242 +++++++++++++++++ docs/_docs/net-specific/net-logging.adoc | 119 ++++++++ docs/_docs/net-specific/net-plugins.adoc | 155 +++++++++++ docs/_docs/net-specific/net-serialization.adoc | 300 +++++++++++++++++++++ docs/_docs/net-specific/net-troubleshooting.adoc | 124 +++++++++ .../net-specific/platform-interoperability.adoc | 181 +++++++++++++ .../net-specific/remote-assembly-loading.adoc | 140 ++++++++++ 15 files changed, 1759 insertions(+), 9 deletions(-) diff --git a/docs/_data/toc.yaml b/docs/_data/toc.yaml index 8db5154..c68e4e8 100644 --- a/docs/_data/toc.yaml +++ b/docs/_data/toc.yaml @@ -430,15 +430,15 @@ - title: Standalone Nodes url: net-specific/standalone-nodes - title: Logging - url: net-specific/logging + url: net-specific/net-logging - title: LINQ url: net-specific/linq - title: Java Services Execution url: net-specific/java-services-execution - title: Plugins - url: net-specific/plugins + url: net-specific/net-plugins - title: Serialization - url: net-specific/serialization + url: net-specific/net-serialization - title: Cross-Platform Support url: net-specific/cross-platform-support - title: Platform Interoperability @@ -446,9 +446,15 @@ - title: Remote Assembly Loading url: net-specific/remote-assembly-loading - title: Troubleshooting - url: net-specific/troubleshooting + url: net-specific/net-troubleshooting - title: Integrations - url: net-specific/integrations + items: + - title: ASP.NET Output Caching + url: net-specific/asp-net-output-caching + - title: ASP.NET Session State Caching + url: net-specific/asp-net-session-state-caching + - title: Entity Framework 2nd Level Cache + url: net-specific/entity-framework-cache - title: Plugins url: plugins - title: SQL Reference diff --git a/docs/_docs/SQL/indexes.adoc b/docs/_docs/SQL/indexes.adoc index 1121d8b..d519bb7 100644 --- a/docs/_docs/SQL/indexes.adoc +++ b/docs/_docs/SQL/indexes.adoc @@ -14,6 +14,10 @@ Ignite automatically creates indexes for each primary key and affinity key field When you define an index on a field in the value object, Ignite creates a composite index consisting of the indexed field and the cache's primary key. In SQL terms, it means that the index will be composed of two columns: the column you want to index and the primary key column. +== Creating Indexes With SQL + +Refer to the link:sql-reference/ddl#create-index[CREATE INDEX] section. + == Configuring Indexes Using Annotations Indexes, as well as queryable fields, can be configured from code via the `@QuerySqlField` annotation. In the example below, the Ignite SQL engine will create indexes for the `id` and `salary` fields. @@ -218,10 +222,6 @@ include::{javaFile}[tag=query,indent=0] Use the link:sql-reference/ddl#create-index[CREATE/DROP INDEX] command if you need to manage indexes or make new fields of the object visible to the SQL engine at​ runtime. ==== -== Creating Indexes Using SQL Command - -Refer to the link:sql-reference/ddl#create-index[CREATE INDEX] section. - == Configuring Index Inline Size Proper index inline size can help speed up queries on indexed fields. diff --git a/docs/_docs/images/net-view-details.png b/docs/_docs/images/net-view-details.png new file mode 100644 index 0000000..b74c945 Binary files /dev/null and b/docs/_docs/images/net-view-details.png differ diff --git a/docs/_docs/net-specific/asp-net-output-caching.adoc b/docs/_docs/net-specific/asp-net-output-caching.adoc new file mode 100644 index 0000000..8cc1ce5 --- /dev/null +++ b/docs/_docs/net-specific/asp-net-output-caching.adoc @@ -0,0 +1,79 @@ += ASP.NET Output Caching + +== Overview + +Ignite cache can be used as an ASP.NET output cache. This can work especially well for web farms where cached output will +be shared between web servers. + +== Installation + +* *Binary distribution*: add a reference to `Apache.Ignite.AspNet.dll` +* *NuGet*: `Install-Package Apache.Ignite.AspNet` + +== Launching Ignite Automatically + +To start Ignite automatically for output caching, configure it +link:net-specific/configuration-options#configure-with-application-or-web-config-files[in the web.config file via IgniteConfigurationSection] + +[tabs] +-- +tab:web.config[] +[source,xml] +---- +<configuration> + <configSections> + <section name="igniteConfiguration" type="Apache.Ignite.Core.IgniteConfigurationSection, Apache.Ignite.Core" /> + </configSections> + + <igniteConfiguration autoGenerateIgniteInstanceName="true"> + <cacheConfiguration> + <cacheConfiguration name='myWebCache' /> + </cacheConfiguration> + </igniteConfiguration> +</configuration> +---- +-- + +Enable the caching in the `web.config` settings: + +[tabs] +-- +tab:web.config[] +[source,xml] +---- +<system.web> + <caching> + <outputCache defaultProvider="apacheIgnite"> + <providers> + <add name="apacheIgnite" type="Apache.Ignite.AspNet.IgniteOutputCacheProvider, Apache.Ignite.AspNet" igniteConfigurationSectionName="igniteConfiguration" cacheName="myWebCache" /> + </providers> + </outputCache> + </caching> +</system.web> +---- +-- + +== Launching Ignite Manually + +You can start an Ignite instance manually and specify it's name in the provider configuration: + +[tabs] +-- +tab:web.config[] +[source,xml] +---- +<system.web> + <caching> + <outputCache defaultProvider="apacheIgnite"> + <providers> + <add name="apacheIgnite" type="Apache.Ignite.AspNet.IgniteOutputCacheProvider, Apache.Ignite.AspNet" cacheName="myWebCache" /> + </providers> + </outputCache> + </caching> +</system.web> +---- +-- + +The Ignite instance needs to be started before any request is served. Typically this is done in the `Application_Start` method of the `global.asax`. + +See link:net-specific/deployment-options#asp-net-deployment[ASP.NET Deployment] for web deployment specifics related to the `IGNITE_HOME` variable. diff --git a/docs/_docs/net-specific/asp-net-session-state-caching.adoc b/docs/_docs/net-specific/asp-net-session-state-caching.adoc new file mode 100644 index 0000000..b60a5da --- /dev/null +++ b/docs/_docs/net-specific/asp-net-session-state-caching.adoc @@ -0,0 +1,67 @@ += ASP.NET Session State Caching + +== Overview + +The ASP.NET session state caching is designed to allow you to store user session data in different sources. +By default, session state values and information are stored in memory within the ASP.NET process. + +Ignite.NET implements a session state store provider that stores session data in a distributed Ignite cluster that spreads +the session data across multiple servers in order to provide high availability, load balancing and fault tolerance. + +[CAUTION] +==== +[discrete] +=== Development and Debugging +During development and debugging, IIS will dynamically detect code changes when you build and run your web application. +This, however, does not restart the embedded Ignite instance and can cause exceptions and undesired behavior. +Make sure to restart IIS manually when using the Ignite Session State Cache. +==== + +== Installation + +* *Binary distribution*: add a reference to Apache.Ignite.AspNet.dll +* *NuGet*: `Install-Package Apache.Ignite.AspNet` + +== Configuration + +To enable the Ignite-based session state storage, modify the `web.config` file as follows: + +[tabs] +-- +tab:web.config[] +[source,xml] +---- +<system.web> + ... + <sessionState mode="Custom" customProvider="IgniteSessionStateProvider"> + <providers> + <add name="IgniteSessionStateProvider" + type="Apache.Ignite.AspNet.IgniteSessionStateStoreProvider, Apache.Ignite.AspNet" + igniteConfigurationSectionName="igniteConfiguration" + applicationId="myApp" + gridName="myGrid" + cacheName="aspNetSessionCache" /> + </providers> + </sessionState> + ... +</<system.web> +---- +-- + +While the `name` and `type` attributes are required, the other attributes listed below are optional: + +[cols="1,3",opts="header"] +|=== +|Attribute |Description +|`igniteConfigurationSectionName`| The `web.config` section name defined in `configSections`. See +link:http://127.0.0.1:4000/docs/net-specific/configuration-options#configure-with-application-or-web-config-files[Configuration: web.config] for +more details. This configuration will be used to start Ignite if it is not started yet. +|`applicationId`| Should only be used when multiple web applications share the same Ignite session state cache. Assign +different ID strings to avoid session data conflicts between applications. It is recommended to use a separate cache +for each application via `cacheName` attribute. +|`gridName`| Session state provider calls `Ignition.TryGetIgnite` with this grid name to check whether Ignite is already started. +|`cacheName`| Session state cache name. Default is `ASPNET_SESSION_STATE`. +|=== + +For more details on how to start Ignite within an ASP.NET application, refer to link:net-specific/asp-net-output-caching[ASP.NET Output Caching]. +Also, see link:net-specific/deployment-options#asp-net-deployment[ASP.NET Deployment] for web deployment specifics related to the `IGNITE_HOME` variable. diff --git a/docs/_docs/net-specific/cross-platform-support.adoc b/docs/_docs/net-specific/cross-platform-support.adoc new file mode 100644 index 0000000..49b895b --- /dev/null +++ b/docs/_docs/net-specific/cross-platform-support.adoc @@ -0,0 +1,51 @@ += Cross-Platform Support + +== Overview + +Starting with version 2.4, Ignite.NET supports .NET Core. It is possible to run .NET nodes and develop Ignite.NET +applications for Linux and macOS, as well as Windows. + +== .NET Core + +*Requirements:* + +* https://www.microsoft.com/net/download/[.NET Core SDK 2.0+, window=_blank] +* http://www.oracle.com/technetwork/java/javase/downloads/index.html[Java 8+, window=_blank] (macOS requires JDK, otherwise JRE works) + +*Running Examples* +https://ignite.apache.org/download.cgi#binaries[Binary distribution, window=_blank] includes .NET Core examples: + +* Download https://ignite.apache.org/download.cgi#binaries[binary distribution, window=_blank] from the Ignite website and extract into any directory. +* `cd platforms/dotnet/examples/dotnetcore` +* `dotnet run` + +== Java Detection + +Ignite.NET looks for a Java installation directory in the following places: + +* `HKLM\Software\JavaSoft\Java Runtime Environment` (Windows) +* `/usr/bin/java` (Linux) +* `/Library/Java/JavaVirtualMachines` (macOS) + +If you changed the default location of Java, then specify the actual path using one of the methods below: + +* Set the `IgniteConfiguration.JvmDllPath` property +* or set the `JAVA_HOME` environment variable + +== Known Issues + +*No Java runtime present, requesting install* + +Java `8u151` has a known bug on macOS: https://bugs.openjdk.java.net/browse/JDK-7131356[JDK-7131356, window=_blank]. Make sure to install `8u152` or later. + +*Serializing delegates is not supported on this platform* + +.NET Core does not support delegate serialization, `System.MulticastDelegate.GetObjectData` +just https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/MulticastDelegate.cs#L52[throws an exception, window=_blank], +so Ignite.NET can not serialize delegates or objects containing them. + +*Could not load file or assembly 'System.Configuration.ConfigurationManager'* + +Known https://github.com/dotnet/standard/issues/506[.NET issue (506), window=_blank], in some cases additional package reference is required: + +* `dotnet add package System.Configuration.ConfigurationManager` diff --git a/docs/_docs/net-specific/entity-framework-cache.adoc b/docs/_docs/net-specific/entity-framework-cache.adoc new file mode 100644 index 0000000..67fe3f6 --- /dev/null +++ b/docs/_docs/net-specific/entity-framework-cache.adoc @@ -0,0 +1,184 @@ += Entity Framework 2nd Level Cache + +== Overview + +Entity Framework, as most other ORMs, can use caching on multiple levels. + +* First level caching is performed by `DbContext` on the entity level (entities are cached within corresponding `DbSet`) +* Second level caching is on the level of `DataReader` and holds raw query data (however, there is no out-of-the-box 2nd +level caching mechanism in Entity Framework 6). + +Ignite.NET provides an EF6 second level caching solution that stores data in a distributed Ignite cache. This is ideal +for scenarios with multiple application servers using a single SQL database via Entity Framework - cached queries are +shared between all machines in the cluster. + +== Installation +* *Binary distribution*: add a reference to `Apache.Ignite.EntityFramework.dll` +* *NuGet*: `Install-Package Apache.Ignite.EntityFramework` + +== Configuration + +Ignite.NET provides a custom `DbConfiguration` implementation which enables second level caching - `Apache.Ignite.EntityFramework.IgniteDbConfiguration`. +There is a number of ways to apply `DbConfiguration` to the EntityFramework `DbContext`. See the following MSDN document +for details: https://msdn.microsoft.com/en-us/library/jj680699[msdn.microsoft.com/en-us/library/jj680699, window=_blank]. + +The simplest way to implement this is to use the `[DbConfigurationType]` attribute: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +[DbConfigurationType(typeof(IgniteDbConfiguration))] +class MyContext : DbContext +{ + public virtual DbSet<Foo> Foos { get; set; } + public virtual DbSet<Bar> Bars { get; set; } +} +---- +-- + +To customize caching behavior, create a class that inherits `IgniteDbConfiguration` and call one of the base constructors. +The example below shows the most custom base constructor: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +private class MyDbConfiguration : IgniteDbConfiguration +{ + public MyDbConfiguration() + : base( + // IIgnite instance to use + Ignition.Start(), + // Metadata cache configuration (small cache, does not tolerate data loss) + // Should be replicated or partitioned with backups + new CacheConfiguration("metaCache") + { + CacheMode = CacheMode.Replicated + }, + // Data cache configuration (large cache, holds actual query results, + // tolerates data loss). Can have no backups. + new CacheConfiguration("dataCache") + { + CacheMode = CacheMode.Partitioned, + Backups = 0 + }, + // Custom caching policy. + new MyCachingPolicy()) + { + // No-op. + } +} + +// Apply custom configuration to the DbContext +[DbConfigurationType(typeof(MyDbConfiguration))] +class MyContext : DbContext +{ + ... +} +---- +-- + +=== Caching Policy + +The caching policy feature controls a selected caching mode, expiration, and which entity sets should be cached. With the default +`null` policy, all entity sets are cached in the `ReadWrite` mode with no expiration. A caching policy can be configured +by implementing the `IDbCachingPolicy` interface or inheriting the `DbCachingPolicy` class. The example below shows a sample implementation: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +public class DbCachingPolicy : IDbCachingPolicy +{ + /// <summary> + /// Determines whether the specified query can be cached. + /// </summary> + public virtual bool CanBeCached(DbQueryInfo queryInfo) + { + // This method is called before database call. + // Cache only Persons. + return queryInfo.AffectedEntitySets.All(x => x.Name == "Person"); + } + + /// <summary> + /// Determines whether specified number of rows should be cached. + /// </summary> + public virtual bool CanBeCached(DbQueryInfo queryInfo, int rowCount) + { + // This method is called after database call. + // Cache only queries that return less than 1000 rows. + return rowCount < 1000; + } + + /// <summary> + /// Gets the absolute expiration timeout for a given query. + /// </summary> + public virtual TimeSpan GetExpirationTimeout(DbQueryInfo queryInfo) + { + // Cache for 5 minutes. + return TimeSpan.FromMinutes(5); + } + + /// <summary> + /// Gets the caching strategy for a given query. + /// </summary> + public virtual DbCachingMode GetCachingMode(DbQueryInfo queryInfo) + { + // Cache with invalidation. + return DbCachingMode.ReadWrite; + } +} +---- +-- + +=== Caching Modes + +[cols="1,3",opts="header"] +|=== +|DbCachingMode |Description +|`ReadOnly`| Read-only mode, never invalidates. 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). This mode is suitable for data +that is not expected to change (like a list of countries and other dictionary data). +|`ReadWrite`| Read-write mode. Cached data is invalidated when underlying entity set changes. This is "normal" cache mode +which always provides correct query results. Keep in mind that this mode works correctly only when all database changes +are performed via DbContext with Ignite caching configured. Other database updates are not tracked. +|=== + +== app.config & web.config + +Ignite caching can be enabled in the config files by providing an assembly-qualified type name of `IgniteDbConfiguration` (or your class that inherits it): + +[tabs] +-- +tab:app.config[] +[source,xml] +---- +<entityFramework codeConfigurationType="Apache.Ignite.EntityFramework.IgniteDbConfiguration, Apache.Ignite.EntityFramework"> + ...Your EF config... +</entityFramework> +---- +-- + +== Advanced Configuration + +When there is no possibility to inherit `IgniteDbConfiguration` (it already inherits some other class), you can call the +`IgniteDbConfiguration.InitializeIgniteCaching` static method from the constructor, passing `this` as the first argument: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +private class MyDbConfiguration : OtherDbConfiguration +{ + public MyDbConfiguration() : base(...) + { + IgniteDbConfiguration.InitializeIgniteCaching(this, Ignition.GetIgnite(), null, null, null); + } +} +---- +-- diff --git a/docs/_docs/net-specific/java-services-execution.adoc b/docs/_docs/net-specific/java-services-execution.adoc new file mode 100644 index 0000000..e0a8827 --- /dev/null +++ b/docs/_docs/net-specific/java-services-execution.adoc @@ -0,0 +1,102 @@ += Java Services Execution from Ignite.NT + +== Overview + +Ignite.NET can work with Java services the same way as with .NET services. To call a Java service from a .NET application, +you need to know the interface of the service. + +== Example + +Let's review how to use this capability by taking a usage example. + +=== Create Java Service + +[tabs] +-- +tab:Java[] +[source,java] +---- +public class MyJavaService implements Service { + // Service method to be called from .NET + public String testToUpper(String x) { + return x.toUpperCase(); + } + + // Service interface implementation + @Override public void cancel(ServiceContext context) { // No-op. } + @Override public void init(ServiceContext context) throws Exception { // No-op. } + @Override public void execute(ServiceContext context) throws Exception { // No-op. } +} +---- +-- + +This Java service can be deployed on any nodes (.NET, C{pp}, Java-only), so there are no restrictions on deployment options: + +[tabs] +-- +tab:Java[] +[source,java] +---- +ignite.services().deployClusterSingleton("myJavaSvc", new MyJavaService()); +---- +-- + +=== Call Java Service From .NET + +Create a version of the service interface for .NET: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +// Interface can have any name +interface IJavaService +{ + // Method must have the same name (case-sensitive) and same signature: + // argument types and order. + // Argument names and return type do not matter. + string testToUpper(string str); +} +---- +-- + +Get the service proxy and invoke the method: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +var config = new IgniteConfiguration +{ + // Make sure that Java service class is in classpath on all nodes, including .NET + JvmClasspath = @"c:\my-project\src\Java\target\classes\" +} + +var ignite = Ignition.Start(config); + +// Make sure to use the same service name as in deployment +var prx = ignite.GetServices().GetServiceProxy<IJavaService>("myJavaSvc"); +string result = prx.testToUpper("invoking Java service..."); +Console.WriteLine(result); +---- +-- + +== Interface Methods Mapping + +The .NET service interface is mapped to its Java counterpart dynamically. This happens at the time of the method invocation: + +* It is not necessary to specify all Java service methods in the .NET interface. +* The .NET interface can have members that are not present in Java service. You won't get any exception until you call these missing methods. + +The Java methods are resolved the following way: + +* Ignite looks for a method with the specified name and parameter count. If there is only one exist, Ignite will use it. +* Among the matched methods Ignite looks for a method with compatible arguments (via `Class.isAssignableFrom`). +Ignite invoke the matched method or throws an exception in case of ambiguity. +* The method return type is ignored, since .NET and Java do not allow identical methods with different return types. + +See link:net-specific/platform-interoperability[Platform Interoperability, Type Compatibility section] for details on +method arguments and result mapping. Note, that the `params/varargs` are also supported, since in .NET and Java these are +syntactic sugar for object arrays. diff --git a/docs/_docs/net-specific/linq.adoc b/docs/_docs/net-specific/linq.adoc new file mode 100644 index 0000000..215fc95 --- /dev/null +++ b/docs/_docs/net-specific/linq.adoc @@ -0,0 +1,242 @@ += Apache Ignite.NET LINQ Provider + +== Overview + +Apache Ignite.NET includes a LINQ provider that is integrated with Ignite SQL APIs. You can avoid dealing with SQL +syntax directly and write queries in C# with LINQ. The Ignite LINQ provider supports all features of ANSI-99 SQL including +distributed joins, groupings, aggregates, sorting, and more. + +== Installation + +* If you use the Ignite *binary distribution*: add a reference to `Apache.Ignite.Linq.dll` +* If you use *NuGet*: `Install-Package Apache.Ignite.Linq` + +== Configuration + +SQL indexes need to be configured the same way as for regular SQL queries, see link:SQL/indexes[Defining Indexes section] +for details. + +== Usage + +`Apache.Ignite.Linq.CacheLinqExtensions` class is an entry point for the LINQ provider. +Obtain a queryable instance over an Ignite cache by calling the `AsCacheQueryable` method, and use LINQ on it: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +ICache<EmployeeKey, Employee> employeeCache = ignite.GetCache<EmployeeKey, Employee>(CacheName); + +IQueryable<ICacheEntry<EmployeeKey, Employee>> queryable = cache.AsCacheQueryable(); + +Employee[] interns = queryable.Where(emp => emp.Value.IsIntern).ToArray(); +---- +-- + +[CAUTION] +==== +[discrete] +You can use LINQ directly on the cache instance, without calling `AsCacheQueryable()`. However, this will result in LINQ +to Objects query that fetches and processes entire cache data set locally, which is very inefficient. +==== + +== Introspection + +The Ignite LINQ provider uses `ICache.QueryFields` underneath. You can examine produced `SqlFieldsQuery` by casting +`IQueryable` to `ICacheQueryable` at any point before materializing statements (`ToList`, `ToArray`, etc): + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +// Create query +var query = ignite.GetCache<EmployeeKey, Employee>(CacheName).AsCacheQueryable().Where(emp => emp.Value.IsIntern); + +// Cast to ICacheQueryable +var cacheQueryable = (ICacheQueryable) query; + +// Get resulting fields query +SqlFieldsQuery fieldsQuery = cacheQueryable.GetFieldsQuery(); + +// Examine generated SQL +Console.WriteLine(fieldsQuery.Sql); + +// Output: select _T0._key, _T0._val from "persons".Person as _T0 where _T0.IsIntern +---- +-- + +== Projections + +Simple `Where` queries operate on `ICacheEntry` objects. You can select Key, Value, or any of the Key and Value fields +separately. Multiple fields can be selected using anonymous types. + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +var query = ignite.GetCache<EmployeeKey, Employee>(CacheName).AsCacheQueryable().Where(emp => emp.Value.IsIntern); + +IQueryable<EmployeeKey> keys = query.Select(emp => emp.Key); + +IQueryable<Employee> values = query.Select(emp => emp.Value); + +IQueryable<string> names = values.Select(emp => emp.Name); + +var custom = query.Select(emp => new {Id = emp.Key, Name = emp.Value.Name, Age = emp.Value.Age}); +---- +-- + +== Compiled Queries + +The LINQ provider causes certain overhead caused by expression parsing and SQL generation. You may want to eliminate this +overhead for frequently used queries. + +The `Apache.Ignite.Linq.CompiledQuery` class supports queries compilation. Call the `Compile` method to create a new delegate +to represent the compiled query. All query parameters should be in the delegate parameters. + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +var queryable = ignite.GetCache<EmployeeKey, Employee>(CacheName).AsCacheQueryable(); + +// Regular query +var persons = queryable.Where(emp => emp.Value.Age > 21); +var result = persons.ToArray(); + +// Corresponding compiled query +var compiledQuery = CompiledQuery.Compile((int age) => queryable.Where(emp => emp.Value.Age > age)); +IQueryCursor<ICacheEntry<EmployeeKey, Employee>> cursor = compiledQuery(21); +result = cursor.ToArray(); +---- +-- + +Refer to the https://ptupitsyn.github.io/LINQ-vs-SQL-in-Ignite/[LINQ vs SQL blog post, window=_blank] for more details +on the LINQ provider performance. + +== Joins + +The LINQ provider support JOINs that span several caches/tables and nodes. + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +var persons = ignite.GetCache<int, Person>("personCache").AsCacheQueryable(); +var orgs = ignite.GetCache<int, Organization>("orgCache").AsCacheQueryable(); + +// SQL join on Person and Organization to find persons working for Apache +var qry = from person in persons from org in orgs + where person.Value.OrgId == org.Value.Id + && org.Value.Name == "Apache" + select person + +foreach (var cacheEntry in qry) + Console.WriteLine(cacheEntry.Value); + +// Same query with method syntax +qry = persons.Join(orgs, person => person.Value.OrgId, org => org.Value.Id, +(person, org) => new {person, org}).Where(p => p.org.Name == "Apache").Select(p => p.person); +---- +-- + +== Contains + +`ICollection.Contains` is supported, which is useful when we want to retrieve data by a set of ids, for example: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +var persons = ignite.GetCache<int, Person>("personCache").AsCacheQueryable(); +var ids = new int[] {1, 20, 56}; + +var personsByIds = persons.Where(p => ids.Contains(p.Value.Id)); +---- +-- + +This query translates into the `... where Id IN (?, ?, ?)` command. However, keep in mind, that this form cannot be used +in compiled queries because of variable argument number. Better alternative is to use `Join` on the `ids` collection: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +var persons = ignite.GetCache<int, Person>("personCache").AsCacheQueryable(); +var ids = new int[] {1, 20, 56}; + +var personsByIds = persons.Join(ids, + person => person.Value.Id, + id => id, + (person, id) => person); +---- +-- + +This LINQ query translates to a temp table join: +`select _T0._KEY, _T0._VAL from "person".Person as _T0 inner join table (F0 int = ?) _T1 on (_T1.F0 = _T0.ID)`, +and has a single array parameter, so the plan can be cached properly, and compiled queries are also allowed. + +== Supported SQL Functions + +Below is a list of .NET functions and their SQL equivalents that are supported by the Ignite LINQ provider. + +[width="100%",cols="1,3",opts="header"] +|=== +|`String.Length`| `LENGTH` +|`String.ToLower`| `LOWER` +|`String.ToUpper`| `UPPER` +|`String.StartsWith("foo")`| `LIKE 'foo%'` +|`String.EndsWith("foo")`| `LIKE '%foo'` +|`String.Contains("foo")`| `LIKE '%foo%'` +|`String.IndexOf("abc")`| `INSTR(MyField, 'abc') - 1` +|`String.IndexOf("abc", 3)`| `INSTR(MyField, 'abc', 3) - 1` +|`String.Substring("abc", 4)`| `SUBSTRING(MyField, 4 + 1)` +|`String.Substring("abc", 4, 7)`| `SUBSTRING(MyField, 4 + 1, 7)` +|`String.Trim()`| `TRIM` +|`String.TrimStart()`| `LTRIM` +|`String.TrimEnd()`| `RTRIM` +|`String.Trim('x')`| `TRIM(MyField, 'x')` +|`String.TrimStart('x')`| `LTRIM(MyField, 'x')` +|`String.TrimEnd('x')`| `RTRIM(MyField, 'x')` +|`String.Replace`| `REPLACE` +|`String.PadLeft`| `LPAD` +|`String.PadRight`| `RPAD` +|`Regex.Replace`| `REGEXP_REPLACE` +|`Regex.IsMatch`| `REGEXP_LIKE` +|`Math.Abs`| `ABS` +|`Math.Acos`| `ACOS` +|`Math.Asin`| `ASIN` +|`Math.Atan`| `ATAN` +|`Math.Atan2`| `ATAN2` +|`Math.Ceiling`| `CEILING` +|`Math.Cos`| `COS` +|`Math.Cosh`| `COSH` +|`Math.Exp`| `EXP` +|`Math.Floor`| `FLOOR` +|`Math.Log`| `LOG` +|`Math.Log10`| `LOG10` +|`Math.Pow`| `POWER` +|`Math.Round`| `ROUND` +|`Math.Sign`| `SIGN` +|`Math.Sin`| `SIN` +|`Math.Sinh`| `SINH` +|`Math.Sqrt`| `SQRT` +|`Math.Tan`| `TAN` +|`Math.Tanh`| `TANH` +|`Math.Truncate`| `TRUNCATE` +|`DateTime.Year`| `YEAR` +|`DateTime.Month`| `MONTH` +|`DateTime.Day`| `DAY_OF_MONTH` +|`DateTime.DayOfYear`| `DAY_OF_YEAR` +|`DateTime.DayOfWeek`| `DAY_OF_WEEK - 1` +|`DateTime.Hour`| `HOUR` +|`DateTime.Minute`| `MINUTE` +|`DateTime.Second`| `SECOND` +|=== diff --git a/docs/_docs/net-specific/net-logging.adoc b/docs/_docs/net-specific/net-logging.adoc new file mode 100644 index 0000000..7678fe1 --- /dev/null +++ b/docs/_docs/net-specific/net-logging.adoc @@ -0,0 +1,119 @@ += Ignite.NET Logging + +== Overview +By default, Ignite uses underlying the Java log4j logging system. Log messages from both .NET and Java are recorded there. +You can also write to this log via an `IIgnite.Logger` instance: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +var ignite = Ignition.Start(); +ignite.Logger.Info("Hello World!"); +---- +-- + +`LoggerExtensions` class provides convenient shortcuts for `ILogger.Log` method. + +== Custom Logger + +You can provide a logger implementation via the `IgniteConfiguration.Logger` and `ILogger` interface. +Messages from both .NET and Java will be redirected there. + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +var cfg = new IgniteConfiguration +{ + Logger = new MemoryLogger() +} + +var ignite = Ignition.Start(); + +class MemoryLogger : ILogger +{ + // Logger can be called from multiple threads, use concurrent collection + private readonly ConcurrentBag<string> _messages = new ConcurrentBag<string>(); + + public void Log(LogLevel level, string message, object[] args, + IFormatProvider formatProvider, string category, + string nativeErrorInfo, Exception ex) + { + _messages.Add(message); + } + + public bool IsEnabled(LogLevel level) + { + // Accept any level. + return true; + } +} +---- +tab:app.config[] +[source,xml] +---- +<igniteConfiguration> +<logger type="MyNamespace.MemoryLogger, MyAssembly" /> +</igniteConfiguration> +---- +-- + +== NLog & log4net Loggers + +Ignite.NET provides `ILogger` implementations for http://nlog-project.org/[NLog, window=_blank] and https://logging.apache.org/log4net/[Apache log4net, window=_blank]. +They are included in the binary package (`Apache.Ignite.NLog.dll` and `Apache.Ignite.Log4Net.dll`) and can be installed via NuGet: + +* `Install-Package Apache.Ignite.NLog` +* `Install-Package Apache.Ignite.Log4Net` + +NLog and Log4Net use statically defined configuration, so there is nothing to configure in Ignite besides `IgniteConfiguration.Logger`: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +var cfg = new IgniteConfiguration +{ + Logger = new IgniteNLogLogger() // or IgniteLog4NetLogger +} + +var ignite = Ignition.Start(); +---- +tab:app.config[] +[source,xml] +---- +<igniteConfiguration> + <logger type="Apache.Ignite.NLog.IgniteNLogLogger, Apache.Ignite.NLog" /> +</igniteConfiguration> +---- +-- + +A simple file-based logging with NLog can be set up like this: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +var nlogConfig = new LoggingConfiguration(); + +var fileTarget = new FileTarget +{ + FileName = "ignite_nlog.log" +}; +nlogConfig.AddTarget("logfile", fileTarget); + +nlogConfig.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, fileTarget)); +LogManager.Configuration = nlogConfig; + +var igniteConfig = new IgniteConfiguration +{ + Logger = new IgniteNLogLogger() +}; +Ignition.Start(igniteConfig); +---- +-- diff --git a/docs/_docs/net-specific/net-plugins.adoc b/docs/_docs/net-specific/net-plugins.adoc new file mode 100644 index 0000000..d753a9f --- /dev/null +++ b/docs/_docs/net-specific/net-plugins.adoc @@ -0,0 +1,155 @@ += Extending Ignite.NET With Custom Plugins + +== Overview + +The Ignite.NET plugin system allows you to extend the core Ignite.NET functionality with custom plugins. The best way to +explain how Ignite plugins work is by looking at the life cycle of plugins. + +== IgniteConfiguration.PluginConfigurations + +First, an Apache Ignite plugin has to be registered via the `IgniteConfiguration.PluginConfigurations` property which is +a collection of the `IPluginConfiguration` implementations. From a user's perspective, this is a manual process - a +plugin's assembly has to be referenced and configured explicitly. + +The `IPluginConfiguration` interface has two members that interact with the Java part of Apache Ignite.NET. This is +described in the next section. Besides those two members, `IPluginConfiguration` implementation should contain all the +other plugin-specific configuration properties. + +Another part of an `IPluginConfiguration` implementation is the mandatory `[PluginProviderType]` attribute that tethers a +plugin configuration with a plugin implementation. For example: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +[PluginProviderType(typeof(MyPluginProvider))] + public class MyPluginConfiguration : IPluginConfiguration + { + public string MyProperty { get; set; } // Plugin-specific property + + public int? PluginConfigurationClosureFactoryId + { + get { return null; } // No Java part + } + + public void WriteBinary(IBinaryRawWriter writer) + { + // No-op. + } + } +---- +-- + +To recap, this is how plugins are added and initialized: + +* You add the `IPluginConfiguration` implementation instance to `IgniteConfiguration`. +* You start an Ignite node with the prepared configuration. +* Before the Ignite node initialization is finished, the Ignite plugin engine examines the `IPluginConfiguration` implementation +for the `[PluginProviderType]` attribute and instantiates the specified class. + +== IPluginProvider + +The `IPluginProvider` implementation is the work-horse of the newly added plugin. It deals with the Ignite node life cycle +by processing the calls to the `OnIgniteStart` and `OnIgniteStop` methods. In addition, it can provide an optional API +to be used by an end user via the `GetPlugin<T>()` method. + +The first method to be invoked on the `IPluginProvider` implementation by the Ignite.NET engine is +`Start(IPluginContext<TestIgnitePluginConfiguration> context)`. `IPluginContext` provides an access to an initial plugin +configuration and all means to interact with Ignite. + +When Ignite is being stopped, the `Stop` and `OnIgniteStop` methods are executed sequentially so that the plugin +implementation can accomplish all cleanup and shutdown-related​ tasks. + +== IIgnite.GetPlugin + +Plugins can expose user-facing API which is accessed via the `IIgnite.GetPlugin(string name)` method. The Ignite engine +will search for `IPluginProvider` with the passed name and call `GetPlugin` on it. + +== Interacting With Java + +The Ignite.NET plugin can interact with an Ignite Java plugin via the `PlatformTarget` & `IPlatformTarget` interface pair. + +=== Java-Specific Logic + +. Implement the `PlatformTarget` interface, which is a communication point with .NET: ++ +[tabs] +-- +tab:Java[] +[source,java] +---- +class MyPluginTarget implements PlatformTarget { + @Override public long processInLongOutLong(int type, long val) throws IgniteCheckedException { + if (type == 1) + return val + 1; + else + return val - 1; + } + ... // Other methods here. +} +---- +-- + +* Implement the `PlatformPluginExtension` interface: ++ +[tabs] +-- +tab:Java[] +[source,java] +---- +public class MyPluginExtension implements PlatformPluginExtension { + @Override public int id() { + return 42; // Unique id to be used from .NET side. + } + + @Override public PlatformTarget createTarget() { + return new MyPluginTarget(); // Return target from previous step. + } +} +---- +-- + +* Implement the `PluginProvider.initExtensions` method and register the `PlatformPluginExtension` class: ++ +[tabs] +-- +tab:Java[] +[source,java] +---- +@Override public void initExtensions(PluginContext ctx, ExtensionRegistry registry) { + registry.registerExtension(PlatformPluginExtension.class, new MyPluginExtension()); +} +---- +-- + +=== .NET-specific Logic + +Call `IPluginContext.GetExtension` with a corresponding id. This will invoke the `createTarget` call on the Java side: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +IPlatformTarget extension = pluginContext.GetExtension(42); + +long result = extension.InLongOutLong(1, 2); // processInLongOutLong is called in Java +---- +-- + +Other `IPlatformTarget` methods provide an efficient way to exchange any kind of data between Java and .NET code. + +=== Callbacks from Java + +.NET \-> Java call mechanism is described above; you can also do Java \-> .NET calls: + +* Register callback handler with some ID on the .NET side via the `IPluginContext.RegisterCallback` method. +* Call `PlatformCallbackGateway.pluginCallback` with that ID on the Java side. + +[NOTE] +==== +[discrete] +=== Complete Example +A detailed walk-through plugin example can be found in https://ptupitsyn.github.io/Ignite-Plugin/[this blog post, window=_blank]. +==== diff --git a/docs/_docs/net-specific/net-serialization.adoc b/docs/_docs/net-specific/net-serialization.adoc new file mode 100644 index 0000000..8176e69 --- /dev/null +++ b/docs/_docs/net-specific/net-serialization.adoc @@ -0,0 +1,300 @@ += Serialization in Ignite.NET + +Most of the user-defined classes going through the Ignite .NET API will be trasferred over the network to other cluster nodes. These classes include: + +* Cache keys and values +* Cache processors and filters (`ICacheEntryProcessor`, `ICacheEntryFilter`, `ICacheEntryEventFilter`, `ICacheEntryEventListener`) +* Compute functions (`IComputeFunc`), actions (`IComputeAction`) and jobs (`IComputeJob`) +* Services (`IService`) +* Event and Message handlers (`IEventListener`, `IEventFilter`, `IMessageListener`) + +Passing objects of these classes over the network requires serialization. Ignite .NET supports the following ways of serializing user data: + +* `Apache.Ignite.Core.Binary.IBinarizable` interface +* `Apache.Ignite.Core.Binary.IBinarySerializer` interface +* `System.Runtime.Serialization.ISerializable` interface +* Ignite reflective serialization (when none of the above applies) + +== IBinarizable + +`IBinarizable` approach provides a fine-grained control over serialization. This is a preferred way for high-performance production code. + +First, implement the `IBinarizable` interface in your class: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +public class Address : IBinarizable +{ + public string Street { get; set; } + + public int Zip { get; set; } + + public void WriteBinary(IBinaryWriter writer) + { + // Alphabetic field order is required for SQL DML to work. + // Even if DML is not used, alphabetic order is recommended. + writer.WriteString("street", Street); + writer.WriteInt("zip", Zip); + } + + public void ReadBinary(IBinaryReader reader) + { + // Read order does not matter, however, reading in the same order + // as writing improves performance. + Street = reader.ReadString("street"); + Zip = reader.ReadInt("zip"); + } +} +---- +-- + +`IBinarizable` can also be implemented in raw mode, without field names. This provides the fastest and the most compact +serialization, but disables SQL queries: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +public class Address : IBinarizable +{ + public string Street { get; set; } + + public int Zip { get; set; } + + public void WriteBinary(IBinaryWriter writer) + { + var rawWriter = writer.GetRawWriter(); + + rawWriter.WriteString(Street); + rawWriter.WriteInt(Zip); + } + + public void ReadBinary(IBinaryReader reader) + { + // Read order must be the same as write order + var rawReader = reader.GetRawReader(); + + Street = rawReader.ReadString(); + Zip = rawReader.ReadInt(); + } +} +---- +-- + +[NOTE] +==== +[discrete] +=== Automatic GetHashCode and Equals Implementation +If an object can be serialized into a binary form, then Ignite will calculate its hash code during serialization and +write it to the resulting binary array. Also, Ignite provides a custom implementation of the equals method for the +binary object's comparison needs. This means that you do not need to override the `GetHashCode` and `Equals` methods of +your custom keys and values in order for them to be used in Ignite. +==== + +== IBinarySerializer + +`IBinarySerializer` is similar to `IBinarizable`, but separates serialization logic from the class implementation. +This may be useful when the class code can not be modified, and serialization logic is shared between multiple classes, +etc. The following code has exactly the same serialization as in the `Address` example above: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +public class Address : IBinarizable +{ + public string Street { get; set; } + + public int Zip { get; set; } +} + +public class AddressSerializer : IBinarySerializer +{ + public void WriteBinary(object obj, IBinaryWriter writer) + { + var addr = (Address) obj; + + writer.WriteString("street", addr.Street); + writer.WriteInt("zip", addr.Zip); + } + + public void ReadBinary(object obj, IBinaryReader reader) + { + var addr = (Address) obj; + + addr.Street = reader.ReadString("street"); + addr.Zip = reader.ReadInt("zip"); + } +} +---- +-- + +The `Serializer` should be specified in the configuration like this: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +var cfg = new IgniteConfiguration +{ + BinaryConfiguration = new BinaryConfiguration + { + TypeConfigurations = new[] + { + new BinaryTypeConfiguration(typeof (Address)) + { + Serializer = new AddressSerializer() + } + } + } +}; + +using (var ignite = Ignition.Start(cfg)) +{ + ... +} +---- +-- + +== ISerializable + +Types that implement the `System.Runtime.Serialization.ISerializable` interface will be serialized accordingly +(by calling `GetObjectData` and serialization constructor). All system features are supported: `IObjectReference`, +`IDeserializationCallback`, `OnSerializingAttribute`, `OnSerializedAttribute`, `OnDeserializingAttribute`, `OnDeserializedAttribute`. + +The `GetObjectData` result is written into the Ignite binary format. The following three classes provide identical serialized representation: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +class Reflective +{ + public int Id { get; set; } + public string Name { get; set; } +} + +class Binarizable : IBinarizable +{ + public int Id { get; set; } + public string Name { get; set; } + + public void WriteBinary(IBinaryWriter writer) + { + writer.WriteInt("Id", Id); + writer.WriteString("Name", Name); + } + + public void ReadBinary(IBinaryReader reader) + { + Id = reader.ReadInt("Id"); + Name = reader.ReadString("Name"); + } +} + +class Serializable : ISerializable +{ + public int Id { get; set; } + public string Name { get; set; } + + public Serializable() {} + + protected Serializable(SerializationInfo info, StreamingContext context) + { + Id = info.GetInt32("Id"); + Name = info.GetString("Name"); + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("Id", Id); + info.AddValue("Name", Name); + } +} +---- +-- + +== Ignite Reflective Serialization + +Ignite reflective serialization is essentially the `IBinarizable` approach where the interface is implemented automatically +by reflecting over all fields and emitting write/read calls. + +There are no requirements for this mechanism, any class or struct can be serialized including all system types, delegates, +expression trees, or anonymous types. + +Use the `[NonSerialized]` attribute to filter out specific fields during serialization. + +The raw mode can be enabled by specifying `BinaryReflectiveSerializer` explicitly: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +var binaryConfiguration = new BinaryConfiguration +{ + TypeConfigurations = new[] + { + new BinaryTypeConfiguration(typeof(MyClass)) + { + Serializer = new BinaryReflectiveSerializer {RawMode = true} + } + } +}; +---- +tab:app.config[] +[source,xml] +---- +<igniteConfiguration> + <binaryConfiguration> + <typeConfigurations> + <binaryTypeConfiguration typeName='Apache.Ignite.ExamplesDll.Binary.Address'> + <serializer type='Apache.Ignite.Core.Binary.BinaryReflectiveSerializer, Apache.Ignite.Core' rawMode='true' /> + </binaryTypeConfiguration> + </typeConfigurations> + </binaryConfiguration> +</igniteConfiguration> +---- +-- + +Otherwise, `BinaryConfiguration` is not required. + +Performance is identical to manual the `IBinarizable` approach. Reflection is only used on startup to iterate over the +fields and emit efficient IL code. + +Types marked with `[Serializable]` attribute but without `ISerializable` interface are written with Ignite reflective serializer. + +== Using Entity Framework POCOs + +The Entity Framework POCOs can be used directly with Ignite. + +However, https://msdn.microsoft.com/en-us/data/jj592886.aspx[POCO proxies, window=_blank] cannot be directly serialized +or deserialized by Ignite, because the proxy type is a dynamic type. + +Make sure to disable proxy creation when using EF objects with Ignite: + +[tabs] +-- +tab:Entity Framework 6[] +[source,csharp] +---- +ctx.Configuration.ProxyCreationEnabled = false; +---- +tab:Entity Framework 5[] +[source,csharp] +---- +ctx.ContextOptions.ProxyCreationEnabled = false; +---- +-- + +== More Info + +See https://ptupitsyn.github.io/Ignite-Serialization-Performance/[Ignite Serialization Performance, window=_blank] blog +post for more details on serialization performance of various modes introduced on this page. diff --git a/docs/_docs/net-specific/net-troubleshooting.adoc b/docs/_docs/net-specific/net-troubleshooting.adoc new file mode 100644 index 0000000..0d48400 --- /dev/null +++ b/docs/_docs/net-specific/net-troubleshooting.adoc @@ -0,0 +1,124 @@ += Ignite.NET Troubleshooting + +== Overview + +This page covers several troubleshooting techniques and commonly-known issues you can come across while building and +using your Ignite.NET applications in production. + +== Troubleshooting With Console + +Ignite produces console output (stdout): information, metrics, warnings, error details. If your app does not open console, you may redirect the console output to a string or a file: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +var sw = new StringWriter(); +Console.SetOut(sw); + +// Examine output: +sw.ToString(); +---- +-- + +== Getting More Insights On Exceptions + +When you are getting an `IgniteException`, always make sure to examine the `InnerException` property that often contains +more details on the root cause of the issue. You can do that in Visual Studio debugger or by calling `ToString()` on the exception object: + +image::images/net-view-details.png[Visual Studio Debugger] + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +try { + IQueryCursor<List> cursor = cache.QueryFields(query); +} +catch (IgniteException e) { + // Printing out the whole exception meesage. + Console.WriteLine(e.ToString()); +} +---- +-- + +== Commonly-Known Issues + +The following section covers several issues you can come across while designing your Ignite.NET applications. + +=== Failed to load jvm.dll + +Make sure that Java Development Kit is installed, and the `JAVA_HOME` variable is set and points to a JDK installation directory. + +The `errorCode=193` code is `ERROR_BAD_EXE_FORMAT`, which is often caused by x64/x86 mismatch. Make sure that the installed +JDK and your application have the same x64/x86 platform target. Ignite detects proper JDK automatically when `JAVA_HOME` is not set, +so if you have x86 AND x64 JDK installed, it will work in any mode. + +The `126 ERROR_MOD_NOT_FOUND` code can occur due to missing dependencies: + +* JDK 8 requires https://www.microsoft.com/en-us/download/details.aspx?id=14632[Microsoft Visual C{pp} 2010 Redistributable Package, window=_blank] +* Later JDK versions require https://www.microsoft.com/en-us/download/details.aspx?id=48145[Microsoft Visual C{pp} 2015 Redistributable Package, window=_blank] or later + +=== Java class is not found + +Check your the `IGNITE_HOME` environment variable, `IgniteConfiguration.IgniteHome` and `IgniteConfiguration.JvmClasspath` properties. +Refer to link:net-specific/deployment-options[Deployment] section for more details. ASP.NET/IIS scenarios require additional steps. + +=== Freeze on Ignition.Start + +Examine console output. Most often this is caused by a topology join failure: + +* Ignite `DiscoverySpi` settings are incorrect +* `ClientMode` is true, but there are no servers nodes that form the cluster. + +=== Failed to start manager : GridManagerAdapter + +Examine console output. Most often this is caused by an invalid or incompatible configuration: + +* Some configuration property has an invalid value (out of range and the like). +* Some configuration property is incompatible with a value in other cluster nodes. In particular, `BinaryConfiguration` properties, +such as `CompactFooter`, `IdMapper`, and `NameMapper` should be the same on all nodes. + +The latter problem often arises when building a mixed cluster (Java + .NET nodes), because default configuration on these +platforms is different. .NET only supports `BinaryBasicIdMapper` and `BinaryBasicNameMapper`. Java configuration has to +be fixed the following way to enable .NET nodes connectivity: + +[tabs] +-- +tab:XML[] +[source,xml] +---- +<property name="binaryConfiguration"> + <bean class="org.apache.ignite.configuration.BinaryConfiguration"> + <property name="compactFooter" value="true"/> + <property name="idMapper"> + <bean class="org.apache.ignite.binary.BinaryBasicIdMapper"> + <constructor-arg value="true"/> + </bean> + </property> + <property name="nameMapper"> + <bean class="org.apache.ignite.binary.BinaryBasicNameMapper"> + <constructor-arg value="true"/> + </bean> + </property> + </bean> +</property> +---- +-- + +=== Could not load file or assembly 'MyAssembly' or one of its dependencies. The system cannot find the file specified. + +This exception can occur due to missing assemblies on remote nodes. +See link:net-specific/standalone-nodes#load-user-assemblies[Standalone Nodes: Loading User Assemblies] for details. + +=== Stack smashing detected: dotnet terminated + +This happens on Linux with .NET Core when `NullReferenceException` occurs in user code. The reason is that both .NET and +Java use `SIGSEGV` to handle certain exceptions, including `NullPointerException` and `NullReferenceException`, and when +JVM runs in the same process as .NET, it overrides that handler, breaking .NET exception handling +(see https://github.com/dotnet/coreclr/issues/25945[1, window=_blank], https://github.com/dotnet/coreclr/issues/25166[2, window=_blank]). + +The fix for this issue exists in .NET Core 3.0 (https://github.com/dotnet/coreclr/pull/25972[#25972, window=_blank]. +by setting the `COMPlus_EnableAlternateStackCheck` environment variable to `1`. diff --git a/docs/_docs/net-specific/platform-interoperability.adoc b/docs/_docs/net-specific/platform-interoperability.adoc new file mode 100644 index 0000000..3c2b4db --- /dev/null +++ b/docs/_docs/net-specific/platform-interoperability.adoc @@ -0,0 +1,181 @@ += Platform Interoperability + +Ignite allows different platforms, such as .NET, Java and C{pp}, to interoperate with each other. +Classes and objects defined and written to Ignite by one platform can be read and used by another platform. + +== Identifiers + +To achieve interoperability Ignite writes objects using the common binary format. This format encodes object type and +fields using integer identifiers. + +To transform an object's type and field names to an integer value, Ignite passes them through two stage: + +* Name transformation: full type name and field names are passed to `IBinaryNameMapper` interface and converted to some common form. +* ID transformation: resulting strings are passed to `IBinaryIdMapper` to produce either type ID or field ID. + +Mappers can be set either globally in `BinaryConfiguration` or for concrete type in `BinaryTypeConfiguration`. + +Java has the same interfaces `BinaryNameMapper` and `BinaryIdMapper`. They are set on `BinaryConfiguration` or `BinaryTypeConfiguration`. + +.NET and Java types must map to the same type ID and relevant fields must map to the same field ID. + +== Default Behavior + +The .NET part of Ignite.NET applies the following conversions by default: + +* Name transformation: the `System.Type.FullName` property for non-generics types; field or property name is unchanged. +* ID transformation: names are converted to lower case and then ID is calculated in the same way as in the `java.lang.String.hashCode()` method in Java. + +The Java part of Ignite.NET applies the following conversions by default: + +* Name transformation: the `Class.getName()` method to get class name; field name is unchanged. +* ID transformation: names are converted to lower case and then `java.lang.String.hashCode()` is used to calculate IDs. + +For example, the following two types will automatically map to each other, if they are outside namespaces (.NET) and packages (Java): + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +class Person +{ + public int Id { get; set; } + public string Name { get; set; } + public byte[] Data { get; set; } +} +---- +tab:Java[] +[source,java] +---- +class Person +{ + public int id; + public String name; + public byte[] data; +} +---- +-- + +However, the types are normally within some namespace or package. And naming conventions for packages and namespaces +differ in Java and .NET. It may be problematic to have .NET namespace be the same as Java package. + +Simple name mapper (which ignores namespace) can be used to avoid this problem. It should be configured both for .NET and Java: + +[tabs] +-- +tab:Java Spring XML[] +[source,xml] +---- +<bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration"> + ... + <property name="binaryConfiguration"> + <bean class="org.apache.ignite.configuration.BinaryConfiguration"> + <property name="nameMapper"> + <bean class="org.apache.ignite.binary.BinaryBasicNameMapper"> + <property name="simpleName" value="true"/> + </bean> + </property> + </bean> + </property> + ... +</bean> +---- +tab:C#[] +[source,csharp] +---- +var cfg = new IgniteConfiguration +{ + BinaryConfiguration = new BinaryConfiguration + { + NameMapper = new BinaryBasicNameMapper {IsSimpleName = true} + } +} +---- +tab:app.config[] +[source,xml] +---- +<igniteConfiguration> + <binaryConfiguration> + <nameMapper type="Apache.Ignite.Core.Binary.BinaryBasicNameMapper, Apache.Ignite.Core" isSimpleName="true" /> + </binaryConfiguration> +</igniteConfiguration> +---- +-- + +== Types Compatibility + +[width="100%",cols="1,3",opts="header"] +|=== +|`C#`| `Java` +|`bool`| `boolean` +|`byte (*), sbyte`| `byte` +|`short, ushort (*)`| `short` +|`int, uint (*)`| `int` +|`long, ulong (*)`| `long` +|`char`| `char` +|`float`| `float` +|`double`| `double` +|`decimal`| `java.math.BigDecimal (**)` +|`string`| `java.lang.String` +|`Guid`| `java.util.UUID` +|`DateTime`| `java.util.Date, java.sql.Timestamp` +|=== +`* byte, ushort, uint, ulong` do not have Java counterparts, and are mapped directly byte-by-byte (no range check). +For example, `byte` value of `200` in C# will result in signed `byte` value of `-56` in Java. + +`** Java BigDecimal` has arbitrary size and precision, while C# decimal is fixed to 16 bytes and 28-29 digit precision. Ignite.NET will throw `BinaryObjectException` if a `BigDecimal` value does not fit into `decimal` on deserialization. + +`Enum` - In Ignite, Java `writeEnum` can only write ordinal values, but in .NET you can assign any number to the `enumValue`. +So, note that any custom enum-to-primitive value bindings are not taken into account. + +[CAUTION] +==== +[discrete] +=== DateTime Serialization +DateTime can be Local and UTC; Java Timestamp can only be UTC. Because of that, Ignite.NET can serialize DateTime in +following ways: + +* .NET style (can work with non-UTC values, does not work in SQL) and as Timestamp (throws exception on non-UTC values, works properly in SQL). + +* Reflective serialization: mark field with `[QuerySqlField]` to enforce Timestamp serialization, or set `BinaryReflectiveSerializer.ForceTimestamp` +to true; this can be done on per-type basis, or globally like this: +`IgniteConfiguration.BinaryConfiguration = new BinaryConfiguration { Serializer = new BinaryReflectiveSerializer { ForceTimestamp = true } }` + +* `IBinarizable`: use IBinaryWriter.WriteTimestamp method. + +When it is not possible to modify class to mark fields with `[QuerySqlField]` or implement `IBinarizable`, use the `IBinarySerializer` approach. +See link:net-specific/net-serialization[Serialization page] for more details. +==== + +== Collection Compatibility + +Arrays of simple types (from the table above) and arrays of objects are interoperable in all cases. For all other collections +and arrays default behavior (with reflective serialization or `IBinaryWriter.WriteObject`) in Ignite.NET is to use `BinaryFormatter`, +and the result can not be read by Java code (this is done to properly support generics). To write collections in interoperable +format, implement 'IBinarizable' interface and use `IBinaryWriter.WriteCollection`, `IBinaryWriter.WriteDictionary`, +`IBinaryReader.ReadCollection`, `IBinaryReader.ReadDictionary`methods. + +== Mixed-Platform Clusters + +Ignite, Ignite.NET and Ignite.C{pp} nodes can join the same cluster + +All platforms are built on top of Java, so any node can execute Java computations. +However, .NET and C{pp} computations can be executed only by corresponding nodes. + +The following Ignite.NET functionality is not supported when there is at least one non-.NET node in the cluster: + +* Scan Queries with filter +* Continuous Queries with filter +* ICache.Invoke methods +* ICache.LoadCache with filter +* Services +* IMessaging.RemoteListen +* IEvents.RemoteQuery + +Blog post with detailed walk-through: https://ptupitsyn.github.io/Ignite-Multi-Platform-Cluster/[Multi-Platform Ignite Cluster: Java + .NET, window=_blank] + +== Compute in Mixed-Platform Clusters + +The `ICompute.ExecuteJavaTask` methods work without limitations in any cluster. Other `ICompute` methods will execute +closures only on .NET nodes. diff --git a/docs/_docs/net-specific/remote-assembly-loading.adoc b/docs/_docs/net-specific/remote-assembly-loading.adoc new file mode 100644 index 0000000..3a11b29 --- /dev/null +++ b/docs/_docs/net-specific/remote-assembly-loading.adoc @@ -0,0 +1,140 @@ += Remote Assembly Loading + +== Overview + +Many Ignite APIs involve remote code execution. For example, Ignite compute tasks are serialized, sent to remote nodes, and executed there. +However, by default, .NET assemblies (DLL files) with those tasks in, must be loaded on remote nodes in order to instantiate +and deserialize tasks' instances. + +Before version 2.1 you had to manually load assemblies (using `-assembly` swith with `Apache.Ignite.exe` or some other ways). +Starting Ignite 2.1 you can take advantage of the remote assembly loading feature, that can be enabled with the +`IgniteConfiguration.PeerAssemblyLoadingMode` flag. This configuration property needs to have the same value on all nodes +in the cluster. Another available mode is `CurrentAppDomain`. + +== CurrentAppDomain Mode + +`PeerAssemblyLoadingMode.CurrentAppDomain` enables automatic on-demand assembly requests to other nodes in cluster, +loading assemblies into https://msdn.microsoft.com/en-us/library/system.appdomain.aspx[AppDomain, window=_blank] where Ignite node runs. + +Consider the following code: + +[tabs] +-- +tab:C#[] +[source,csharp] +---- +// Print Hello World on all cluster nodes. +ignite.GetCompute().Broadcast(new HelloAction()); + +class HelloAction : IComputeAction +{ + public void Invoke() + { + Console.WriteLine("Hello World!"); + } +} +---- +-- +* Ignite serializes the `HelloAction` instance and broadcasts to every node in the cluster. +* Remote nodes attempt to deserialize the `HelloAction` instance. If there is no such class in the currently loaded or referenced assemblies, +the nodes request an assembly with the class from the node that initiated the compute task or from other nodes (if necessary). +* The assembly file is sent from the originating or other node as a byte array and loaded with the `Assembly.Load(byte[])` method. + +=== Versioning + +https://msdn.microsoft.com/en-us/library/system.type.assemblyqualifiedname.aspx[Assembly-qualified type name, window=_blank] +includes the assembly version and is used to resolve types. + +If you keep the cluster running, do this change in the logic and see how the assembly gets reloaded automatically: + +* Modify `HelloAction` intance to print something else +* Change https://msdn.microsoft.com/en-us/library/system.reflection.assemblyversionattribute.aspx[AssemblyVersion, window=_blank] +* Recompile and run the application code +* The new version of the assembly will be deployed and executed on other nodes. + +Note, if you keep the `AssemblyVersion` unchanged, Ignite will use existing assembly that was previously loaded, since +there are no changes in the type name. + +Assemblies with different versions can co-exist and be used side by side. Some nodes can continue running old code, while +other nodes can execute computations with a newer version of the same class. + +The `AssemblyVersion` attribute can include asterisk (`*`) to enable the auto-increment on build: `[assembly: AssemblyVersion("1.0.*")]`. +This way you can keep the cluster running, repeatedly modify and run computations, and new assembly versions will be deployed every time. + +=== Dependencies + +Dependent assemblies are also loaded automatically, e.g. when `ComputeAction` calls some code from a different assembly. +Keep that in mind when using heavy frameworks and libraries: single compute call can cause lots of assemblies to be sent over the network. + +=== Unloading + +.NET does not allow assembly unloading. Instead, only the entire `AppDomain` can be unloaded with all assemblies. +Currently available `CurrentAppDomain` mode uses existing `AppDomain`, which means all peer-deployed assemblies will stay +loaded while current `AppDomain` lives. This may cause increased memory usage. + +== Example + +https://github.com/apache/ignite/blob/56975c266e7019f307bb9da42333a6db4e47365e/modules/platforms/dotnet/examples/Apache.Ignite.Examples/Compute/PeerAssemblyLoadingExample.cs[PeerAssemblyLoadingExample, window=_blank] can be used +to try out the remote assembly loading feature in practice: + +* Create a new Console Application in Visual Studio +* Install the Ignite.NET NuGet package `Install-Package Apache.Ignite` +* Open the `packages\Apache.Ignite.2.1\lib\net40` folder +* Add the `peerAssemblyLoadingMode='CurrentAppDomain'` attribute to `<igniteConfiguration>` element +* Run `Apache.Ignite.exe` (one or more times), leave the processes running +* Change `[AssemblyVersion]` in `AssemblyInfo.cs` to `1.0.*` +* Modify `Program.cs` in Visual Studio as shown below ++ +[tabs] +-- +tab:C#[] +[source,csharp] +---- +using System; +using Apache.Ignite.Core; +using Apache.Ignite.Core.Compute; +using Apache.Ignite.Core.Deployment; + +namespace ConsoleApp +{ + class Program + { + static void Main(string[] args) + { + var cfg = new IgniteConfiguration + { + PeerAssemblyLoadingMode = PeerAssemblyLoadingMode.CurrentAppDomain + }; + + using (var ignite = Ignition.Start(cfg)) + { + ignite.GetCompute().Broadcast(new HelloAction()); + } + } + + class HelloAction : IComputeAction + { + public void Invoke() + { + Console.WriteLine("Hello, World!"); + } + } + } +} +---- +tab:Apache.Ignite.exe.config[] +[source,xml] +---- +<igniteConfiguration peerAssemblyLoadingMode='CurrentAppDomain' /> +---- +tab:AssemblyInfo.cs[] +[source,csharp] +---- +... +[assembly: AssemblyVersion("1.0.*")] +... +---- +-- +* Run the project and observe the `"Hello, World!"` output in the console of all `Apache.Ignite.exe` windows. +* Change the `"Hello, World!"` text to something else and run the program again +* Observe different output on the nodes started with `Apache.Ignite.exe` earlier.