5 // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne
\r
7 // Permission is hereby granted, free of charge, to any person obtaining a copy
\r
8 // of this software and associated documentation files (the "Software"), to deal
\r
9 // in the Software without restriction, including without limitation the rights
\r
10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
\r
11 // copies of the Software, and to permit persons to whom the Software is
\r
12 // furnished to do so, subject to the following conditions:
\r
14 // The above copyright notice and this permission notice shall be included in
\r
15 // all copies or substantial portions of the Software.
\r
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
\r
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
\r
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
\r
20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
\r
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
\r
22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
\r
28 using System.Collections.Generic;
\r
29 using System.Data.Linq.Mapping;
\r
30 using System.Linq.Expressions;
\r
31 using System.Reflection;
\r
34 using System.Data.Linq.Sql;
\r
35 using System.Data.Linq.Sugar.Expressions;
\r
37 using DbLinq.Data.Linq.Sql;
\r
38 using DbLinq.Data.Linq.Sugar.Expressions;
\r
44 namespace System.Data.Linq.Sugar.Implementation
\r
46 namespace DbLinq.Data.Linq.Sugar.Implementation
\r
49 partial class QueryBuilder
\r
51 protected class UpsertParameters
\r
53 public MetaTable Table;
\r
54 public readonly IList<ObjectInputParameterExpression> InputParameters = new List<ObjectInputParameterExpression>();
\r
55 public readonly IList<ObjectOutputParameterExpression> OutputParameters = new List<ObjectOutputParameterExpression>();
\r
56 public readonly IList<SqlStatement> InputColumns = new List<SqlStatement>();
\r
57 public readonly IList<SqlStatement> InputValues = new List<SqlStatement>();
\r
58 public readonly IList<SqlStatement> OutputValues = new List<SqlStatement>();
\r
59 public readonly IList<SqlStatement> OutputExpressions = new List<SqlStatement>();
\r
60 public readonly IList<SqlStatement> InputPKColumns = new List<SqlStatement>();
\r
61 public readonly IList<SqlStatement> InputPKValues = new List<SqlStatement>();
\r
65 // IsPrimaryKey = true, IsDbGenerated = true, CanBeNull = false, Expression = null
\r
66 // INSERT INTO main.Products (CategoryID, Discontinued, ProductName, QuantityPerUnit)
\r
67 // VALUES (@P1, @P2, @P3, @P4) ;SELECT last_insert_rowid()
\r
70 // IsPrimaryKey = true, IsDbGenerated = true, CanBeNull = false,
\r
71 // Expression = "next value for \"linquser\".\"products_seq\"")]
\r
72 // INSERT INTO linquser.products (categoryid, discontinued, productid, productname, quantityperunit)
\r
73 // VALUES ($param_000001_param$, $param_000002_param$,
\r
74 // next value for "linquser"."products_seq", $param_000004_param$, $param_000005_param$)
\r
77 // IsPrimaryKey = true, IsDbGenerated = true, CanBeNull = false, Expression = null
\r
79 // INSERT INTO NORTHWIND."Products" ("CategoryID", "Discontinued", "ProductID", "ProductName", "QuantityPerUnit")
\r
80 // VALUES (:P1, :P2, NORTHWIND."Products_SEQ".NextVal, :P4, :P5)
\r
81 // ;SELECT NORTHWIND."Products_SEQ".CurrVal INTO :P3 FROM DUAL; END;
\r
84 // IsPrimaryKey = true, IsDbGenerated = true, CanBeNull = false, Expression = "nextval('\"Products_ProductID_seq\"')"
\r
85 // INSERT INTO public."Products" ("CategoryID", "Discontinued", "ProductName", "QuantityPerUnit")
\r
86 // VALUES (:P1, :P2, :P3, :P4)
\r
87 // ;SELECT currval('"Products_ProductID_seq"')
\r
89 // SQL Server (bogus):
\r
90 // IsPrimaryKey = true, IsDbGenerated = true
\r
91 // INSERT INTO [dbo].[Products] (, , , ) VALUES (@P1, @P2, @P3, @P4)
\r
92 // ; SELECT @@IDENTITY
\r
94 // Column: default --> use value
\r
95 // PK: Expression !null --> use parameter (Oracle is wrong here)
\r
96 // Expression null --> ignore
\r
97 // SQL: wrap clause with PK information
\r
101 /// Creates a query for insertion
\r
103 /// <param name="objectToInsert"></param>
\r
104 /// <param name="queryContext"></param>
\r
105 /// <returns></returns>
\r
106 public UpsertQuery GetInsertQuery(object objectToInsert, QueryContext queryContext)
\r
109 var upsertParameters = GetUpsertParameters(objectToInsert, false, null, queryContext);
\r
110 var sqlProvider = queryContext.DataContext.Vendor.SqlProvider;
\r
111 var insertSql = sqlProvider.GetInsert(
\r
112 sqlProvider.GetTable(upsertParameters.Table.TableName),
\r
113 upsertParameters.InputColumns,
\r
114 upsertParameters.InputValues);
\r
115 var insertIdSql = sqlProvider.GetInsertIds(
\r
116 upsertParameters.OutputValues,
\r
117 upsertParameters.OutputExpressions);
\r
118 return new UpsertQuery(queryContext.DataContext, insertSql, insertIdSql, upsertParameters.InputParameters, upsertParameters.OutputParameters);
\r
121 protected enum ParameterType
\r
129 /// Gets values for insert/update
\r
131 /// <param name="objectToUpsert"></param>
\r
132 /// <param name="queryContext"></param>
\r
133 /// <param name="update"></param>
\r
134 /// <param name="modifiedMembers"></param>
\r
135 /// <returns></returns>
\r
136 protected virtual UpsertParameters GetUpsertParameters(object objectToUpsert, bool update, IList<MemberInfo> modifiedMembers, QueryContext queryContext)
\r
138 var rowType = objectToUpsert.GetType();
\r
139 var sqlProvider = queryContext.DataContext.Vendor.SqlProvider;
\r
140 var upsertParameters = new UpsertParameters
\r
142 Table = queryContext.DataContext.Mapping.GetTable(rowType)
\r
144 foreach (var dataMember in upsertParameters.Table.RowType.PersistentDataMembers)
\r
146 var column = sqlProvider.GetColumn(dataMember.MappedName);
\r
147 ParameterType type = GetParameterType(objectToUpsert, dataMember, update);
\r
148 var memberInfo = dataMember.Member;
\r
149 // if the column is generated AND not specified, we may have:
\r
150 // - an explicit generation (Expression property is not null, so we add the column)
\r
151 // - an implicit generation (Expression property is null
\r
152 // in all cases, we want to get the value back
\r
153 if (type == ParameterType.Output)
\r
155 if (dataMember.Expression != null)
\r
157 upsertParameters.InputColumns.Add(column);
\r
158 upsertParameters.InputValues.Add(dataMember.Expression);
\r
160 var setter = (Expression<Action<object, object>>)((o, v) => memberInfo.SetMemberValue(o, v));
\r
161 var outputParameter = new ObjectOutputParameterExpression(setter,
\r
162 memberInfo.GetMemberType(),
\r
164 upsertParameters.OutputParameters.Add(outputParameter);
\r
165 upsertParameters.OutputValues.Add(sqlProvider.GetParameterName(outputParameter.Alias));
\r
166 upsertParameters.OutputExpressions.Add(dataMember.Expression);
\r
168 else // standard column
\r
170 var getter = (Expression<Func<object, object>>)(o => memberInfo.GetMemberValue(o));
\r
171 var inputParameter = new ObjectInputParameterExpression(
\r
173 memberInfo.GetMemberType(), dataMember.Name);
\r
174 if (type == ParameterType.InputPK)
\r
176 upsertParameters.InputPKColumns.Add(column);
\r
177 upsertParameters.InputPKValues.Add(sqlProvider.GetParameterName(inputParameter.Alias));
\r
178 upsertParameters.InputParameters.Add(inputParameter);
\r
180 // for a standard column, we keep it only if modifiedMembers contains the specified memberInfo
\r
181 // caution: this makes the cache harder to maintain
\r
182 else if (modifiedMembers == null || modifiedMembers.Contains(memberInfo))
\r
184 upsertParameters.InputColumns.Add(column);
\r
185 upsertParameters.InputValues.Add(sqlProvider.GetParameterName(inputParameter.Alias));
\r
186 upsertParameters.InputParameters.Add(inputParameter);
\r
190 return upsertParameters;
\r
194 /// Provides the parameter type for a given data member
\r
196 /// <param name="objectToUpsert"></param>
\r
197 /// <param name="dataMember"></param>
\r
198 /// <param name="update"></param>
\r
199 /// <returns></returns>
\r
200 protected virtual ParameterType GetParameterType(object objectToUpsert, MetaDataMember dataMember, bool update)
\r
202 var memberInfo = dataMember.Member;
\r
203 // the deal with columns is:
\r
204 // PK only: explicit for INSERT, criterion for UPDATE
\r
205 // PK+GEN: implicit/explicit for INSERT, criterion for UPDATE
\r
206 // GEN only: implicit for both
\r
207 // -: explicit for both
\r
209 // explicit is input,
\r
210 // implicit is output,
\r
211 // criterion is input PK
\r
212 ParameterType type;
\r
213 if (dataMember.IsPrimaryKey)
\r
216 type = ParameterType.InputPK;
\r
219 if (dataMember.IsDbGenerated)
\r
221 if (IsSpecified(objectToUpsert, memberInfo))
\r
222 type = ParameterType.Input;
\r
224 type = ParameterType.Output;
\r
227 type = ParameterType.Input;
\r
232 if (dataMember.IsDbGenerated)
\r
233 type = ParameterType.Output;
\r
235 type = ParameterType.Input;
\r
241 /// Determines if a property is different from its default value
\r
243 /// <param name="target"></param>
\r
244 /// <param name="memberInfo"></param>
\r
245 /// <returns></returns>
\r
246 protected virtual bool IsSpecified(object target, MemberInfo memberInfo)
\r
248 object value = memberInfo.GetMemberValue(target);
\r
251 if (Equals(value, TypeConvert.GetDefault(memberInfo.GetMemberType())))
\r
257 /// Creates or gets an UPDATE query
\r
259 /// <param name="objectToUpdate"></param>
\r
260 /// <param name="modifiedMembers">List of modified members, or NULL</param>
\r
261 /// <param name="queryContext"></param>
\r
262 /// <returns></returns>
\r
263 public UpsertQuery GetUpdateQuery(object objectToUpdate, IList<MemberInfo> modifiedMembers, QueryContext queryContext)
\r
265 var upsertParameters = GetUpsertParameters(objectToUpdate, true, modifiedMembers, queryContext);
\r
266 var sqlProvider = queryContext.DataContext.Vendor.SqlProvider;
\r
267 var updateSql = sqlProvider.GetUpdate(sqlProvider.GetTable(upsertParameters.Table.TableName),
\r
268 upsertParameters.InputColumns, upsertParameters.InputValues,
\r
269 upsertParameters.OutputValues, upsertParameters.OutputExpressions,
\r
270 upsertParameters.InputPKColumns, upsertParameters.InputPKValues
\r
272 return new UpsertQuery(queryContext.DataContext, updateSql, "", upsertParameters.InputParameters, upsertParameters.OutputParameters);
\r
276 /// Creates or gets a DELETE query
\r
278 /// <param name="objectToDelete"></param>
\r
279 /// <param name="queryContext"></param>
\r
280 /// <returns></returns>
\r
281 public DeleteQuery GetDeleteQuery(object objectToDelete, QueryContext queryContext)
\r
283 var sqlProvider = queryContext.DataContext.Vendor.SqlProvider;
\r
284 var rowType = objectToDelete.GetType();
\r
285 var table = queryContext.DataContext.Mapping.GetTable(rowType);
\r
286 var deleteParameters = new List<ObjectInputParameterExpression>();
\r
287 var pkColumns = new List<SqlStatement>();
\r
288 var pkValues = new List<SqlStatement>();
\r
289 foreach (var pkMember in table.RowType.IdentityMembers)
\r
291 var memberInfo = pkMember.Member;
\r
292 var getter = (Expression<Func<object, object>>)(o => memberInfo.GetMemberValue(o));
\r
293 var inputParameter = new ObjectInputParameterExpression(
\r
295 memberInfo.GetMemberType(), pkMember.Name);
\r
296 var column = sqlProvider.GetColumn(pkMember.MappedName);
\r
297 pkColumns.Add(column);
\r
298 pkValues.Add(sqlProvider.GetParameterName(inputParameter.Alias));
\r
299 deleteParameters.Add(inputParameter);
\r
301 var deleteSql = sqlProvider.GetDelete(sqlProvider.GetTable(table.TableName), pkColumns, pkValues);
\r
302 return new DeleteQuery(queryContext.DataContext, deleteSql, deleteParameters);
\r