You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by vo...@apache.org on 2016/03/31 12:51:05 UTC
[2/3] ignite git commit: IGNITE-1630: .NET: Added LINQ support. This
closes #482.
http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/DelegateConverter.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/DelegateConverter.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/DelegateConverter.cs
index 00bda16..9a0f789 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/DelegateConverter.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/DelegateConverter.cs
@@ -18,11 +18,13 @@
namespace Apache.Ignite.Core.Impl.Common
{
using System;
+ using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
+ using Apache.Ignite.Core.Binary;
/// <summary>
/// Converts generic and non-generic delegates.
@@ -32,6 +34,9 @@ namespace Apache.Ignite.Core.Impl.Common
/** */
private const string DefaultMethodName = "Invoke";
+ /** */
+ private static readonly MethodInfo ReadObjectMethod = typeof (IBinaryRawReader).GetMethod("ReadObject");
+
/// <summary>
/// Compiles a function without arguments.
/// </summary>
@@ -188,7 +193,7 @@ namespace Apache.Ignite.Core.Impl.Common
/// <typeparam name="T">Result func type.</typeparam>
/// <param name="type">Type to be created by ctor.</param>
/// <param name="argTypes">Argument types.</param>
- /// <param name="convertResultToObject">if set to <c>true</c> [convert result to object].
+ /// <param name="convertResultToObject">
/// Flag that indicates whether ctor return value should be converted to object.
/// </param>
/// <returns>
@@ -203,6 +208,73 @@ namespace Apache.Ignite.Core.Impl.Common
}
/// <summary>
+ /// Compiles a contructor that reads all arguments from a binary reader.
+ /// </summary>
+ /// <typeparam name="T">Result type</typeparam>
+ /// <param name="ctor">The ctor.</param>
+ /// <param name="innerCtorFunc">Function to retrieve reading constructor for an argument.
+ /// Can be null or return null, in this case the argument will be read directly via ReadObject.</param>
+ /// <returns></returns>
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
+ public static Func<IBinaryRawReader, T> CompileCtor<T>(ConstructorInfo ctor,
+ Func<Type, ConstructorInfo> innerCtorFunc)
+ {
+ Debug.Assert(ctor != null);
+
+ var readerParam = Expression.Parameter(typeof (IBinaryRawReader));
+
+ var ctorExpr = GetConstructorExpression(ctor, innerCtorFunc, readerParam, typeof(T));
+
+ return Expression.Lambda<Func<IBinaryRawReader, T>>(ctorExpr, readerParam).Compile();
+ }
+
+ /// <summary>
+ /// Gets the constructor expression.
+ /// </summary>
+ /// <param name="ctor">The ctor.</param>
+ /// <param name="innerCtorFunc">The inner ctor function.</param>
+ /// <param name="readerParam">The reader parameter.</param>
+ /// <param name="resultType">Type of the result.</param>
+ /// <returns>
+ /// Ctor call expression.
+ /// </returns>
+ private static Expression GetConstructorExpression(ConstructorInfo ctor,
+ Func<Type, ConstructorInfo> innerCtorFunc, Expression readerParam, Type resultType)
+ {
+ var ctorParams = ctor.GetParameters();
+
+ var paramsExpr = new List<Expression>(ctorParams.Length);
+
+ foreach (var param in ctorParams)
+ {
+ var paramType = param.ParameterType;
+
+ var innerCtor = innerCtorFunc != null ? innerCtorFunc(paramType) : null;
+
+ if (innerCtor != null)
+ {
+ var readExpr = GetConstructorExpression(innerCtor, innerCtorFunc, readerParam, paramType);
+
+ paramsExpr.Add(readExpr);
+ }
+ else
+ {
+ var readMethod = ReadObjectMethod.MakeGenericMethod(paramType);
+
+ var readExpr = Expression.Call(readerParam, readMethod);
+
+ paramsExpr.Add(readExpr);
+ }
+ }
+
+ Expression ctorExpr = Expression.New(ctor, paramsExpr);
+
+ ctorExpr = Expression.Convert(ctorExpr, resultType);
+
+ return ctorExpr;
+ }
+
+ /// <summary>
/// Compiles the field setter.
/// </summary>
/// <param name="field">The field.</param>
@@ -211,7 +283,7 @@ namespace Apache.Ignite.Core.Impl.Common
public static Action<object, object> CompileFieldSetter(FieldInfo field)
{
Debug.Assert(field != null);
- Debug.Assert(field.DeclaringType != null); // non-static
+ Debug.Assert(field.DeclaringType != null);
var targetParam = Expression.Parameter(typeof(object));
var valParam = Expression.Parameter(typeof(object));
@@ -231,7 +303,7 @@ namespace Apache.Ignite.Core.Impl.Common
public static Action<object, object> CompilePropertySetter(PropertyInfo prop)
{
Debug.Assert(prop != null);
- Debug.Assert(prop.DeclaringType != null); // non-static
+ Debug.Assert(prop.DeclaringType != null);
var targetParam = Expression.Parameter(typeof(object));
var targetParamConverted = Expression.Convert(targetParam, prop.DeclaringType);
@@ -247,6 +319,53 @@ namespace Apache.Ignite.Core.Impl.Common
}
/// <summary>
+ /// Compiles the property setter.
+ /// </summary>
+ /// <param name="prop">The property.</param>
+ /// <returns>Compiled property setter.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
+ public static Func<object, object> CompilePropertyGetter(PropertyInfo prop)
+ {
+ Debug.Assert(prop != null);
+ Debug.Assert(prop.DeclaringType != null);
+
+ var targetParam = Expression.Parameter(typeof(object));
+ var targetParamConverted = prop.GetGetMethod().IsStatic
+ ? null
+ // ReSharper disable once AssignNullToNotNullAttribute (incorrect warning)
+ : Expression.Convert(targetParam, prop.DeclaringType);
+
+ var fld = Expression.Property(targetParamConverted, prop);
+
+ var fldConverted = Expression.Convert(fld, typeof (object));
+
+ return Expression.Lambda<Func<object, object>>(fldConverted, targetParam).Compile();
+ }
+
+ /// <summary>
+ /// Compiles the property setter.
+ /// </summary>
+ /// <param name="field">The field.</param>
+ /// <returns>Compiled property setter.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
+ public static Func<object, object> CompileFieldGetter(FieldInfo field)
+ {
+ Debug.Assert(field != null);
+ Debug.Assert(field.DeclaringType != null);
+
+ var targetParam = Expression.Parameter(typeof(object));
+ var targetParamConverted = field.IsStatic
+ ? null
+ : Expression.Convert(targetParam, field.DeclaringType);
+
+ var fld = Expression.Field(targetParamConverted, field);
+
+ var fldConverted = Expression.Convert(fld, typeof (object));
+
+ return Expression.Lambda<Func<object, object>>(fldConverted, targetParam).Compile();
+ }
+
+ /// <summary>
/// Gets a method to write a field (including private and readonly).
/// NOTE: Expression Trees can't write readonly fields.
/// </summary>
@@ -259,7 +378,7 @@ namespace Apache.Ignite.Core.Impl.Common
var declaringType = field.DeclaringType;
- Debug.Assert(declaringType != null); // static fields are not supported
+ Debug.Assert(declaringType != null);
var method = new DynamicMethod(string.Empty, null, new[] { typeof(object), field.FieldType },
declaringType, true);
http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/Logger.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/Logger.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/Logger.cs
new file mode 100644
index 0000000..cab5afc
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/Logger.cs
@@ -0,0 +1,37 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+namespace Apache.Ignite.Core.Impl.Common
+{
+ using System;
+
+ /// <summary>
+ /// Console logger.
+ /// </summary>
+ internal static class Logger
+ {
+ /// <summary>
+ /// Logs the warning.
+ /// </summary>
+ /// <param name="warning">The warning.</param>
+ /// <param name="args">The arguments.</param>
+ public static void LogWarning(string warning, params object[] args)
+ {
+ Console.WriteLine("WARNING: " + string.Format(warning, args));
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/TypeCaster.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/TypeCaster.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/TypeCaster.cs
index 2d4936f..6e6bf7d 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/TypeCaster.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/TypeCaster.cs
@@ -39,7 +39,19 @@ namespace Apache.Ignite.Core.Impl.Common
Justification = "Intended usage to leverage compiler caching.")]
public static T Cast<TFrom>(TFrom obj)
{
+#if (DEBUG)
+ try
+ {
+ return Casters<TFrom>.Caster(obj);
+ }
+ catch (InvalidCastException)
+ {
+ throw new InvalidCastException(string.Format("Specified cast is not valid: {0} -> {1}", typeof (TFrom),
+ typeof (T)));
+ }
+#else
return Casters<TFrom>.Caster(obj);
+#endif
}
/// <summary>
http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core/Impl/IgniteManager.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/IgniteManager.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/IgniteManager.cs
index b35c46f..7bd0417 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/IgniteManager.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/IgniteManager.cs
@@ -69,7 +69,7 @@ namespace Apache.Ignite.Core.Impl
{
if (!_jvmCfg.Equals(jvmCfg))
{
- Console.WriteLine("Attempting to start Ignite node with different Java " +
+ Logger.LogWarning("Attempting to start Ignite node with different Java " +
"configuration; current Java configuration will be ignored (consider " +
"starting node in separate process) [oldConfig=" + _jvmCfg +
", newConfig=" + jvmCfg + ']');
http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.csproj
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.csproj b/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.csproj
new file mode 100644
index 0000000..59aef1c
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.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>{5B571661-17F4-4F29-8C7D-0EDB38CA9B55}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Apache.Ignite.Linq</RootNamespace>
+ <AssemblyName>Apache.Ignite.Linq</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
+ <DebugSymbols>true</DebugSymbols>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <DebugType>full</DebugType>
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <ErrorReport>prompt</ErrorReport>
+ <CodeAnalysisRuleSet>..\Apache.Ignite.Core\Apache.Ignite.Core.ruleset</CodeAnalysisRuleSet>
+ <RunCodeAnalysis>true</RunCodeAnalysis>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <Optimize>true</Optimize>
+ <DebugType>none</DebugType>
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <ErrorReport>prompt</ErrorReport>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup>
+ <SignAssembly>true</SignAssembly>
+ </PropertyGroup>
+ <PropertyGroup>
+ <AssemblyOriginatorKeyFile>Apache.Ignite.Linq.snk</AssemblyOriginatorKeyFile>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Remotion.Linq, Version=2.0.0.0, Culture=neutral, PublicKeyToken=fee00910d6e5f53b, processorArchitecture=MSIL">
+ <HintPath>..\packages\Remotion.Linq.2.0.1\lib\net40\Remotion.Linq.dll</HintPath>
+ <Private>True</Private>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="CacheExtensions.cs" />
+ <Compile Include="CompiledQuery.cs" />
+ <Compile Include="ICacheQueryable.cs" />
+ <Compile Include="Impl\AliasDictionary.cs" />
+ <Compile Include="Impl\CacheFieldsQueryable.cs" />
+ <Compile Include="Impl\CacheFieldsQueryExecutor.cs" />
+ <Compile Include="Impl\CacheFieldsQueryProvider.cs" />
+ <Compile Include="Impl\CacheQueryable.cs" />
+ <Compile Include="Impl\CacheQueryableBase.cs" />
+ <Compile Include="Impl\CacheQueryExpressionVisitor.cs" />
+ <Compile Include="Impl\CacheQueryModelVisitor.cs" />
+ <Compile Include="Impl\CacheQueryParser.cs" />
+ <Compile Include="Impl\ICacheQueryableInternal.cs" />
+ <Compile Include="Impl\ICacheQueryProxy.cs" />
+ <Compile Include="Impl\MethodVisitor.cs" />
+ <Compile Include="Impl\QueryData.cs" />
+ <Compile Include="Impl\SqlTypes.cs" />
+ <Compile Include="Impl\ExpressionWalker.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="Apache.Ignite.Linq.nuspec" />
+ <None Include="Apache.Ignite.Linq.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>
+ <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/ef642e91/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.nuspec
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.nuspec b/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.nuspec
new file mode 100644
index 0000000..7654dac
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.nuspec
@@ -0,0 +1,63 @@
+<?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 (x64 configuration)
+2) Create package (use csproj instead of nuspec so that template substitution works):
+ nuget pack Apache.Ignite.Linq.csproj -Prop Configuration=Release -Prop Platform=x64
+
+-->
+
+<package >
+ <metadata>
+ <id>Apache.Ignite.Linq</id>
+ <title>Apache Ignite LINQ Provider</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>
+LINQ Provider for Apache Ignite
+
+More info: https://apacheignite-net.readme.io/
+
+WARNING: this only works with x64 build targets.
+ </description>
+ <summary>
+ LINQ Provider for Apache Ignite
+ </summary>
+ <releaseNotes></releaseNotes>
+ <copyright>Copyright 2015</copyright>
+ <tags>Apache Ignite In-Memory Distributed Computing SQL NoSQL LINQ Grid Map Reduce Cache linqpad-samples</tags>
+ <dependencies>
+ <dependency id="Apache.Ignite" version="[$version$]" />
+ <dependency id="Remotion.Linq" version="[2.0.1]" />
+ </dependencies>
+ </metadata>
+ <files>
+ <!-- LINQPad samples -->
+ <file src="NuGet\LINQPad\*.*" target="linqpad-samples" />
+ </files>
+</package>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.snk
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.snk b/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.snk
new file mode 100644
index 0000000..799e742
Binary files /dev/null and b/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.snk differ
http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Linq/CacheExtensions.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/CacheExtensions.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/CacheExtensions.cs
new file mode 100644
index 0000000..ecea4ed
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/CacheExtensions.cs
@@ -0,0 +1,98 @@
+/*
+ * 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.Linq
+{
+ using System.Linq;
+ using Apache.Ignite.Core.Cache;
+ using Apache.Ignite.Core.Cache.Configuration;
+ using Apache.Ignite.Linq.Impl;
+
+ /// <summary>
+ /// Extensions methods for <see cref="ICache{TK,TV}"/>.
+ /// </summary>
+ public static class CacheLinqExtensions
+ {
+ /// <summary>
+ /// Gets an <see cref="IQueryable{T}"/> instance over this cache.
+ /// <para />
+ /// Resulting query will be translated to cache SQL query and executed over the cache instance
+ /// via either <see cref="ICache{TK,TV}.Query"/> or <see cref="ICache{TK,TV}.QueryFields"/>,
+ /// depending on requested result.
+ /// <para />
+ /// Result of this method (and subsequent query) can be cast to <see cref="ICacheQueryable"/> for introspection.
+ /// </summary>
+ /// <typeparam name="TKey">The type of the key.</typeparam>
+ /// <typeparam name="TValue">The type of the value.</typeparam>
+ /// <param name="cache">The cache.</param>
+ /// <returns><see cref="IQueryable{T}"/> instance over this cache.</returns>
+ public static IQueryable<ICacheEntry<TKey, TValue>> AsCacheQueryable<TKey, TValue>(
+ this ICache<TKey, TValue> cache)
+ {
+ return cache.AsCacheQueryable(false, null);
+ }
+
+ /// <summary>
+ /// Gets an <see cref="IQueryable{T}"/> instance over this cache.
+ /// <para />
+ /// Resulting query will be translated to cache SQL query and executed over the cache instance
+ /// via either <see cref="ICache{TK,TV}.Query"/> or <see cref="ICache{TK,TV}.QueryFields"/>,
+ /// depending on requested result.
+ /// <para />
+ /// Result of this method (and subsequent query) can be cast to <see cref="ICacheQueryable"/> for introspection.
+ /// </summary>
+ /// <typeparam name="TKey">The type of the key.</typeparam>
+ /// <typeparam name="TValue">The type of the value.</typeparam>
+ /// <param name="cache">The cache.</param>
+ /// <param name="local">Local flag. When set query will be executed only on local node, so only local
+ /// entries will be returned as query result.</param>
+ /// <returns><see cref="IQueryable{T}"/> instance over this cache.</returns>
+ public static IQueryable<ICacheEntry<TKey, TValue>> AsCacheQueryable<TKey, TValue>(
+ this ICache<TKey, TValue> cache, bool local)
+ {
+ return cache.AsCacheQueryable(local, null);
+ }
+
+ /// <summary>
+ /// Gets an <see cref="IQueryable{T}" /> instance over this cache.
+ /// <para />
+ /// Resulting query will be translated to cache SQL query and executed over the cache instance
+ /// via either <see cref="ICache{TK,TV}.Query" /> or <see cref="ICache{TK,TV}.QueryFields" />,
+ /// depending on requested result.
+ /// <para />
+ /// Result of this method (and subsequent query) can be cast to <see cref="ICacheQueryable" /> for introspection.
+ /// </summary>
+ /// <typeparam name="TKey">The type of the key.</typeparam>
+ /// <typeparam name="TValue">The type of the value.</typeparam>
+ /// <param name="cache">The cache.</param>
+ /// <param name="local">Local flag. When set query will be executed only on local node, so only local
+ /// entries will be returned as query result.</param>
+ /// <param name="tableName">
+ /// Name of the table.
+ /// <para />
+ /// Table name is equal to short class name of a cache value.
+ /// When a cache has only one type of values, or only one <see cref="QueryEntity"/> defined,
+ /// table name will be inferred and can be omitted.
+ /// </param>
+ /// <returns><see cref="IQueryable{T}" /> instance over this cache.</returns>
+ public static IQueryable<ICacheEntry<TKey, TValue>> AsCacheQueryable<TKey, TValue>(
+ this ICache<TKey, TValue> cache, bool local, string tableName)
+ {
+ return new CacheQueryable<TKey, TValue>(cache, local, tableName);
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Linq/CompiledQuery.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/CompiledQuery.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/CompiledQuery.cs
new file mode 100644
index 0000000..2fa66ce
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/CompiledQuery.cs
@@ -0,0 +1,206 @@
+/*
+ * 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.Linq
+{
+ using System;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Linq;
+ using Apache.Ignite.Core.Cache.Query;
+ using Apache.Ignite.Core.Impl.Common;
+ using Apache.Ignite.Linq.Impl;
+
+ /// <summary>
+ /// Represents a compiled cache query.
+ /// </summary>
+ public static class CompiledQuery
+ {
+ /// <summary>
+ /// Creates a new delegate that represents the compiled cache query.
+ /// </summary>
+ /// <param name="query">The query to compile.</param>
+ /// <returns>Delegate that represents the compiled cache query.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0",
+ Justification = "Invalid warning, validation is present.")]
+ public static Func<IQueryCursor<T>> Compile<T>(Func<IQueryable<T>> query)
+ {
+ IgniteArgumentCheck.NotNull(query, "query");
+
+ var compiledQuery = GetCompiledQuery(query(), null);
+
+ return () => compiledQuery(new object[0]);
+ }
+
+ /// <summary>
+ /// Creates a new delegate that represents the compiled cache query.
+ /// </summary>
+ /// <param name="query">The query to compile.</param>
+ /// <returns>Delegate that represents the compiled cache query.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0",
+ Justification = "Invalid warning, validation is present.")]
+ public static Func<T1, IQueryCursor<T>> Compile<T, T1>(Func<T1, IQueryable<T>> query)
+ {
+ IgniteArgumentCheck.NotNull(query, "query");
+
+ var compiledQuery = GetCompiledQuery(query(default(T1)), null);
+
+ return x => compiledQuery(new object[] {x});
+ }
+
+ /// <summary>
+ /// Creates a new delegate that represents the compiled cache query.
+ /// </summary>
+ /// <param name="query">The query to compile.</param>
+ /// <returns>Delegate that represents the compiled cache query.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0",
+ Justification = "Invalid warning, validation is present.")]
+ public static Func<T1, T2, IQueryCursor<T>> Compile<T, T1, T2>(Func<T1, T2,
+ IQueryable<T>> query)
+ {
+ IgniteArgumentCheck.NotNull(query, "query");
+
+ var compiledQuery = GetCompiledQuery(query(default(T1), default(T2)), query);
+
+ return (x, y) => compiledQuery(new object[] {x, y});
+ }
+
+ /// <summary>
+ /// Creates a new delegate that represents the compiled cache query.
+ /// </summary>
+ /// <param name="query">The query to compile.</param>
+ /// <returns>Delegate that represents the compiled cache query.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0",
+ Justification = "Invalid warning, validation is present.")]
+ public static Func<T1, T2, T3, IQueryCursor<T>> Compile<T, T1, T2, T3>(Func<T1, T2, T3,
+ IQueryable<T>> query)
+ {
+ IgniteArgumentCheck.NotNull(query, "query");
+
+ var compiledQuery = GetCompiledQuery(query(default(T1), default(T2), default(T3)), query);
+
+ return (x, y, z) => compiledQuery(new object[] {x, y, z});
+ }
+
+ /// <summary>
+ /// Creates a new delegate that represents the compiled cache query.
+ /// </summary>
+ /// <param name="query">The query to compile.</param>
+ /// <returns>Delegate that represents the compiled cache query.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0",
+ Justification = "Invalid warning, validation is present.")]
+ public static Func<T1, T2, T3, T4, IQueryCursor<T>> Compile<T, T1, T2, T3, T4>(Func<T1, T2, T3, T4,
+ IQueryable<T>> query)
+ {
+ IgniteArgumentCheck.NotNull(query, "query");
+
+ var compiledQuery = GetCompiledQuery(query(default(T1), default(T2), default(T3), default(T4)), query);
+
+ return (x, y, z, a) => compiledQuery(new object[] {x, y, z, a});
+ }
+
+ /// <summary>
+ /// Creates a new delegate that represents the compiled cache query.
+ /// </summary>
+ /// <param name="query">The query to compile.</param>
+ /// <returns>Delegate that represents the compiled cache query.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0",
+ Justification = "Invalid warning, validation is present.")]
+ public static Func<T1, T2, T3, T4, T5, IQueryCursor<T>> Compile<T, T1, T2, T3, T4, T5>(
+ Func<T1, T2, T3, T4, T5, IQueryable<T>> query)
+ {
+ IgniteArgumentCheck.NotNull(query, "query");
+
+ var compiledQuery =
+ GetCompiledQuery(query(default(T1), default(T2), default(T3), default(T4), default(T5)), query);
+
+ return (x, y, z, a, b) => compiledQuery(new object[] {x, y, z, a, b});
+ }
+
+ /// <summary>
+ /// Creates a new delegate that represents the compiled cache query.
+ /// </summary>
+ /// <param name="query">The query to compile.</param>
+ /// <returns>Delegate that represents the compiled cache query.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0",
+ Justification = "Invalid warning, validation is present.")]
+ public static Func<T1, T2, T3, T4, T5, T6, IQueryCursor<T>> Compile<T, T1, T2, T3, T4, T5, T6>(
+ Func<T1, T2, T3, T4, T5, T6, IQueryable<T>> query)
+ {
+ IgniteArgumentCheck.NotNull(query, "query");
+
+ var compiledQuery = GetCompiledQuery(query(default(T1), default(T2), default(T3), default(T4),
+ default(T5), default(T6)), query);
+
+ return (x, y, z, a, b, c) => compiledQuery(new object[] {x, y, z, a, b, c});
+ }
+
+ /// <summary>
+ /// Creates a new delegate that represents the compiled cache query.
+ /// </summary>
+ /// <param name="query">The query to compile.</param>
+ /// <returns>Delegate that represents the compiled cache query.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0",
+ Justification = "Invalid warning, validation is present.")]
+ public static Func<T1, T2, T3, T4, T5, T6, T7, IQueryCursor<T>> Compile<T, T1, T2, T3, T4, T5, T6, T7>(
+ Func<T1, T2, T3, T4, T5, T6, T7, IQueryable<T>> query)
+ {
+ IgniteArgumentCheck.NotNull(query, "query");
+
+ var compiledQuery = GetCompiledQuery(query(default(T1), default(T2), default(T3), default(T4),
+ default(T5), default(T6), default(T7)), query);
+
+ return (x, y, z, a, b, c, d) => compiledQuery(new object[] {x, y, z, a, b, c, d});
+ }
+
+ /// <summary>
+ /// Creates a new delegate that represents the compiled cache query.
+ /// </summary>
+ /// <param name="query">The query to compile.</param>
+ /// <returns>Delegate that represents the compiled cache query.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0",
+ Justification = "Invalid warning, validation is present.")]
+ public static Func<T1, T2, T3, T4, T5, T6, T7, T8, IQueryCursor<T>> Compile<T, T1, T2, T3, T4, T5, T6, T7, T8>(
+ Func<T1, T2, T3, T4, T5, T6, T7, T8, IQueryable<T>> query)
+ {
+ IgniteArgumentCheck.NotNull(query, "query");
+
+ var compiledQuery = GetCompiledQuery(query(default(T1), default(T2), default(T3), default(T4),
+ default(T5), default(T6), default(T7), default(T8)), query);
+
+ return (x, y, z, a, b, c, d, e) => compiledQuery(new object[] {x, y, z, a, b, c, d, e});
+ }
+
+ /// <summary>
+ /// Gets the compiled query.
+ /// </summary>
+ private static Func<object[], IQueryCursor<T>> GetCompiledQuery<T>(IQueryable<T> queryable,
+ Delegate queryCaller)
+ {
+ var cacheQueryable = queryable as ICacheQueryableInternal;
+
+ if (cacheQueryable == null)
+ throw new ArgumentException(
+ string.Format("{0} can only compile cache queries produced by AsCacheQueryable method. " +
+ "Provided query is not valid: '{1}'", typeof (CompiledQuery).FullName, queryable));
+
+ Debug.WriteLine(queryable);
+
+ return cacheQueryable.CompileQuery<T>(queryCaller);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Linq/ICacheQueryable.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/ICacheQueryable.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/ICacheQueryable.cs
new file mode 100644
index 0000000..684f746
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/ICacheQueryable.cs
@@ -0,0 +1,53 @@
+/*
+ * 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.Linq
+{
+ using System;
+ using Apache.Ignite.Core;
+ using Apache.Ignite.Core.Cache.Query;
+
+ /// <summary>
+ /// Common interface for cache queryables.
+ /// </summary>
+ public interface ICacheQueryable
+ {
+ /// <summary>
+ /// Gets the name of the cache that is associated with this query.
+ /// </summary>
+ /// <value>
+ /// The name of the cache.
+ /// </value>
+ string CacheName { get; }
+
+ /// <summary>
+ /// Gets the Ignite instance associated with this query.
+ /// </summary>
+ IIgnite Ignite { get; }
+
+ /// <summary>
+ /// Returns fields query that represents current queryable.
+ /// </summary>
+ /// <returns>Fields query that represents current queryable.</returns>
+ SqlFieldsQuery GetFieldsQuery();
+
+ /// <summary>
+ /// Gets the type of the element.
+ /// </summary>
+ Type ElementType { get; }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/AliasDictionary.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/AliasDictionary.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/AliasDictionary.cs
new file mode 100644
index 0000000..10a414c
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/AliasDictionary.cs
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Linq.Impl
+{
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Text;
+ using Remotion.Linq.Clauses;
+
+ /// <summary>
+ /// Alias dictionary.
+ /// </summary>
+ internal class AliasDictionary
+ {
+ /** */
+ private int _aliasIndex;
+
+ /** */
+ private Dictionary<string, string> _aliases = new Dictionary<string, string>();
+
+ /** */
+ private readonly Stack<Dictionary<string, string>> _stack = new Stack<Dictionary<string, string>>();
+
+ /// <summary>
+ /// Pushes current aliases to stack.
+ /// </summary>
+ public void Push()
+ {
+ _stack.Push(_aliases);
+
+ _aliases = new Dictionary<string, string>();
+ }
+
+ /// <summary>
+ /// Pops current aliases from stack.
+ /// </summary>
+ public void Pop()
+ {
+ _aliases = _stack.Pop();
+ }
+
+ /// <summary>
+ /// Gets the table alias.
+ /// </summary>
+ public string GetTableAlias(ICacheQueryableInternal queryable)
+ {
+ Debug.Assert(queryable != null);
+
+ return GetTableAlias(ExpressionWalker.GetTableNameWithSchema(queryable));
+ }
+
+ /// <summary>
+ /// Gets the table alias.
+ /// </summary>
+ public string GetTableAlias(string fullName)
+ {
+ Debug.Assert(!string.IsNullOrEmpty(fullName));
+
+ string alias;
+
+ if (!_aliases.TryGetValue(fullName, out alias))
+ {
+ alias = "_T" + _aliasIndex++;
+
+ _aliases[fullName] = alias;
+ }
+
+ return alias;
+ }
+
+ /// <summary>
+ /// Appends as clause.
+ /// </summary>
+ public StringBuilder AppendAsClause(StringBuilder builder, IFromClause clause)
+ {
+ Debug.Assert(builder != null);
+ Debug.Assert(clause != null);
+
+ var queryable = ExpressionWalker.GetCacheQueryable(clause);
+ var tableName = ExpressionWalker.GetTableNameWithSchema(queryable);
+
+ builder.AppendFormat("{0} as {1}", tableName, GetTableAlias(tableName));
+
+ return builder;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryExecutor.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryExecutor.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryExecutor.cs
new file mode 100644
index 0000000..09f57ff
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryExecutor.cs
@@ -0,0 +1,225 @@
+/*
+ * 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.Linq.Impl
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Linq;
+ using System.Linq.Expressions;
+ using System.Reflection;
+ using Apache.Ignite.Core.Binary;
+ using Apache.Ignite.Core.Cache;
+ using Apache.Ignite.Core.Cache.Query;
+ using Apache.Ignite.Core.Impl.Cache;
+ using Apache.Ignite.Core.Impl.Common;
+ using Remotion.Linq;
+
+ /// <summary>
+ /// Fields query executor.
+ /// </summary>
+ internal class CacheFieldsQueryExecutor : IQueryExecutor
+ {
+ /** */
+ private readonly ICacheInternal _cache;
+
+ /** */
+ private static readonly CopyOnWriteConcurrentDictionary<ConstructorInfo, object> CtorCache =
+ new CopyOnWriteConcurrentDictionary<ConstructorInfo, object>();
+
+ /** */
+ private readonly bool _local;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CacheFieldsQueryExecutor" /> class.
+ /// </summary>
+ /// <param name="cache">The executor function.</param>
+ /// <param name="local">Local flag.</param>
+ public CacheFieldsQueryExecutor(ICacheInternal cache, bool local)
+ {
+ Debug.Assert(cache != null);
+
+ _cache = cache;
+ _local = local;
+ }
+
+ /// <summary>
+ /// Gets the local flag.
+ /// </summary>
+ public bool Local
+ {
+ get { return _local; }
+ }
+
+ /** <inheritdoc /> */
+ public T ExecuteScalar<T>(QueryModel queryModel)
+ {
+ return ExecuteSingle<T>(queryModel, false);
+ }
+
+ /** <inheritdoc /> */
+ public T ExecuteSingle<T>(QueryModel queryModel, bool returnDefaultWhenEmpty)
+ {
+ var col = ExecuteCollection<T>(queryModel);
+
+ return returnDefaultWhenEmpty ? col.SingleOrDefault() : col.Single();
+ }
+
+ /** <inheritdoc /> */
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
+ public IEnumerable<T> ExecuteCollection<T>(QueryModel queryModel)
+ {
+ Debug.Assert(queryModel != null);
+
+ var qryData = GetQueryData(queryModel);
+
+ Debug.WriteLine("\nFields Query: {0} | {1}", qryData.QueryText,
+ string.Join(", ", qryData.Parameters.Select(x => x == null ? "null" : x.ToString())));
+
+ var qry = new SqlFieldsQuery(qryData.QueryText, _local, qryData.Parameters.ToArray());
+
+ var selector = GetResultSelector<T>(queryModel.SelectClause.Selector);
+
+ return _cache.QueryFields(qry, selector);
+ }
+
+ /// <summary>
+ /// Compiles the query.
+ /// </summary>
+ public Func<object[], IQueryCursor<T>> CompileQuery<T>(QueryModel queryModel, Delegate queryCaller)
+ {
+ Debug.Assert(queryModel != null);
+
+ var qryData = GetQueryData(queryModel);
+
+ var qryText = qryData.QueryText;
+
+ var selector = GetResultSelector<T>(queryModel.SelectClause.Selector);
+
+ if (queryCaller == null)
+ return args => _cache.QueryFields(new SqlFieldsQuery(qryText, _local, args), selector);
+
+ // Compiled query is a delegate with query parameters
+ // Delegate parameters order and query parameters order may differ
+
+ // These are in order of usage in query
+ var qryOrderParams = qryData.ParameterExpressions.OfType<MemberExpression>()
+ .Select(x => x.Member.Name).ToList();
+
+ // These are in order they come from user
+ var userOrderParams = queryCaller.Method.GetParameters().Select(x => x.Name).ToList();
+
+ if ((qryOrderParams.Count != qryData.Parameters.Count) ||
+ (qryOrderParams.Count != userOrderParams.Count))
+ throw new InvalidOperationException("Error compiling query: all compiled query arguments " +
+ "should come from enclosing lambda expression");
+
+ var indices = qryOrderParams.Select(x => userOrderParams.IndexOf(x)).ToArray();
+
+ // Check if user param order is already correct
+ if (indices.SequenceEqual(Enumerable.Range(0, indices.Length)))
+ return args => _cache.QueryFields(new SqlFieldsQuery(qryText, _local, args), selector);
+
+ // Return delegate with reorder
+ return args => _cache.QueryFields(new SqlFieldsQuery(qryText, _local,
+ args.Select((x, i) => args[indices[i]]).ToArray()), selector);
+ }
+
+ /** <inheritdoc /> */
+ public static QueryData GetQueryData(QueryModel queryModel)
+ {
+ Debug.Assert(queryModel != null);
+
+ return new CacheQueryModelVisitor().GenerateQuery(queryModel);
+ }
+
+ /// <summary>
+ /// Gets the result selector.
+ /// </summary>
+ private static Func<IBinaryRawReader, int, T> GetResultSelector<T>(Expression selectorExpression)
+ {
+ var newExpr = selectorExpression as NewExpression;
+
+ if (newExpr != null)
+ return GetCompiledCtor<T>(newExpr.Constructor);
+
+ var entryCtor = GetCacheEntryCtorInfo(typeof(T));
+
+ if (entryCtor != null)
+ return GetCompiledCtor<T>(entryCtor);
+
+ if (typeof(T) == typeof(bool))
+ return ReadBool<T>;
+
+ return (reader, count) => reader.ReadObject<T>();
+ }
+
+ /// <summary>
+ /// Reads the bool. Actual data may be bool or int/long.
+ /// </summary>
+ private static T ReadBool<T>(IBinaryRawReader reader, int count)
+ {
+ var obj = reader.ReadObject<object>();
+
+ if (obj is bool)
+ return (T) obj;
+
+ if (obj is long)
+ return TypeCaster<T>.Cast((long) obj != 0);
+
+ if (obj is int)
+ return TypeCaster<T>.Cast((int) obj != 0);
+
+ throw new InvalidOperationException("Expected bool, got: " + obj);
+ }
+
+ /// <summary>
+ /// Gets the cache entry constructor.
+ /// </summary>
+ private static ConstructorInfo GetCacheEntryCtorInfo(Type entryType)
+ {
+ if (!entryType.IsGenericType || entryType.GetGenericTypeDefinition() != typeof(ICacheEntry<,>))
+ return null;
+
+ var args = entryType.GetGenericArguments();
+
+ var targetType = typeof (CacheEntry<,>).MakeGenericType(args);
+
+ return targetType.GetConstructors().Single();
+ }
+
+ /// <summary>
+ /// Gets the compiled constructor.
+ /// </summary>
+ private static Func<IBinaryRawReader, int, T> GetCompiledCtor<T>(ConstructorInfo ctorInfo)
+ {
+ object result;
+
+ if (CtorCache.TryGetValue(ctorInfo, out result))
+ return (Func<IBinaryRawReader, int, T>) result;
+
+ return (Func<IBinaryRawReader, int, T>) CtorCache.GetOrAdd(ctorInfo, x =>
+ {
+ var innerCtor1 = DelegateConverter.CompileCtor<T>(x, GetCacheEntryCtorInfo);
+
+ return (Func<IBinaryRawReader, int, T>) ((r, c) => innerCtor1(r));
+ });
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryProvider.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryProvider.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryProvider.cs
new file mode 100644
index 0000000..3f5fe34
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryProvider.cs
@@ -0,0 +1,239 @@
+/*
+ * 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.Linq.Impl
+{
+ using System;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Linq;
+ using System.Linq.Expressions;
+ using System.Reflection;
+ using Apache.Ignite.Core;
+ using Apache.Ignite.Core.Cache;
+ using Apache.Ignite.Core.Cache.Configuration;
+ using Remotion.Linq;
+ using Remotion.Linq.Clauses.StreamedData;
+ using Remotion.Linq.Parsing.Structure;
+ using Remotion.Linq.Utilities;
+
+ /// <summary>
+ /// Query provider for fields queries (projections).
+ /// </summary>
+ internal class CacheFieldsQueryProvider : IQueryProvider
+ {
+ /** */
+ private static readonly MethodInfo GenericCreateQueryMethod =
+ typeof (CacheFieldsQueryProvider).GetMethods().Single(m => m.Name == "CreateQuery" && m.IsGenericMethod);
+
+ /** */
+ private readonly IQueryParser _parser;
+
+ /** */
+ private readonly CacheFieldsQueryExecutor _executor;
+
+ /** */
+ private readonly IIgnite _ignite;
+
+ /** */
+ private readonly CacheConfiguration _cacheConfiguration;
+
+ /** */
+ private readonly string _tableName;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CacheFieldsQueryProvider"/> class.
+ /// </summary>
+ public CacheFieldsQueryProvider(IQueryParser queryParser, CacheFieldsQueryExecutor executor, IIgnite ignite,
+ CacheConfiguration cacheConfiguration, string tableName, Type cacheValueType)
+ {
+ Debug.Assert(queryParser != null);
+ Debug.Assert(executor != null);
+ Debug.Assert(ignite != null);
+ Debug.Assert(cacheConfiguration != null);
+ Debug.Assert(cacheValueType != null);
+
+ _parser = queryParser;
+ _executor = executor;
+ _ignite = ignite;
+ _cacheConfiguration = cacheConfiguration;
+
+ if (tableName != null)
+ {
+ _tableName = tableName;
+
+ ValidateTableName();
+ }
+ else
+ _tableName = InferTableName(cacheValueType);
+ }
+
+ /// <summary>
+ /// Gets the ignite.
+ /// </summary>
+ public IIgnite Ignite
+ {
+ get { return _ignite; }
+ }
+
+ /// <summary>
+ /// Gets the name of the cache.
+ /// </summary>
+ public CacheConfiguration CacheConfiguration
+ {
+ get { return _cacheConfiguration; }
+ }
+
+ /// <summary>
+ /// Gets the name of the table.
+ /// </summary>
+ public string TableName
+ {
+ get { return _tableName; }
+ }
+
+ /// <summary>
+ /// Gets the executor.
+ /// </summary>
+ public CacheFieldsQueryExecutor Executor
+ {
+ get { return _executor; }
+ }
+
+ /// <summary>
+ /// Generates the query model.
+ /// </summary>
+ public QueryModel GenerateQueryModel(Expression expression)
+ {
+ return _parser.GetParsedQuery(expression);
+ }
+
+ /** <inheritdoc /> */
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0")]
+ public IQueryable CreateQuery(Expression expression)
+ {
+ Debug.Assert(expression != null);
+
+ var elementType = GetItemTypeOfClosedGenericIEnumerable(expression.Type, "expression");
+
+ // Slow, but this method is never called during normal LINQ usage with generics
+ return (IQueryable) GenericCreateQueryMethod.MakeGenericMethod(elementType)
+ .Invoke(this, new object[] {expression});
+ }
+
+ /** <inheritdoc /> */
+ public IQueryable<T> CreateQuery<T>(Expression expression)
+ {
+ return new CacheFieldsQueryable<T>(this, expression);
+ }
+
+ /** <inheritdoc /> */
+ object IQueryProvider.Execute(Expression expression)
+ {
+ return Execute(expression);
+ }
+
+ /** <inheritdoc /> */
+ public TResult Execute<TResult>(Expression expression)
+ {
+ return (TResult) Execute(expression).Value;
+ }
+
+ /// <summary>
+ /// Executes the specified expression.
+ /// </summary>
+ private IStreamedData Execute(Expression expression)
+ {
+ var model = GenerateQueryModel(expression);
+
+ return model.Execute(_executor);
+ }
+
+ /// <summary>
+ /// Validates the name of the table.
+ /// </summary>
+ private void ValidateTableName()
+ {
+ var validTableNames = GetValidTableNames();
+
+ if (!validTableNames.Contains(_tableName, StringComparer.OrdinalIgnoreCase))
+ {
+ throw new CacheException(string.Format("Invalid table name specified for CacheQueryable: '{0}'; " +
+ "configured table names are: {1}",
+ _tableName,
+ validTableNames.Aggregate((x, y) => x + ", " + y)));
+ }
+ }
+
+ /// <summary>
+ /// Gets the valid table names for current cache.
+ /// </summary>
+ private string[] GetValidTableNames()
+ {
+ // Split on '.' to throw away Java type namespace
+ var validTableNames = _cacheConfiguration.QueryEntities == null
+ ? null
+ : _cacheConfiguration.QueryEntities.Select(e => e.ValueTypeName.Split('.').Last()).ToArray();
+
+ if (validTableNames == null || !validTableNames.Any())
+ throw new CacheException(string.Format("Queries are not configured for cache '{0}'",
+ _cacheConfiguration.Name ?? "null"));
+
+ return validTableNames;
+ }
+
+ /// <summary>
+ /// Infers the name of the table from cache configuration.
+ /// </summary>
+ /// <param name="cacheValueType"></param>
+ private string InferTableName(Type cacheValueType)
+ {
+ var validTableNames = GetValidTableNames();
+
+ if (validTableNames.Length == 1)
+ return validTableNames[0];
+
+ var valueTypeName = cacheValueType.Name;
+
+ if (validTableNames.Contains(valueTypeName, StringComparer.OrdinalIgnoreCase))
+ return valueTypeName;
+
+ throw new CacheException(string.Format("Table name cannot be inferred for cache '{0}', " +
+ "please use AsCacheQueryable overload with tableName parameter. " +
+ "Valid table names: {1}", _cacheConfiguration.Name ?? "null",
+ validTableNames.Aggregate((x, y) => x + ", " + y)));
+ }
+
+ /// <summary>
+ /// Gets the item type of closed generic i enumerable.
+ /// </summary>
+ private static Type GetItemTypeOfClosedGenericIEnumerable(Type enumerableType, string argumentName)
+ {
+ Type itemType;
+
+ if (!ItemTypeReflectionUtility.TryGetItemTypeOfClosedGenericIEnumerable(enumerableType, out itemType))
+ {
+ var message = string.Format("Expected a closed generic type implementing IEnumerable<T>, " +
+ "but found '{0}'.", enumerableType);
+
+ throw new ArgumentException(message, argumentName);
+ }
+
+ return itemType;
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryable.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryable.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryable.cs
new file mode 100644
index 0000000..2db5399
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryable.cs
@@ -0,0 +1,40 @@
+/*
+ * 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.Linq.Impl
+{
+ using System.Linq;
+ using System.Linq.Expressions;
+ using Apache.Ignite.Core.Cache;
+
+ /// <summary>
+ /// Fields <see cref="IQueryable{T}"/> implementation for <see cref="ICache{TK,TV}"/>.
+ /// </summary>
+ internal class CacheFieldsQueryable<T> : CacheQueryableBase<T>
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CacheQueryable{TKey, TValue}"/> class.
+ /// </summary>
+ /// <param name="provider">The provider used to execute the query represented by this queryable
+ /// and to construct new queries.</param>
+ /// <param name="expression">The expression representing the query.</param>
+ public CacheFieldsQueryable(IQueryProvider provider, Expression expression) : base(provider, expression)
+ {
+ // No-op.
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs
new file mode 100644
index 0000000..eaca07a
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs
@@ -0,0 +1,506 @@
+/*
+ * 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.Text;
+
+namespace Apache.Ignite.Linq.Impl
+{
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Linq;
+ using System.Linq.Expressions;
+ using System.Reflection;
+ using Apache.Ignite.Core.Cache;
+ using Apache.Ignite.Core.Cache.Configuration;
+ using Apache.Ignite.Core.Impl.Common;
+ using Remotion.Linq.Clauses;
+ using Remotion.Linq.Clauses.Expressions;
+ using Remotion.Linq.Clauses.ResultOperators;
+ using Remotion.Linq.Parsing;
+
+ /// <summary>
+ /// Expression visitor, transforms query subexpressions (such as Where clauses) to SQL.
+ /// </summary>
+ internal class CacheQueryExpressionVisitor : ThrowingExpressionVisitor
+ {
+ /** */
+ private readonly bool _useStar;
+
+ /** */
+ private readonly CacheQueryModelVisitor _modelVisitor;
+
+ /** */
+ private static readonly CopyOnWriteConcurrentDictionary<MemberInfo, string> FieldNameMap =
+ new CopyOnWriteConcurrentDictionary<MemberInfo, string>();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CacheQueryExpressionVisitor" /> class.
+ /// </summary>
+ /// <param name="modelVisitor">The _model visitor.</param>
+ /// <param name="useStar">Flag indicating that star '*' qualifier should be used
+ /// for the whole-table select instead of _key, _val.</param>
+ public CacheQueryExpressionVisitor(CacheQueryModelVisitor modelVisitor, bool useStar)
+ {
+ Debug.Assert(modelVisitor != null);
+
+ _modelVisitor = modelVisitor;
+ _useStar = useStar;
+ }
+
+ /// <summary>
+ /// Gets the result builder.
+ /// </summary>
+ public StringBuilder ResultBuilder
+ {
+ get { return _modelVisitor.Builder; }
+ }
+
+ /// <summary>
+ /// Gets the parameters.
+ /// </summary>
+ public IList<object> Parameters
+ {
+ get { return _modelVisitor.Parameters; }
+ }
+
+ /// <summary>
+ /// Gets the aliases.
+ /// </summary>
+ private AliasDictionary Aliases
+ {
+ get { return _modelVisitor.Aliases; }
+ }
+
+ /** <inheritdoc /> */
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0")]
+ protected override Expression VisitUnary(UnaryExpression expression)
+ {
+ ResultBuilder.Append("(");
+
+ switch (expression.NodeType)
+ {
+ case ExpressionType.Negate:
+ ResultBuilder.Append("-");
+ break;
+ case ExpressionType.Not:
+ ResultBuilder.Append("not ");
+ break;
+ case ExpressionType.Convert:
+ // Ignore, let the db do the conversion
+ break;
+ default:
+ return base.VisitUnary(expression);
+ }
+
+ Visit(expression.Operand);
+
+ ResultBuilder.Append(")");
+
+ return expression;
+ }
+
+ /// <summary>
+ /// Visits the binary function.
+ /// </summary>
+ /// <param name="expression">The expression.</param>
+ /// <returns>True if function detected, otherwise false.</returns>
+ private bool VisitBinaryFunc(BinaryExpression expression)
+ {
+ if (expression.NodeType == ExpressionType.Add && expression.Left.Type == typeof (string))
+ ResultBuilder.Append("concat(");
+ else if (expression.NodeType == ExpressionType.Coalesce)
+ ResultBuilder.Append("coalesce(");
+ else
+ return false;
+
+ Visit(expression.Left);
+ ResultBuilder.Append(", ");
+ Visit(expression.Right);
+ ResultBuilder.Append(")");
+
+ return true;
+ }
+
+ /** <inheritdoc /> */
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
+ protected override Expression VisitBinary(BinaryExpression expression)
+ {
+ // Either func or operator
+ if (VisitBinaryFunc(expression))
+ return expression;
+
+ ResultBuilder.Append("(");
+
+ Visit(expression.Left);
+
+ switch (expression.NodeType)
+ {
+ case ExpressionType.Equal:
+ {
+ var rightConst = expression.Right as ConstantExpression;
+
+ if (rightConst != null && rightConst.Value == null)
+ {
+ // Special case for nulls, since "= null" does not work in SQL
+ ResultBuilder.Append(" is null)");
+ return expression;
+ }
+
+ ResultBuilder.Append(" = ");
+ break;
+ }
+
+ case ExpressionType.NotEqual:
+ {
+ var rightConst = expression.Right as ConstantExpression;
+
+ if (rightConst != null && rightConst.Value == null)
+ {
+ // Special case for nulls, since "<> null" does not work in SQL
+ ResultBuilder.Append(" is not null)");
+ return expression;
+ }
+
+ ResultBuilder.Append(" <> ");
+ break;
+ }
+
+ case ExpressionType.AndAlso:
+ case ExpressionType.And:
+ ResultBuilder.Append(" and ");
+ break;
+
+ case ExpressionType.OrElse:
+ case ExpressionType.Or:
+ ResultBuilder.Append(" or ");
+ break;
+
+ case ExpressionType.Add:
+ ResultBuilder.Append(" + ");
+ break;
+
+ case ExpressionType.Subtract:
+ ResultBuilder.Append(" - ");
+ break;
+
+ case ExpressionType.Multiply:
+ ResultBuilder.Append(" * ");
+ break;
+
+ case ExpressionType.Modulo:
+ ResultBuilder.Append(" % ");
+ break;
+
+ case ExpressionType.Divide:
+ ResultBuilder.Append(" / ");
+ break;
+
+ case ExpressionType.GreaterThan:
+ ResultBuilder.Append(" > ");
+ break;
+
+ case ExpressionType.GreaterThanOrEqual:
+ ResultBuilder.Append(" >= ");
+ break;
+
+ case ExpressionType.LessThan:
+ ResultBuilder.Append(" < ");
+ break;
+
+ case ExpressionType.LessThanOrEqual:
+ ResultBuilder.Append(" <= ");
+ break;
+
+ case ExpressionType.Coalesce:
+ break;
+
+ default:
+ base.VisitBinary(expression);
+ break;
+ }
+
+ Visit(expression.Right);
+ ResultBuilder.Append(")");
+
+ return expression;
+ }
+
+ /** <inheritdoc /> */
+ protected override Expression VisitQuerySourceReference(QuerySourceReferenceExpression expression)
+ {
+ // Count, sum, max, min expect a single field or *
+ // In other cases we need both parts of cache entry
+ var format = _useStar ? "{0}.*" : "{0}._key, {0}._val";
+
+ var tableName = Aliases.GetTableAlias(ExpressionWalker.GetCacheQueryable(expression));
+
+ ResultBuilder.AppendFormat(format, tableName);
+
+ return expression;
+ }
+
+ /** <inheritdoc /> */
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
+ protected override Expression VisitMember(MemberExpression expression)
+ {
+ // Field hierarchy is flattened (Person.Address.Street is just Street), append as is, do not call Visit.
+
+ // Special case: string.Length
+ if (expression.Member == MethodVisitor.StringLength)
+ {
+ ResultBuilder.Append("length(");
+
+ VisitMember((MemberExpression) expression.Expression);
+
+ ResultBuilder.Append(")");
+
+ return expression;
+ }
+
+ // Special case: grouping
+ if (VisitGroupByMember(expression.Expression))
+ return expression;
+
+ var queryable = ExpressionWalker.GetCacheQueryable(expression, false);
+
+ if (queryable != null)
+ {
+ var fieldName = GetFieldName(expression, queryable);
+
+ ResultBuilder.AppendFormat("{0}.{1}", Aliases.GetTableAlias(queryable), fieldName);
+ }
+ else
+ AppendParameter(RegisterEvaluatedParameter(expression));
+
+ return expression;
+ }
+
+ /// <summary>
+ /// Registers query parameter that is evaluated from a lambda expression argument.
+ /// </summary>
+ public object RegisterEvaluatedParameter(Expression expression)
+ {
+ _modelVisitor.ParameterExpressions.Add(expression);
+
+ return ExpressionWalker.EvaluateExpression<object>(expression);
+ }
+
+ /// <summary>
+ /// Gets the name of the field from a member expression.
+ /// </summary>
+ private static string GetFieldName(MemberExpression expression, ICacheQueryableInternal queryable)
+ {
+ var fieldName = GetMemberFieldName(expression.Member);
+
+ // Look for a field alias
+ var cacheCfg = queryable.CacheConfiguration;
+
+ if (cacheCfg.QueryEntities == null || cacheCfg.QueryEntities.All(x => x.Aliases == null))
+ return fieldName; // There are no aliases defined - early exit
+
+ // Find query entity by key-val types
+ var keyValTypes = queryable.ElementType.GetGenericArguments();
+
+ Debug.Assert(keyValTypes.Length == 2);
+
+ var entity = cacheCfg.QueryEntities.FirstOrDefault(e =>
+ e.Aliases != null &&
+ (e.KeyType == keyValTypes[0] || e.KeyTypeName == keyValTypes[0].Name) &&
+ (e.ValueType == keyValTypes[1] || e.ValueTypeName == keyValTypes[1].Name));
+
+ if (entity == null)
+ return fieldName;
+
+ // There are some aliases for the current query type
+ // Calculate full field name and look for alias
+ var fullFieldName = fieldName;
+ var member = expression;
+
+ while ((member = member.Expression as MemberExpression) != null &&
+ member.Member.DeclaringType != queryable.ElementType)
+ fullFieldName = GetFieldName(member, queryable) + "." + fullFieldName;
+
+ var alias = entity.Aliases.Where(x => x.FullName == fullFieldName)
+ .Select(x => x.Alias).FirstOrDefault();
+
+ return alias ?? fieldName;
+ }
+
+ /// <summary>
+ /// Gets the name of the member field.
+ /// </summary>
+ [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase",
+ Justification = "Not applicable.")]
+ private static string GetMemberFieldName(MemberInfo member)
+ {
+ string fieldName;
+
+ if (FieldNameMap.TryGetValue(member, out fieldName))
+ return fieldName;
+
+ return FieldNameMap.GetOrAdd(member, m =>
+ {
+ // Special case: _key, _val
+ if (m.DeclaringType != null &&
+ m.DeclaringType.IsGenericType &&
+ m.DeclaringType.GetGenericTypeDefinition() == typeof (ICacheEntry<,>))
+ return "_" + m.Name.ToLowerInvariant().Substring(0, 3);
+
+ var qryFieldAttr = m.GetCustomAttributes(true)
+ .OfType<QuerySqlFieldAttribute>().FirstOrDefault();
+
+ return qryFieldAttr == null || string.IsNullOrEmpty(qryFieldAttr.Name)
+ ? m.Name
+ : qryFieldAttr.Name;
+ });
+ }
+
+ /// <summary>
+ /// Visits the group by member.
+ /// </summary>
+ private bool VisitGroupByMember(Expression expression)
+ {
+ var srcRef = expression as QuerySourceReferenceExpression;
+ if (srcRef == null)
+ return false;
+
+ var from = srcRef.ReferencedQuerySource as IFromClause;
+ if (from == null)
+ return false;
+
+ var subQry = from.FromExpression as SubQueryExpression;
+ if (subQry == null)
+ return false;
+
+ var grpBy = subQry.QueryModel.ResultOperators.OfType<GroupResultOperator>().FirstOrDefault();
+ if (grpBy == null)
+ return false;
+
+ Visit(grpBy.KeySelector);
+
+ return true;
+ }
+
+ /** <inheritdoc /> */
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
+ protected override Expression VisitConstant(ConstantExpression expression)
+ {
+ AppendParameter(expression.Value);
+
+ return expression;
+ }
+
+ /// <summary>
+ /// Appends the parameter.
+ /// </summary>
+ private void AppendParameter(object value)
+ {
+ ResultBuilder.Append("?");
+
+ _modelVisitor.Parameters.Add(value);
+ }
+
+ /** <inheritdoc /> */
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
+ protected override Expression VisitMethodCall(MethodCallExpression expression)
+ {
+ MethodVisitor.VisitMethodCall(expression, this);
+
+ return expression;
+ }
+
+ /** <inheritdoc /> */
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
+ protected override Expression VisitNew(NewExpression expression)
+ {
+ VisitArguments(expression.Arguments);
+
+ return expression;
+ }
+
+ /** <inheritdoc /> */
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
+ protected override Expression VisitInvocation(InvocationExpression expression)
+ {
+ VisitArguments(expression.Arguments);
+
+ return expression;
+ }
+
+ /** <inheritdoc /> */
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
+ protected override Expression VisitConditional(ConditionalExpression expression)
+ {
+ ResultBuilder.Append("casewhen(");
+
+ Visit(expression.Test);
+
+ // Explicit type specification is required when all arguments of CASEWHEN are parameters
+ ResultBuilder.Append(", cast(");
+ Visit(expression.IfTrue);
+ ResultBuilder.AppendFormat(" as {0}), ", SqlTypes.GetSqlTypeName(expression.Type) ?? "other");
+
+ Visit(expression.IfFalse);
+ ResultBuilder.Append(")");
+
+ return expression;
+ }
+
+ /** <inheritdoc /> */
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
+ protected override Expression VisitSubQuery(SubQueryExpression expression)
+ {
+ // This happens when New expression uses a subquery, in a GroupBy.
+ _modelVisitor.VisitSelectors(expression.QueryModel, false);
+
+ return expression;
+ }
+
+ /** <inheritdoc /> */
+ protected override Exception CreateUnhandledItemException<T>(T unhandledItem, string visitMethod)
+ {
+ return new NotSupportedException(string.Format("The expression '{0}' (type: {1}) is not supported.",
+ unhandledItem, typeof (T)));
+ }
+
+ /// <summary>
+ /// Visits multiple arguments.
+ /// </summary>
+ /// <param name="arguments">The arguments.</param>
+ private void VisitArguments(IEnumerable<Expression> arguments)
+ {
+ var first = true;
+
+ foreach (var e in arguments)
+ {
+ if (!first)
+ {
+ if (_useStar)
+ throw new NotSupportedException("Aggregate functions do not support multiple fields");
+
+ ResultBuilder.Append(", ");
+ }
+
+ first = false;
+
+ Visit(e);
+ }
+ }
+ }
+}