From f7f58c3684b663f31451d86b2849be34896a3fd9 Mon Sep 17 00:00:00 2001 From: grandua Date: Fri, 5 Jul 2013 17:27:47 -0400 Subject: [PATCH 1/4] Support for SQL INSERT INTO and SELECT ... INSERT - moving data between databases without bringing it into RAM. -Contributed by Agile Design LLC ( http://agiledesignllc.com/ ) Signed-off-by: grandua --- .../EntityFramework.Extended.Test.csproj | 1 + .../InsertSqlGenerationTests.cs | 69 ++++++++++++++ .../EntityFramework.Extended.csproj | 1 + .../Extensions/QueryableExtensions.cs | 95 +++++++++++++++++++ 4 files changed, 166 insertions(+) create mode 100644 Source/EntityFramework.Extended.Test/InsertSqlGenerationTests.cs create mode 100644 Source/EntityFramework.Extended/Extensions/QueryableExtensions.cs diff --git a/Source/EntityFramework.Extended.Test/EntityFramework.Extended.Test.csproj b/Source/EntityFramework.Extended.Test/EntityFramework.Extended.Test.csproj index 33cb4b9..4f119da 100644 --- a/Source/EntityFramework.Extended.Test/EntityFramework.Extended.Test.csproj +++ b/Source/EntityFramework.Extended.Test/EntityFramework.Extended.Test.csproj @@ -80,6 +80,7 @@ + diff --git a/Source/EntityFramework.Extended.Test/InsertSqlGenerationTests.cs b/Source/EntityFramework.Extended.Test/InsertSqlGenerationTests.cs new file mode 100644 index 0000000..8e5ae4e --- /dev/null +++ b/Source/EntityFramework.Extended.Test/InsertSqlGenerationTests.cs @@ -0,0 +1,69 @@ +using System; +using System.Data.Entity; +using System.Linq; +using EntityFramework.Extensions; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace EntityFramework.Test +{ + [TestClass] + public class InsertSqlGenerationTests + { + private IQueryable linqQuery; + + [TestInitialize] + public void TestFixtureSetUp() + { + Database.SetInitializer(null); + + linqQuery = new Repository().GetAll() + .Select(person => person); + } + + [TestMethod] + public void SelectIntoTempTableFromLinq() + { + string insertSql = linqQuery.SelectInsertSql("#tmp"); + Console.WriteLine(insertSql); + StringAssert.Contains(insertSql, " INTO #tmp FROM "); + } + + [TestMethod] + public void InsertSelectFromLinq() + { + string insertSql = linqQuery.InsertIntoSql("TableNameToInsert", "Id, FirstName, LastName"); + Console.WriteLine(insertSql); + StringAssert.Contains(insertSql, "INSERT INTO TableNameToInsert (Id, FirstName, LastName)\r\nSELECT"); + + Console.WriteLine(); + string insertSqlSimple = linqQuery.InsertIntoSql("TableNameToInsert"); + Console.WriteLine(insertSqlSimple); + StringAssert.Contains(insertSqlSimple, "INSERT INTO TableNameToInsert \r\nSELECT"); + } + } + + public class Person + { + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + } + + public class Repository + where TAggregateRoot : class + { + private DbContext _dbContext = new TestContext(); + + public IQueryable GetAll() + { + return _dbContext.Set(); + } + } + + public class TestContext + : DbContext + { + public DbSet Persons { get; set; } + } + +} diff --git a/Source/EntityFramework.Extended/EntityFramework.Extended.csproj b/Source/EntityFramework.Extended/EntityFramework.Extended.csproj index 5d35fef..47f43df 100644 --- a/Source/EntityFramework.Extended/EntityFramework.Extended.csproj +++ b/Source/EntityFramework.Extended/EntityFramework.Extended.csproj @@ -86,6 +86,7 @@ + diff --git a/Source/EntityFramework.Extended/Extensions/QueryableExtensions.cs b/Source/EntityFramework.Extended/Extensions/QueryableExtensions.cs new file mode 100644 index 0000000..8e6b77d --- /dev/null +++ b/Source/EntityFramework.Extended/Extensions/QueryableExtensions.cs @@ -0,0 +1,95 @@ +using System.Data; +using System.Linq; +using System.Text.RegularExpressions; + +namespace EntityFramework.Extensions +{ + /// + /// Extension methods for IQueryable to support INSERT ... and SELECT INSERT ... SQL statements without loading objects into RAM. + /// Contributed by Agile Design LLC ( http://agiledesignllc.com/ ). + /// + public static class QueryableExtensions + { + private const string FromTableSqlExpression = @"\bFROM\b"; + + /// + /// Captures SELECT ... FROM ... SQL from IQueryable, converts it into SELECT ... INTO ... T-SQL and executes it on sqlCommand.
+ /// No objects are being brought into RAM / Context.
+ /// Only MS SQL Server and Sybase T-SQL RDBMS are supported. + /// Contributed by Agile Design LLC ( http://agiledesignllc.com/ ). + ///
+ /// Entity type + /// Executes generated T-SQL + /// Target table name to insert into + /// DbSet of entities (or any IQueryable that returns SQL from its ToString() implementation + public static void SelectInsert(this IQueryable source, string tableName, IDbCommand sqlCommand) + { + sqlCommand.CommandText = source.SelectInsertSql(tableName); + sqlCommand.ExecuteNonQuery(); + } + + /// + /// Captures SELECT ... FROM ... SQL from IQueryable, converts it into SELECT ... INTO ... T-SQL and executes it on sqlCommand.
+ /// No objects are being brought into RAM / Context.
+ /// Only MS SQL Server and Sybase T-SQL RDBMS are supported. + /// Contributed by Agile Design LLC ( http://agiledesignllc.com/ ). + ///
+ /// Entity type + /// Executes generated T-SQL + /// Target table name to insert into + /// DbSet of entities (or any IQueryable that returns SQL from its ToString() implementation + public static void SelectInsert(this IDbCommand sqlCommand, string tableName, IQueryable source) + { + source.SelectInsert(tableName, sqlCommand); + } + + public static void SelectInsert(this IDbConnection sqlConnection, string tableName, IQueryable source) + { + source.SelectInsert(tableName, sqlConnection.CreateCommand()); + } + + /// + /// Captures SELECT ... FROM ... SQL from IQueryable and converts it into SELECT ... INTO ... T-SQL.
+ /// No objects are being brought into RAM / Context.
+ /// Only MS SQL Server and Sybase T-SQL RDBMS are supported. + /// Contributed by Agile Design LLC ( http://agiledesignllc.com/ ). + ///
+ /// Entity type + /// DbSet of entities (or any IQueryable that returns SQL from its ToString() implementation + /// Target table name to insert into + /// SELECT ... INSERT ... T-SQL statement + public static string SelectInsertSql(this IQueryable source, string tableName) + { + var regex = new Regex(FromTableSqlExpression); + string selectInsertSql = regex.Replace( + source.ToString() + , string.Format(" INTO {0} FROM ", tableName) + , 1); + + return selectInsertSql; + } + + /// + /// Captures SELECT ... FROM ... SQL from IQueryable and converts it into INSERT INTO ... SELECT FROM ... ANSI-SQL.
+ /// No objects are being brought into RAM / Context.
+ /// Contributed by Agile Design LLC ( http://agiledesignllc.com/ ). + ///
+ /// Entity type + /// DbSet of entities (or any IQueryable that returns SQL from its ToString() implementation + /// Target table name to insert into + /// Optional parameter for a list of columns to insert into + /// INSERT INTO ... SELECT FROM ANSI-SQL statement + public static string InsertIntoSql(this IQueryable source, string tableName, string columnList = "") + { + string originalSql = source.ToString(); + if (! string.IsNullOrWhiteSpace(columnList)) + { + columnList = string.Format("({0})", columnList); + } + return string.Format("INSERT INTO {0} {1}\r\n{2}" + , tableName + , columnList + , originalSql); + } + } +} \ No newline at end of file From 511c930bb27e39d31ae036743abec9cd7ac89690 Mon Sep 17 00:00:00 2001 From: grandua Date: Sat, 13 Jul 2013 21:41:14 -0400 Subject: [PATCH 2/4] SelectInsert extension method for IDbSet. Changed IDbCommand to IDbConnection as more universally available in extension methods that were based on IDbCommand. --- .../Extensions/QueryableExtensions.cs | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/Source/EntityFramework.Extended/Extensions/QueryableExtensions.cs b/Source/EntityFramework.Extended/Extensions/QueryableExtensions.cs index 8e6b77d..21e6134 100644 --- a/Source/EntityFramework.Extended/Extensions/QueryableExtensions.cs +++ b/Source/EntityFramework.Extended/Extensions/QueryableExtensions.cs @@ -1,4 +1,5 @@ using System.Data; +using System.Data.Entity; using System.Linq; using System.Text.RegularExpressions; @@ -12,6 +13,22 @@ public static class QueryableExtensions { private const string FromTableSqlExpression = @"\bFROM\b"; + /// + /// Captures SELECT ... FROM ... SQL from IDbSet, converts it into SELECT ... INTO ... T-SQL and executes it via context.ExecuteStoreCommand().
+ /// No objects are being brought into RAM / Context.
+ /// Only MS SQL Server and Sybase T-SQL RDBMS are supported. + /// Contributed by Agile Design LLC ( http://agiledesignllc.com/ ). + ///
+ /// Entity type + /// DbSet of entities + /// Target table name to insert into + public static void SelectInsert(this IDbSet source, string tableName) + where TEntity : class + { + source.GetContext() + .ExecuteStoreCommand(source.SelectInsertSql(tableName)); + } + /// /// Captures SELECT ... FROM ... SQL from IQueryable, converts it into SELECT ... INTO ... T-SQL and executes it on sqlCommand.
/// No objects are being brought into RAM / Context.
@@ -19,11 +36,13 @@ public static class QueryableExtensions /// Contributed by Agile Design LLC ( http://agiledesignllc.com/ ). ///
/// Entity type - /// Executes generated T-SQL /// Target table name to insert into /// DbSet of entities (or any IQueryable that returns SQL from its ToString() implementation - public static void SelectInsert(this IQueryable source, string tableName, IDbCommand sqlCommand) + /// Creates IDbCommand which executes generated T-SQL + public static void SelectInsert(this IQueryable source, string tableName, IDbConnection connection) + where TEntity : class { + var sqlCommand = connection.CreateCommand(); sqlCommand.CommandText = source.SelectInsertSql(tableName); sqlCommand.ExecuteNonQuery(); } @@ -35,17 +54,13 @@ public static void SelectInsert(this IQueryable source, string /// Contributed by Agile Design LLC ( http://agiledesignllc.com/ ). /// /// Entity type - /// Executes generated T-SQL + /// Creates IDbCommand which executes generated T-SQL /// Target table name to insert into /// DbSet of entities (or any IQueryable that returns SQL from its ToString() implementation - public static void SelectInsert(this IDbCommand sqlCommand, string tableName, IQueryable source) - { - source.SelectInsert(tableName, sqlCommand); - } - - public static void SelectInsert(this IDbConnection sqlConnection, string tableName, IQueryable source) + public static void SelectInsert(this IDbConnection sqlConnection, string tableName, IQueryable source) + where TEntity : class { - source.SelectInsert(tableName, sqlConnection.CreateCommand()); + source.SelectInsert(tableName, sqlConnection); } /// From 8c25f0e206286a192ff59502b019a7c998a36eac Mon Sep 17 00:00:00 2001 From: grandua Date: Sat, 13 Jul 2013 21:51:58 -0400 Subject: [PATCH 3/4] InsertInto extension method on IDbSet --- .../Extensions/QueryableExtensions.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Source/EntityFramework.Extended/Extensions/QueryableExtensions.cs b/Source/EntityFramework.Extended/Extensions/QueryableExtensions.cs index 21e6134..ebdad19 100644 --- a/Source/EntityFramework.Extended/Extensions/QueryableExtensions.cs +++ b/Source/EntityFramework.Extended/Extensions/QueryableExtensions.cs @@ -106,5 +106,22 @@ public static string InsertIntoSql(this IQueryable source, str , columnList , originalSql); } + + /// + /// Captures SELECT ... FROM ... SQL from IDbSet, converts it into INSERT INTO ... SELECT FROM ... ANSI-SQL + /// and executes it via context.ExecuteStoreCommand().
+ /// No objects are being brought into RAM / Context.
+ /// Contributed by Agile Design LLC ( http://agiledesignllc.com/ ). + ///
+ /// DbSet of entities + /// Target table name to insert into + /// Optional parameter for a list of columns to insert into + /// Entity type + public static void InsertInto(this IDbSet source, string tableName, string columnList = "") + where TEntity : class + { + source.GetContext() + .ExecuteStoreCommand(source.InsertIntoSql(tableName, columnList)); + } } } \ No newline at end of file From 7ccee73fa4c55ccf9d645a9d2c40a163c0b613c2 Mon Sep 17 00:00:00 2001 From: grandua Date: Sat, 13 Jul 2013 22:08:14 -0400 Subject: [PATCH 4/4] Used IQueryable instead of IDbSet for consistency. Moved extension methods only working with DbContext/Object context into ObjectQueryExtensions for consistency. --- .../Extensions/ObjectQueryExtensions.cs | 36 +++++++- .../Extensions/QueryableExtensions.cs | 71 +-------------- .../Tracker.SqlServer.CodeFirst/Entity.csp | 90 +++++++++---------- 3 files changed, 79 insertions(+), 118 deletions(-) diff --git a/Source/EntityFramework.Extended/Extensions/ObjectQueryExtensions.cs b/Source/EntityFramework.Extended/Extensions/ObjectQueryExtensions.cs index 69c1670..9d2de65 100644 --- a/Source/EntityFramework.Extended/Extensions/ObjectQueryExtensions.cs +++ b/Source/EntityFramework.Extended/Extensions/ObjectQueryExtensions.cs @@ -1,11 +1,9 @@ using System; -using System.Collections.Generic; -using System.Data.Common; +using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Data.Objects; using System.Linq; using System.Linq.Expressions; -using System.Text; using EntityFramework.Reflection; namespace EntityFramework.Extensions @@ -124,5 +122,37 @@ public static ObjectContext GetContext(this IQueryable query) return null; } + /// + /// Captures SELECT ... FROM ... SQL from IDbSet, converts it into SELECT ... INTO ... T-SQL and executes it via context.ExecuteStoreCommand().
+ /// No objects are being brought into RAM / Context.
+ /// Only MS SQL Server and Sybase T-SQL RDBMS are supported.
+ /// Contributed by Agile Design LLC ( http://agiledesignllc.com/ ). + ///
+ /// Entity type + /// DbSet of entities + /// Target table name to insert into + public static void SelectInsert(this IQueryable source, string tableName) + where TEntity : class + { + source.GetContext() + .ExecuteStoreCommand(source.SelectInsertSql(tableName)); + } + + /// + /// Captures SELECT ... FROM ... SQL from IDbSet, converts it into INSERT INTO ... SELECT FROM ... ANSI-SQL + /// and executes it via context.ExecuteStoreCommand().
+ /// No objects are being brought into RAM / Context.
+ /// Contributed by Agile Design LLC ( http://agiledesignllc.com/ ). + ///
+ /// DbSet of entities + /// Target table name to insert into + /// Optional parameter for a list of columns to insert into + /// Entity type + public static void InsertInto(this IQueryable source, string tableName, string columnList = "") + where TEntity : class + { + source.GetContext() + .ExecuteStoreCommand(source.InsertIntoSql(tableName, columnList)); + } } } diff --git a/Source/EntityFramework.Extended/Extensions/QueryableExtensions.cs b/Source/EntityFramework.Extended/Extensions/QueryableExtensions.cs index ebdad19..e753311 100644 --- a/Source/EntityFramework.Extended/Extensions/QueryableExtensions.cs +++ b/Source/EntityFramework.Extended/Extensions/QueryableExtensions.cs @@ -1,6 +1,4 @@ -using System.Data; -using System.Data.Entity; -using System.Linq; +using System.Linq; using System.Text.RegularExpressions; namespace EntityFramework.Extensions @@ -13,56 +11,6 @@ public static class QueryableExtensions { private const string FromTableSqlExpression = @"\bFROM\b"; - /// - /// Captures SELECT ... FROM ... SQL from IDbSet, converts it into SELECT ... INTO ... T-SQL and executes it via context.ExecuteStoreCommand().
- /// No objects are being brought into RAM / Context.
- /// Only MS SQL Server and Sybase T-SQL RDBMS are supported. - /// Contributed by Agile Design LLC ( http://agiledesignllc.com/ ). - ///
- /// Entity type - /// DbSet of entities - /// Target table name to insert into - public static void SelectInsert(this IDbSet source, string tableName) - where TEntity : class - { - source.GetContext() - .ExecuteStoreCommand(source.SelectInsertSql(tableName)); - } - - /// - /// Captures SELECT ... FROM ... SQL from IQueryable, converts it into SELECT ... INTO ... T-SQL and executes it on sqlCommand.
- /// No objects are being brought into RAM / Context.
- /// Only MS SQL Server and Sybase T-SQL RDBMS are supported. - /// Contributed by Agile Design LLC ( http://agiledesignllc.com/ ). - ///
- /// Entity type - /// Target table name to insert into - /// DbSet of entities (or any IQueryable that returns SQL from its ToString() implementation - /// Creates IDbCommand which executes generated T-SQL - public static void SelectInsert(this IQueryable source, string tableName, IDbConnection connection) - where TEntity : class - { - var sqlCommand = connection.CreateCommand(); - sqlCommand.CommandText = source.SelectInsertSql(tableName); - sqlCommand.ExecuteNonQuery(); - } - - /// - /// Captures SELECT ... FROM ... SQL from IQueryable, converts it into SELECT ... INTO ... T-SQL and executes it on sqlCommand.
- /// No objects are being brought into RAM / Context.
- /// Only MS SQL Server and Sybase T-SQL RDBMS are supported. - /// Contributed by Agile Design LLC ( http://agiledesignllc.com/ ). - ///
- /// Entity type - /// Creates IDbCommand which executes generated T-SQL - /// Target table name to insert into - /// DbSet of entities (or any IQueryable that returns SQL from its ToString() implementation - public static void SelectInsert(this IDbConnection sqlConnection, string tableName, IQueryable source) - where TEntity : class - { - source.SelectInsert(tableName, sqlConnection); - } - /// /// Captures SELECT ... FROM ... SQL from IQueryable and converts it into SELECT ... INTO ... T-SQL.
/// No objects are being brought into RAM / Context.
@@ -106,22 +54,5 @@ public static string InsertIntoSql(this IQueryable source, str , columnList , originalSql); } - - /// - /// Captures SELECT ... FROM ... SQL from IDbSet, converts it into INSERT INTO ... SELECT FROM ... ANSI-SQL - /// and executes it via context.ExecuteStoreCommand().
- /// No objects are being brought into RAM / Context.
- /// Contributed by Agile Design LLC ( http://agiledesignllc.com/ ). - ///
- /// DbSet of entities - /// Target table name to insert into - /// Optional parameter for a list of columns to insert into - /// Entity type - public static void InsertInto(this IDbSet source, string tableName, string columnList = "") - where TEntity : class - { - source.GetContext() - .ExecuteStoreCommand(source.InsertIntoSql(tableName, columnList)); - } } } \ No newline at end of file diff --git a/Source/Samples/net40/Tracker.SqlServer.CodeFirst/Entity.csp b/Source/Samples/net40/Tracker.SqlServer.CodeFirst/Entity.csp index bc67dae..25ec03f 100644 --- a/Source/Samples/net40/Tracker.SqlServer.CodeFirst/Entity.csp +++ b/Source/Samples/net40/Tracker.SqlServer.CodeFirst/Entity.csp @@ -1,46 +1,46 @@ - - - - - - - - .\ - .\Entities - .\Mapping - Singular - Singular - Plural - Plural - - - sysdiagrams$ - - - False - - - ^(sp|tbl|udf|vw)_ - - - False - False - .\Queries - By - GetBy - Key - False - .\Mocks - True - Tracker.SqlServer.CodeFirst - - $(TrackerConnectionString) - SchemaExplorer.SqlSchemaProvider,SchemaExplorer.SqlSchemaProvider - - Tracker.SqlServer.CodeFirst.Mapping - Tracker.SqlServer.CodeFirst.Entities - Tracker.SqlServer.CodeFirst.Queries - Tracker.SqlServer.CodeFirst.Mocks - - + + + + + + + + .\ + .\Entities + .\Mapping + Singular + Singular + Plural + Plural + + + sysdiagrams$ + + + False + + + ^(sp|tbl|udf|vw)_ + + + False + False + .\Queries + By + GetBy + Key + False + .\Mocks + True + Tracker.SqlServer.CodeFirst + + $(TrackerConnectionString) + SchemaExplorer.SqlSchemaProvider,SchemaExplorer.SqlSchemaProvider + + Tracker.SqlServer.CodeFirst.Mapping + Tracker.SqlServer.CodeFirst.Entities + Tracker.SqlServer.CodeFirst.Queries + Tracker.SqlServer.CodeFirst.Mocks + + \ No newline at end of file