/// <summary> /// 指定された型情報から対象となるテーブルのレコードを取得するクエリを生成します。 /// </summary> /// <typeparam name="T">テーブルの型</typeparam> /// <param name="propertyNames">抽出する列にマッピングされるプロパティのコレクション。指定がない場合はすべての列を抽出対象とします。</param> /// <returns>生成されたSQL</returns> public static string CreateSelect(Type type, IEnumerable <string> propertyNames = null) { if (type == null) { throw new ArgumentNullException(nameof(type)); } if (propertyNames == null) { propertyNames = Enumerable.Empty <string>(); } var table = TableMappingInfo.Create(type); var columns = propertyNames.IsEmpty() ? table.Columns : table.Columns.Join ( propertyNames, x => x.PropertyName, y => y, (x, y) => x ); var columnNames = columns.Select(x => $" {x.ColumnName} as {x.PropertyName}"); var builder = new StringBuilder(); builder.AppendLine("select"); builder.AppendLine(string.Join($",{Environment.NewLine}", columnNames)); builder.Append($"from {table.FullName}"); return(builder.ToString()); }
/// <summary> /// バルク方式で指定のデータを挿入するためのコマンドを生成します。 /// </summary> /// <typeparam name="T">テーブルにマッピングされた型</typeparam> /// <param name="data">挿入するデータ</param> /// <returns>コマンド</returns> private DbCommand CreateBulkInsertCommand <T>(IEnumerable <T> data) { //--- 実体化 data = data.Materialize(); //--- build DbCommand var factory = DbProvider.GetFactory(this.DbKind); dynamic command = factory.CreateCommand(); command.Connection = (dynamic)this.Connection; command.CommandText = PrimitiveSql.CreateInsert <T>(this.DbKind, false, true); command.BindByName = true; command.ArrayBindCount = data.Count(); if (this.Timeout.HasValue) { command.CommandTimeout = this.Timeout.Value; } //--- bind params foreach (var x in TableMappingInfo.Create <T>().Columns) { var getter = AccessorCache <T> .LookupGet(x.PropertyName); dynamic parameter = factory.CreateParameter(); parameter.ParameterName = x.PropertyName; parameter.DbType = x.ColumnType; parameter.Value = data.Select(y => getter(y)).ToArray(); command.Parameters.Add(parameter); } return(command); }
/// <summary> /// 指定された型情報から、対象となるテーブルのレコードを指定されたプロパティにマッピングされている列に絞って更新するクエリを生成します。 /// </summary> /// <param name="targetDatabase">対象データベース</param> /// <param name="type">テーブルの型</param> /// <param name="propertyNames">プロパティ名のコレクション。指定がない場合はすべての列を抽出対象とします。</param> /// <param name="setIdentity">自動採番のID列に値を設定するかどうか</param> /// <returns>生成されたSQL</returns> public static string CreateUpdate(DbKind targetDatabase, Type type, IEnumerable <string> propertyNames = null, bool setIdentity = false) { if (type == null) { throw new ArgumentNullException(nameof(type)); } if (propertyNames == null) { propertyNames = Enumerable.Empty <string>(); } var prefix = targetDatabase.GetBindParameterPrefix(); var table = TableMappingInfo.Create(type); var columns = table.Columns.Where(x => setIdentity ? true : !x.IsAutoIncrement); if (propertyNames.Any()) { columns = columns.Join(propertyNames, x => x.PropertyName, y => y, (x, y) => x); } var setters = columns.Select(x => $" {x.ColumnName} = {prefix}{x.PropertyName}"); var builder = new StringBuilder(); builder.AppendLine($"update {table.FullName}"); builder.AppendLine("set"); builder.Append(string.Join($",{Environment.NewLine}", setters)); return(builder.ToString()); }
/// <summary> /// InsertAndGet メソッドを実行可能かどうかを診断します。 /// </summary> /// <typeparam name="T">テーブルにマッピングされた型</typeparam> /// <returns>実行可能かどうか</returns> protected static void AssertInsertAndGet <T>() { if (typeof(T).IsCollection()) { throw new InvalidOperationException("Can insert single entity only."); } var table = TableMappingInfo.Create <T>(); var primary = table.Columns.Where(x => x.IsPrimaryKey).ToArray(); if (primary.Length != 1) { throw new InvalidOperationException("Primary key column should be only one."); } var autoIncrement = table.Columns.Where(x => x.IsAutoIncrement).ToArray(); var sequence = table.Columns.Where(x => x.Sequence != null).ToArray(); var idColumnCount = autoIncrement.Length + sequence.Length; if (idColumnCount != 1) { throw new InvalidOperationException("Id column (auto increment or sequence) should be only one."); } if (!primary[0].IsAutoIncrement && primary[0].Sequence == null) { throw new InvalidOperationException("Id column should be primary key."); } }
/// <summary> /// 指定したデータからバルクインサート用のSQL文を生成します。 /// </summary> /// <typeparam name="T">テーブルにマッピングされた型</typeparam> /// <param name="data">挿入するデータ</param> /// <returns>SQL文</returns> private string CreateBulkInsertSql <T>(IEnumerable <T> data) { var prefix = this.DbKind.GetBindParameterPrefix(); var table = TableMappingInfo.Create <T>(); var columnNames = table.Columns.Select(x => " " + x.ColumnName); var builder = new StringBuilder(); builder.AppendLine($"insert into {table.FullName}"); builder.AppendLine("("); builder.AppendLine(string.Join($",{Environment.NewLine}", columnNames)); builder.AppendLine(")"); builder.Append("values"); var getters = table.Columns.Select(c => AccessorCache <T> .LookupGet(c.PropertyName)).ToArray(); foreach (var x in data) { builder.AppendLine(); builder.Append("("); var values = getters.Select(f => ToSqlLiteral(f(x))); builder.Append(string.Join(", ", values)); builder.Append("),"); } builder.Length--; //--- 最後の「,」を削除 return(builder.ToString()); }
/// <summary> /// レコードを挿入し、そのレコードに自動採番されたIDを取得するSQLを生成します。 /// </summary> /// <typeparam name="T">テーブルにマッピングされた型</typeparam> /// <returns>SQL文</returns> protected override string CreateInsertAndGetSql <T>() { var sequence = TableMappingInfo.Create <T>().Columns.First(x => x.IsPrimaryKey).Sequence; return ($@"{PrimitiveSql.CreateInsert<T>(this.DbKind)}; select currval({sequence.FullName}) as Id;"); }
/// <summary> /// 指定された型情報から対象となるテーブルのレコード数をカウントするクエリを生成します。 /// </summary> /// <param name="type">テーブルの型</param> /// <returns>生成されたSQL</returns> public static string CreateCount(Type type) { if (type == null) { throw new ArgumentNullException(nameof(type)); } var table = TableMappingInfo.Create(type); return($"select count(*) as Count from {table.FullName}"); }
/// <summary> /// 指定された型情報から対象となるテーブルのすべてのレコードを切り捨てるクエリを生成します。 /// </summary> /// <param name="type">テーブルの型</param> /// <returns>生成されたSQL</returns> public static string CreateTruncate(Type type) { if (type == null) { throw new ArgumentNullException(nameof(type)); } var table = TableMappingInfo.Create(type); return($"truncate table {table.FullName}"); }
/// <summary> /// 指定された型情報から対象となるテーブルにレコードを挿入するクエリを生成します。 /// </summary> /// <param name="targetDatabase">対象データベース</param> /// <param name="type">テーブルの型</param> /// <param name="useSequence">シーケンスを利用するかどうか</param> /// <param name="setIdentity">自動採番のID列に値を設定するかどうか</param> /// <returns>生成されたSQL</returns> public static string CreateInsert(DbKind targetDatabase, Type type, bool useSequence = true, bool setIdentity = false) { if (type == null) { throw new ArgumentNullException(nameof(type)); } var prefix = targetDatabase.GetBindParameterPrefix(); var table = TableMappingInfo.Create(type); var columns = table.Columns.Where(x => setIdentity ? true : !x.IsAutoIncrement); var values = columns.Select(x => { if (useSequence) { if (x.Sequence != null) { switch (targetDatabase) { case DbKind.SqlServer: return($"next value for {x.Sequence.FullName}"); case DbKind.Oracle: return($"{x.Sequence.FullName}.nextval"); case DbKind.PostgreSql: return($"nextval('{x.Sequence.FullName}')"); } } } return($"{prefix}{x.PropertyName}"); }) .Select(x => " " + x); var columnNames = columns.Select(x => " " + x.ColumnName); var builder = new StringBuilder(); builder.AppendLine($"insert into {table.FullName}"); builder.AppendLine("("); builder.AppendLine(string.Join($",{Environment.NewLine}", columnNames)); builder.AppendLine(")"); builder.AppendLine("values"); builder.AppendLine("("); builder.AppendLine(string.Join($",{Environment.NewLine}", values)); builder.Append(")"); return(builder.ToString()); }
/// <summary> /// バルク方式での挿入処理の準備を行います。 /// </summary> /// <typeparam name="T">テーブルにマッピングされた型</typeparam> /// <param name="executor">バルク処理実行機能</param> /// <param name="data">挿入する生データ</param> /// <returns>挿入するデータ</returns> private DataTable SetupBulkInsert <T>(SqlBulkCopy executor, IEnumerable <T> data) { //--- タイムアウト if (this.Timeout.HasValue) { executor.BulkCopyTimeout = this.Timeout.Value; } //--- 対象テーブル名 var info = TableMappingInfo.Create <T>(); executor.DestinationTableName = info.FullName; //--- 列のマップ var table = new DataTable(); var getters = new List <Func <T, object> >(); foreach (var x in info.Columns) { executor.ColumnMappings.Add(x.PropertyName, x.ColumnName); table.Columns.Add(new DataColumn { ColumnName = x.PropertyName, DataType = x.IsNullable ? Nullable.GetUnderlyingType(x.PropertyType) : x.PropertyType, AllowDBNull = x.IsNullable }); getters.Add(AccessorCache <T> .LookupGet(x.PropertyName)); } //--- データ生成 foreach (var x in data) { var row = table.NewRow(); for (int i = 0; i < getters.Count; i++) { row[i] = getters[i](x); } table.Rows.Add(row); } return(table); }
/// <summary> /// InsertAndGetするためのパラメーターを生成します。 /// </summary> /// <typeparam name="T">テーブルにマッピングされた型</typeparam> /// <param name="data">挿入するデータ</param> /// <returns>パラメーター</returns> private Tuple <DbCommand, DbParameter> CreateInsertAndGetParameter <T>(T data) { //--- command var factory = DbProvider.GetFactory(this.DbKind); dynamic command = factory.CreateCommand(); command.BindByName = true; command.Connection = (dynamic)this.Connection; if (this.Timeout.HasValue) { command.CommandTimeout = this.Timeout.Value; } //--- parameters DbParameter output = null; foreach (var x in TableMappingInfo.Create <T>().Columns) { dynamic parameter = factory.CreateParameter(); parameter.ParameterName = x.PropertyName; parameter.DbType = x.ColumnType; if (x.IsPrimaryKey) { parameter.Direction = ParameterDirection.Output; output = parameter; command.CommandText = $@"{PrimitiveSql.CreateInsert<T>(this.DbKind)} returning {x.ColumnName} into :{x.PropertyName}"; } else { parameter.Direction = ParameterDirection.Input; parameter.Value = AccessorCache <T> .LookupGet(x.PropertyName)(data); } command.Parameters.Add(parameter); } //--- ok return(Tuple.Create((DbCommand)command, output)); }
/// <summary> /// 条件を表す式からSQLを生成します /// </summary> /// <typeparam name="T">テーブルの型</typeparam> /// <param name="targetDatabase">対象となるデータベース</param> /// <param name="predicate">条件式</param> /// <returns>条件SQL</returns> public static This From <T>(DbKind targetDatabase, Expression <Func <T, bool> > predicate) { //--- 解析実行 var root = PredicateParser.Parse(predicate); //--- SQL文 / パラメーター生成の定義 uint index = 0; var columnMap = TableMappingInfo.Create <T>().Columns.ToDictionary(x => x.PropertyName); var prefix = targetDatabase.GetBindParameterPrefix(); var parameterCount = root.DescendantsAndSelf().Count(x => { return(x.Operator != PredicateOperator.AndAlso && x.Operator != PredicateOperator.OrElse && x.Value != null); }); var digit = (parameterCount - 1).ToString().Length; var digitFormat = $"D{digit}"; IDictionary <string, object> parameter = new ExpandoObject(); Func <PredicateElement, string> sqlBuilder = null; sqlBuilder = element => { if (element.HasChildren) { var left = sqlBuilder(element.Left); var right = sqlBuilder(element.Right); if (element.Operator != element.Left.Operator && element.Left.HasChildren) { left = $"({left})"; } if (element.Operator != element.Right.Operator && element.Right.HasChildren) { right = $"({right})"; } if (element.Operator == PredicateOperator.AndAlso) { return($"{left} and {right}"); } if (element.Operator == PredicateOperator.OrElse) { return($"{left} or {right}"); } throw new InvalidOperationException(); } else { var builder = new StringBuilder(); builder.Append(columnMap[element.PropertyName].ColumnName); switch (element.Operator) { case PredicateOperator.Equal: if (element.Value == null) { builder.Append(" is null"); return(builder.ToString()); } builder.Append(" = "); break; case PredicateOperator.NotEqual: if (element.Value == null) { builder.Append(" is not null"); return(builder.ToString()); } builder.Append(" <> "); break; case PredicateOperator.LessThan: builder.Append(" < "); break; case PredicateOperator.LessThanOrEqual: builder.Append(" <= "); break; case PredicateOperator.GreaterThan: builder.Append(" > "); break; case PredicateOperator.GreaterThanOrEqual: builder.Append(" >= "); break; case PredicateOperator.Contains: builder.Append(" in "); break; default: throw new InvalidOperationException(); } var parameterName = $"p{index.ToString(digitFormat)}"; ++index; parameter.Add(parameterName, element.Value); //--- cache parameter builder.Append($"{prefix}{parameterName}"); return(builder.ToString()); } }; //--- 組み立て return(new This(sqlBuilder(root), parameter as ExpandoObject)); }