diff --git a/Framework.sln b/Framework.sln
index 29d006e..78d6321 100644
--- a/Framework.sln
+++ b/Framework.sln
@@ -41,6 +41,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connected.Validation", "src
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connected.Expressions", "src\Connected.Expressions\Connected.Expressions.csproj", "{E597528B-95CA-4ACB-A44B-2C0737084581}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Connected.Data.Tests", "src\Connected.Data.Tests\Connected.Data.Tests.csproj", "{C6520F60-774E-41B1-8B17-7EF0B825BF6A}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{D6F579A8-2BDD-4E47-BF39-FB8650E5039E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Connected.Expressions.Tests", "src\Connected.Expressions.Tests\Connected.Expressions.Tests.csproj", "{9D4ED240-112A-467D-BCD7-2989DD172B83}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -123,10 +129,22 @@ Global
{E597528B-95CA-4ACB-A44B-2C0737084581}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E597528B-95CA-4ACB-A44B-2C0737084581}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E597528B-95CA-4ACB-A44B-2C0737084581}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C6520F60-774E-41B1-8B17-7EF0B825BF6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C6520F60-774E-41B1-8B17-7EF0B825BF6A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C6520F60-774E-41B1-8B17-7EF0B825BF6A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C6520F60-774E-41B1-8B17-7EF0B825BF6A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9D4ED240-112A-467D-BCD7-2989DD172B83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9D4ED240-112A-467D-BCD7-2989DD172B83}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9D4ED240-112A-467D-BCD7-2989DD172B83}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9D4ED240-112A-467D-BCD7-2989DD172B83}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {C6520F60-774E-41B1-8B17-7EF0B825BF6A} = {D6F579A8-2BDD-4E47-BF39-FB8650E5039E}
+ {9D4ED240-112A-467D-BCD7-2989DD172B83} = {D6F579A8-2BDD-4E47-BF39-FB8650E5039E}
+ EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C63B5A75-C99A-45D7-A691-26E568A51740}
EndGlobalSection
diff --git a/src/Connected.Data.Tests/Connected.Data.Tests.csproj b/src/Connected.Data.Tests/Connected.Data.Tests.csproj
new file mode 100644
index 0000000..fb7938d
--- /dev/null
+++ b/src/Connected.Data.Tests/Connected.Data.Tests.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net7.0
+ enable
+ enable
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Connected.Data.Tests/Translation/TranslatorTests.cs b/src/Connected.Data.Tests/Translation/TranslatorTests.cs
new file mode 100644
index 0000000..bfa4e26
--- /dev/null
+++ b/src/Connected.Data.Tests/Translation/TranslatorTests.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Connected.Expressions;
+using Connected.Data.Sql;
+using Connected.Expressions.Translation;
+using System.Linq.Expressions;
+using Connected.Data.Storage;
+using System.Collections;
+using System.Reflection;
+using Connected.Entities;
+
+namespace Connected.Data.Tests.Translation;
+
+[TestClass]
+public class TranslatorTests
+{
+ [TestMethod]
+ public void Translator_GeneratesBasicSelectExpression_UsingTSQL()
+ {
+ var context = new ExpressionCompilationContext(new TSqlLanguage());
+
+ var translator = new Translator(context);
+
+ var data = SampleData.All();
+
+ var dataQueryable = data.AsQueryable();
+
+ var query = (from x in dataQueryable select x);
+
+ var operatorMethodInfo = QueryableMethods.SingleOrDefaultWithoutPredicate;
+
+ if (operatorMethodInfo.IsGenericMethod)
+ {
+ operatorMethodInfo = operatorMethodInfo.GetGenericArguments().Length == 2
+ ? operatorMethodInfo.MakeGenericMethod(typeof(DummyEntity), typeof(DummyEntity).GetGenericArguments().Single())
+ : operatorMethodInfo.MakeGenericMethod(typeof(DummyEntity));
+ }
+
+ var expr = Expression.Call(instance: null, method: operatorMethodInfo, arguments: new[] { query.Expression });
+
+ var result = translator.Translate(expr) as ProjectionExpression;
+
+ var select = result.Select;
+
+ Assert.IsInstanceOfType(select, typeof(SelectExpression));
+ Assert.IsInstanceOfType(select.From, typeof(TableExpression));
+ Assert.IsNull(select.Where, "Where should be null");
+ Assert.IsNull(select.Take, "Take should be null");
+ Assert.IsNull(select.Skip, "Skip should be null");
+ Assert.AreEqual(0, select.GroupBy.Count);
+ Assert.IsFalse(select.IsReverse);
+ }
+
+ [TestMethod]
+ public void Translator_GeneratesSelectExpression_WithWhere_UsingTSQL()
+ {
+ var context = new ExpressionCompilationContext(new TSqlLanguage());
+
+ var translator = new Translator(context);
+
+ var data = SampleData.All();
+
+ var dataQueryable = data.AsQueryable();
+
+ var query = (from x in dataQueryable where x.Id == 1 select x);
+
+ var operatorMethodInfo = QueryableMethods.SingleOrDefaultWithoutPredicate;
+
+ if (operatorMethodInfo.IsGenericMethod)
+ {
+ operatorMethodInfo = operatorMethodInfo.GetGenericArguments().Length == 2
+ ? operatorMethodInfo.MakeGenericMethod(typeof(DummyEntity), typeof(DummyEntity).GetGenericArguments().Single())
+ : operatorMethodInfo.MakeGenericMethod(typeof(DummyEntity));
+ }
+
+ var expr = Expression.Call(instance: null, method: operatorMethodInfo, arguments: new[] { query.Expression });
+
+ var result = translator.Translate(expr) as ProjectionExpression;
+
+ var select = result.Select;
+
+ Assert.IsInstanceOfType(select, typeof(SelectExpression));
+ Assert.IsInstanceOfType(select.From, typeof(TableExpression));
+ Assert.IsNotNull(select.Where, "Where should not be null");
+ Assert.IsNull(select.Take, "Take should be null");
+ Assert.IsNull(select.Skip, "Skip should be null");
+ Assert.AreEqual(0, select.GroupBy.Count);
+ Assert.IsFalse(select.IsReverse);
+ }
+}
+
+public record DummyEntity : Entity;
+
+internal static class SampleData
+{
+ public static List All()
+ {
+ return new List
+ {
+ new DummyEntity{ Id = 1},
+ new DummyEntity{ Id = 2},
+ new DummyEntity{ Id = 3},
+ new DummyEntity{ Id = 4},
+ new DummyEntity{ Id = 5}
+ };
+ }
+}
+
+internal static class QueryableMethods
+{
+ static QueryableMethods()
+ {
+ var queryableMethodGroups = typeof(Queryable)
+ .GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)
+ .GroupBy(mi => mi.Name)
+ .ToDictionary(e => e.Key, l => l.ToList());
+
+ SingleWithoutPredicate = GetMethod(nameof(Queryable.Single), 1, types => new[] { typeof(IQueryable<>).MakeGenericType(types[0]) });
+ SingleOrDefaultWithoutPredicate = GetMethod(nameof(Queryable.SingleOrDefault), 1, types => new[] { typeof(IQueryable<>).MakeGenericType(types[0]) });
+
+ MethodInfo GetMethod(string name, int genericParameterCount, Func parameterGenerator)
+ {
+ return queryableMethodGroups[name].Single(mi => ((genericParameterCount == 0 && !mi.IsGenericMethod)
+ || (mi.IsGenericMethod && mi.GetGenericArguments().Length == genericParameterCount))
+ && mi.GetParameters().Select(e => e.ParameterType).SequenceEqual(parameterGenerator(mi.IsGenericMethod ? mi.GetGenericArguments() : Array.Empty())));
+ }
+ }
+
+ public static MethodInfo SingleWithoutPredicate { get; }
+ public static MethodInfo SingleOrDefaultWithoutPredicate { get; }
+}
diff --git a/src/Connected.Data.Tests/Usings.cs b/src/Connected.Data.Tests/Usings.cs
new file mode 100644
index 0000000..ab67c7e
--- /dev/null
+++ b/src/Connected.Data.Tests/Usings.cs
@@ -0,0 +1 @@
+global using Microsoft.VisualStudio.TestTools.UnitTesting;
\ No newline at end of file