The BatchInsert command is now capable of generating a DbCommand which INSERTs INTO a table from a SELECT statement defined by a Linq query.
However, the current solution does not allow to assign columns of the inserted table to columns of the SELECT statement – the columns are mapping in order of the table definition and query definition.
An additional List<> and a column assignment method is necessary:
public class BatchInsert<T, S> { List<Tuple<LambdaExpression, LambdaExpression>> setClauses = new List<Tuple<LambdaExpression, LambdaExpression>>(); public BatchInsert<T, S> Set<P>(Expression<Func<T, P>> columnClause, Expression<Func<S, P>> valueClause) { setClauses.Add(new Tuple<LambdaExpression, LambdaExpression>( columnClause, valueClause)); return this; }
We also need to extend the ToMSSqlString() method to handle the new collection:
public string ToMSSqlString() { var attr = typeof(T).GetCustomAttributes( typeof(global::System.Data.Linq.Mapping.TableAttribute), true); var sb = new StringBuilder(); sb.AppendLine("INSERT INTO " + ((System.Data.Linq.Mapping.TableAttribute)attr[0]).Name); var sep = " ("; if (insertClauses.Count > 0) { foreach (var set in insertClauses) { sb.Append(sep + set.ToMSSqlString()); sep = ", "; } sb.AppendLine(")"); } else if (setClauses.Count > 0) { foreach (var set in setClauses) { sb.Append(sep + set.Item1.ToMSSqlString()); sep = ", "; } sb.AppendLine(")"); } return sb.ToString(); }
The ToDbCommand() method needs to be extended to SELECT the columns from the query in the order defined by the setClauses collection:
public DbCommand ToDbCommand(System.Data.Linq.DataContext context) { var command = context.GetCommand(source); //var command = context.Connection.CreateCommand(); var sb = new StringBuilder(); sb.Append(this.ToMSSqlString()); if (insertClauses.Count > 0) sb.AppendLine(command.CommandText); else if (setClauses.Count > 0) { var sep = "SELECT "; foreach (var set in setClauses) { sb.Append(sep + set.Item2.ToMSSqlString()); sep = ", "; } sb.AppendLine(); sb.AppendLine("FROM ("); sb.AppendLine(command.CommandText); sb.AppendLine(") AS T"); } else throw new ArgumentException("no columns defined"); command.CommandText = sb.ToString(); return command; }
Using the FooMax table for some examples:
Creating a BatchInsert command without column mapping
var database = new DataContext(); var id = Guid.NewGuid().ToString(); var query = database.FooMaxes.Where(fm => fm.OID == 2) .Select(fm => new { newID = fm.ID + " " + id, newText = fm.SomeText + " upd" }); var insert = BatchInsert<FooMax>.From(query) .Insert(fm => fm.ID) .Insert(fm => fm.SomeText); var cmd = insert.ToDbCommand(database); Console.WriteLine(cmd.CommandText); foreach (System.Data.Common.DbParameter p in cmd.Parameters) Console.WriteLine(p.ParameterName + ": " + p.Value.ToString()); Console.WriteLine();
results in:
INSERT INTO dbo.FooMax ([ID], [SomeText]) SELECT ([t0].[ID] + @p1) + @p2 AS [newID], [t0].[SomeText] + @p3 AS [newText] FROM [dbo].[FooMax] AS [t0] WHERE [t0].[OID] = @p0 @p0: 2 @p1: @p2: 55125f61-d96a-4ef8-8185-9419ffa2a80b @p3: upd
Creating a BatchInsert command with column mapping
var insert = BatchInsert<FooMax>.From(query) .Set(fm => fm.SomeText, q => q.newText) .Set(fm => fm.ID, q => q.newID); cmd = insert.ToDbCommand(database); Console.WriteLine(cmd.CommandText); foreach (System.Data.Common.DbParameter p in cmd.Parameters) Console.WriteLine(p.ParameterName + ": " + p.Value.ToString()); Console.WriteLine(); var inserted = database.Execute(insert);
results in:
INSERT INTO dbo.FooMax ([SomeText], [ID]) SELECT [newText], [newID] FROM ( SELECT ([t0].[ID] + @p1) + @p2 AS [newID], [t0].[SomeText] + @p3 AS [newText] FROM [dbo].[FooMax] AS [t0] WHERE [t0].[OID] = @p0 ) AS T @p0: 2 @p1: @p2: 55125f61-d96a-4ef8-8185-9419ffa2a80b @p3: upd
Pingback: Series: Batch Insert and Batch Update using Linq To SQL « devioblog