嘗試讓查詢更簡單

victor.x.qu發表於2024-09-28

為什麼要寫

為什麼要寫,大概就是沉沒成本吧

只是從Source Generators出來開始,就打算以其研究是否能做 aop (現在已經有內建功能了),本來當年就想嘗試能否在 orm 做一些嘗試,可惜種種原因,自己都忘了這個打算了

直到今年7月份,才又想起了這個打算,現在精力不行了,本來研究一下原理和功能限制也算完了,

可是都寫了蠻久了,不寫完整點,感覺有點浪費,又花了不少時間才把 類似 DapperAOT 功能做的差不多

寫完了又覺得光復制複製功能 沒意思, 所以又把以前為了在公司內少寫一些比較重複性程式碼做的 查詢定製功能 在 sv.db 基礎上搞一把, 反正成本已經花了那麼多了

雖然這也不算多大創新, 類似想法老早就不少人搞了,比如最誇張的 graphql 這一套甚至讓大家的基礎設施都得搭一套,

不過越搞越多,都到國慶了,目前還只能算主體完成了,細節還有很多沒搞

很多轉換 解析都是手寫的,不是因為寫的nb,只是引入其他庫相容可能有點麻煩,場景不多,遞迴遍歷就夠了

nuget 什麼的也沒空搞了,bug 估計不少,等後面再完整點再搞,反正都是自娛自樂

真是年紀大了,做什麼都越來越慢了

sv.db 能做什麼

1. db對映到實體

DapperAOT 一樣,使用 Source Generators 在構建期間生成必要的程式碼,以幫助您更輕鬆地使用 sql。

理論上,您還可以進行 Native AOT 部署

其實沒什麼,舉個例子吧

public async Task<object> OldWay()
{
    var a = factory.GetConnection(StaticInfo.Demo);
    using var dd = await a.ExecuteReaderAsync("""
SELECT count(1)
FROM Weather;
SELECT *
FROM Weather;
""");
    var t = await dd.QueryFirstOrDefaultAsync<int>();
    var r = await dd.QueryAsync<string>().ToListAsync();
    return new { TotalCount = t, Rows = r };
}

2. 讓查詢編碼簡單,並支援更復雜的條件

透過定義一些簡單的查詢規則,我們可以將 查詢轉換為 db / api / es 查詢語句 ....

目前只搞了 db 轉換,並且還沒空完整適配測試, es 、mongodb 什麼等後面有空把

不過理論上,我們可以這樣做:

http query string / body  |------>  select statement    |------>  db (sqlite / mysql/ sqlserver / PostgreSQL)
Expression code           |------>                      |------>  es
                                                        |------>  mongodb
                                                        |------>  more .....

中間 select statement 這一層已經定義好了,前面的轉換也有了, 後面的理論加上適配,什麼都可以做,

2.1 舉個 api 的栗子

Code exmples:

首先定義一個 實體配置, 列明哪些欄位可以查,可以排序,可以篩選

[Db("Demo")]
[Table(nameof(Weather))]
public class Weather
{ 
 [Select, Where, OrderBy]
 public string Name { get; set; }

 [Select(Field = "Value as v"), Where, OrderBy]
 public string V { get; set; }

 [Select(NotAllow = true)]
 public string Test { get; set; }
}

然後定義 查詢介面

[HttpGet] 
public async Task<object> Selects() //  你可以自己做一些欄位,授權檢查 [FromQuery, Required] string name) 
{
    return await this.QueryByParamsAsync<Weather>();
}

接著你就可以讓使用者自己拼寫各種條件,讓她們自己滿足自己的場景,這樣自己可以多摸一會魚了

curl --location 'http://localhost:5259/weather?where=not (name like '%e%')&TotalCount=true'

Response

{
    "totalCount": 1,
    "rows": [
        {
            "name": "H",
            "v": "mery!"
        }
    ]
}
2.2 同樣可以讓查詢程式碼更簡單
Code exmples:

其實很多 orm 都提供用Expression 達到類似或更復雜效果的

這裡考慮工作量,restful api 和其他資料查詢實現 支援度不一,目前只做基礎filter 支援, join 什麼都不搞了,就算搞了多半又會被罵,直接寫sql 不更好嗎?

