From b37bef786dd174cc11c80cc9c752e576660add35 Mon Sep 17 00:00:00 2001 From: mk3008 Date: Sat, 1 Jun 2024 21:06:37 +0900 Subject: [PATCH] Improved the Select clause writing experience When an IDataRow is passed to the Select method, it is interpreted as selecting all columns in the dataset. This is equivalent to the behavior of wildcards in SQL. Carbunql also allows the Select clause to be written multiple times, so it can be written in a similar way even when joining tables. If a column to be added has already been registered, it will be ignored. This is because it is common to write columns with higher priority first. --- src/Carbunql.TypeSafe/FluentSelectQuery.cs | 60 +++++++-- src/Carbunql.TypeSafe/Sql.cs | 23 ---- test/Carbunql.TypeSafe.Test/SelectTest.cs | 146 +++++++++++++++++++++ 3 files changed, 196 insertions(+), 33 deletions(-) create mode 100644 test/Carbunql.TypeSafe.Test/SelectTest.cs diff --git a/src/Carbunql.TypeSafe/FluentSelectQuery.cs b/src/Carbunql.TypeSafe/FluentSelectQuery.cs index 723ab750..1281879c 100644 --- a/src/Carbunql.TypeSafe/FluentSelectQuery.cs +++ b/src/Carbunql.TypeSafe/FluentSelectQuery.cs @@ -3,9 +3,11 @@ using Carbunql.Building; using Carbunql.Clauses; using Carbunql.Definitions; +using Carbunql.Extensions; using Carbunql.Tables; using Carbunql.TypeSafe.Extensions; using Carbunql.Values; +using System.Data; using System.Linq.Expressions; namespace Carbunql.TypeSafe; @@ -17,23 +19,61 @@ public FluentSelectQuery Select(Expression> expression) where T : cla #if DEBUG var analyzed = ExpressionReader.Analyze(expression); #endif - - var body = (NewExpression)expression.Body; - var prmManager = new ParameterManager(GetParameters(), AddParameter); - if (body.Members != null) + if (expression.Body is MemberExpression mem) + { + var c = mem.CompileAndInvoke(); + if (c is IDataRow dr) + { + foreach (var item in dr.DataSet.Columns) + { + //Do not add duplicate columns + if (SelectClause != null && SelectClause.Where(x => x.Alias.IsEqualNoCase(item)).FirstOrDefault() != null) + { + continue; + } + + //add + this.Select(mem.Member.Name, item); + } + return this; + } + throw new InvalidProgramException(); + + } + else if (expression.Body is NewExpression ne) { - var cnt = body.Members.Count(); - for (var i = 0; i < cnt; i++) + if (ne.Members != null) { - var alias = body.Members[i].Name; - var value = ToValue(body.Arguments[i], prmManager.AddParaemter); - this.Select(RemoveRootBracketOrDefault(value)).As(alias); + var columns = GetColumnNames(); + + var cnt = ne.Members.Count(); + for (var i = 0; i < cnt; i++) + { + var alias = ne.Members[i].Name; + + //Remove duplicate columns before adding + if (SelectClause != null) + { + //remove + var col = SelectClause.Where(x => x.Alias.IsEqualNoCase(alias)).FirstOrDefault(); + if (col != null) + { + SelectClause!.Remove(col); + } + } + + //add + var value = ToValue(ne.Arguments[i], prmManager.AddParaemter); + this.Select(RemoveRootBracketOrDefault(value)).As(alias); + } + return this; } + } - return this; + throw new InvalidProgramException(); } public FluentSelectQuery InnerJoin(Expression> tableExpression, Expression> conditionExpression) where T : IDataRow diff --git a/src/Carbunql.TypeSafe/Sql.cs b/src/Carbunql.TypeSafe/Sql.cs index d97b7c94..d3be66d7 100644 --- a/src/Carbunql.TypeSafe/Sql.cs +++ b/src/Carbunql.TypeSafe/Sql.cs @@ -95,29 +95,6 @@ public static class Sql return DefineDataSet(expression, Materialized.NotMaterialized); } - public static T DefineSubQuery(SelectQuery query) where T : IDataRow, new() - { - var instance = new T(); - - //var info = TableInfoFactory.Create(typeof(T)); - - instance.DataSet = new QueryDataSet(query); - return instance; - } - - public static T DefineSubQuery(FluentSelectQuery query) where T : IDataRow, new() - { - var instance = new T(); - - instance.DataSet = new QueryDataSet(query); - return instance; - } - - public static T DefineSubQuery(Func> builder) where T : IDataRow, new() - { - return DefineSubQuery(builder.Invoke()); - } - public static T DefineDataSet() where T : IDataRow, new() { var instance = new T(); diff --git a/test/Carbunql.TypeSafe.Test/SelectTest.cs b/test/Carbunql.TypeSafe.Test/SelectTest.cs new file mode 100644 index 00000000..c13b4e70 --- /dev/null +++ b/test/Carbunql.TypeSafe.Test/SelectTest.cs @@ -0,0 +1,146 @@ +using Xunit.Abstractions; + +namespace Carbunql.TypeSafe.Test; + +public class SelectTest +{ + public SelectTest(ITestOutputHelper output) + { + Output = output; + } + + private ITestOutputHelper Output { get; } + + [Fact] + public void SelectAllDataSet() + { + var od = Sql.DefineDataSet(); + + var query = Sql.From(() => od) + .Select(() => od); + + var actual = query.ToText(); + Output.WriteLine(actual); + + var expect = @"SELECT + od.order_detail_id, + od.order_id, + od.product_id, + od.quantity, + od.price +FROM + order_detail AS od"; + + Assert.Equal(expect, actual, true, true, true); + } + + [Fact] + public void SelectAllDataSetMultiple() + { + var od = Sql.DefineDataSet(); + var p = Sql.DefineDataSet(); + + var query = Sql.From(() => od) + .InnerJoin(() => p, () => od.product_id == p.product_id) + .Select(() => od) + .Select(() => p); + + var actual = query.ToText(); + Output.WriteLine(actual); + + var expect = @"SELECT + od.order_detail_id, + od.order_id, + od.product_id, + od.quantity, + od.price, + p.name +FROM + order_detail AS od + INNER JOIN product AS p ON od.product_id = p.product_id"; + + Assert.Equal(expect, actual, true, true, true); + } + + [Fact] + public void SelectAllDataSetMultiple_WithAlias() + { + var od = Sql.DefineDataSet(); + var p = Sql.DefineDataSet(); + + var query = Sql.From(() => od) + .InnerJoin(() => p, () => od.product_id == p.product_id) + .Select(() => od) + .Select(() => new + { + product_price = p.price, + product_name = p.name, + }); + + var actual = query.ToText(); + Output.WriteLine(actual); + + var expect = @"SELECT + od.order_detail_id, + od.order_id, + od.product_id, + od.quantity, + od.price, + p.price AS product_price, + p.name AS product_name +FROM + order_detail AS od + INNER JOIN product AS p ON od.product_id = p.product_id"; + + Assert.Equal(expect, actual, true, true, true); + } + + public record product : IDataRow + { + public int product_id { get; set; } + public string name { get; set; } = string.Empty; + public decimal price { get; set; } + + // interface property + IDataSet IDataRow.DataSet { get; set; } = null!; + } + + public record store : IDataRow + { + public int store_id { get; set; } + public string name { get; set; } = string.Empty; + public string location { get; set; } = string.Empty; + + // interface property + IDataSet IDataRow.DataSet { get; set; } = null!; + } + + public record order : IDataRow + { + public int order_id { get; set; } + public DateTime order_date { get; set; } + public string customer_name { get; set; } = string.Empty; + public int store_id { get; set; } + public IList order_details { get; init; } = new List(); + + // interface property + IDataSet IDataRow.DataSet { get; set; } = null!; + } + + public record order_detail : IDataRow + { + public int order_detail_id { get; set; } + public int order_id { get; set; } + public int product_id { get; set; } + public int quantity { get; set; } + public decimal price { get; set; } + + // interface property + IDataSet IDataRow.DataSet { get; set; } = null!; + } + + public record order_detail_product : order_detail + { + public string product_name { get; set; } = string.Empty; + } +}