比如下面 query Weather which name no Contains 'e'

public async Task<object> DoSelects()
{
    return await factory.ExecuteQueryAsync(From.Of<Weather>().Where(i => !i.Name.Like("e")).WithTotalCount());
}

這裡就非常簡單介紹一下,複雜的,怎麼實現的不寫,累了,寫不動了,要有感興趣的 可以在 gayhub 看原始碼 https://github.com/fs7744/sv.db

下面再列舉一下過濾運算子支援情況

Query in api

Both has func support use query string or body to query

body or query string will map to Dictionary<string, string> to handle

operater

such filter operater just make api more restful (Where=urlencode(complex condition) will be more better)

  • {{nl}} is null
    • query string ?name={{nl}}
    • body {"name":"{{nl}}"}
  • {{eq}} Equal =
    • query string ?name=xxx
    • body {"name":"xxx"}
  • {{lt}} LessThan or Equal <=
    • query string ?age={{lt}}30
    • body {"age":"{{lt}}30"}
  • {{le}} LessThan <
    • query string ?age={{le}}30
    • body {"age":"{{le}}30"}
  • {{gt}} GreaterThan or Equal >=
    • query string ?age={{gt}}30
    • body {"age":"{{gt}}30"}
  • {{gr}} GreaterThan >
    • query string ?age={{gr}}30
    • body {"age":"{{gr}}30"}
  • {{nq}} Not Equal !=
    • query string ?age={{nq}}30
    • body {"age":"{{nq}}30"}
  • {{lk}} Prefix Like 'e%'
    • query string ?name={{lk}}e
    • body {"name":"{{lk}}e"}
  • {{rk}} Suffix Like '%e'
    • query string ?name={{rk}}e
    • body {"name":"{{rk}}e"}
  • {{kk}} Like '%e%'
    • query string ?name={{kk}}e
    • body {"name":"{{kk}}e"}
  • {{in}} in array (bool/number/string)
    • query string ?name={{in}}[true,false]
    • body {"name":"{{in}}[\"s\",\"sky\"]"}
  • {{no}} not
    • query string ?age={{no}}{{lt}}30
    • body {"age":"{{no}}{{lt}}30"}

Func Fields:

  • Fields return some Fields , no Fields or Fields=* is return all
    • query string ?Fields=name,age
    • body {"Fields":"name,age"}
  • TotalCount return total count
    • query string ?TotalCount=true
    • body {"TotalCount":"true"}
  • NoRows no return rows
    • query string ?NoRows=true
    • body {"NoRows":"true"}
  • Offset Offset Rows index
    • query string ?Offset=10
    • body {"Offset":10}
  • Rows Take Rows count, default is 10
    • query string ?Rows=100
    • body {"Rows":100}
  • OrderBy sort result
    • query string ?OrderBy=name:asc,age:desc
    • body {"OrderBy":"name:asc,age:desc"}
  • Where complex condition filter
    • query string ?Where=urlencode( not(name like 'H%') or name like '%v%' )
    • body {"Where":"not(name like 'H%') or name like '%v%'"}
    • operaters
      • bool
        • example true or false
      • number
        • example 12323 or 1.324 or -44.4
      • string
        • example 'sdsdfa' or 'sds\'dfa' or "dsdsdsd" or "fs\"dsf"
      • = null is null
        • example name = null
      • = Equal
        • example name = 'sky'
      • <= LessThan or Equal
        • example age <= 30
      • < LessThan
        • example age < 30
      • >= GreaterThan or Equal
        • example age >= 30
      • > GreaterThan
        • example age > 30
      • != Not Equal
        • example age != 30
      • like 'e%' Prefix Like
        • example name like 'xx%'
      • like '%e' Suffix Like
        • example name like '%xx'
      • like '%e%' Like
        • example name like '%xx%'
      • in () in array (bool/number/string)
        • example in (1,2,3) or in ('sdsdfa','sdfa') or in (true,false)
      • not
        • example not( age <= 30 )
      • and
        • example age <= 30 and age > 60
      • or
        • example age <= 30 or age > 60
      • ()
        • example (age <= 30 or age > 60) and name = 'killer'

相關文章