{"id":13989688,"url":"https://github.com/dntzhang/qone","last_synced_at":"2025-04-04T22:04:34.973Z","repository":{"id":57314309,"uuid":"99460051","full_name":"dntzhang/qone","owner":"dntzhang","description":".NET LINQ in JavaScript","archived":false,"fork":false,"pushed_at":"2022-12-21T09:18:55.000Z","size":1009,"stargazers_count":460,"open_issues_count":2,"forks_count":45,"subscribers_count":34,"default_branch":"master","last_synced_at":"2025-03-28T21:05:53.448Z","etag":null,"topics":["ast","csharp","linq","qone","query","sql"],"latest_commit_sha":null,"homepage":"https://dntzhang.github.io/qone/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dntzhang.png","metadata":{"files":{"readme":"README-CN.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-08-06T02:42:19.000Z","updated_at":"2025-03-23T09:56:46.000Z","dependencies_parsed_at":"2023-01-30T03:15:54.545Z","dependency_job_id":null,"html_url":"https://github.com/dntzhang/qone","commit_stats":null,"previous_names":["alloyteam/omix"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dntzhang%2Fqone","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dntzhang%2Fqone/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dntzhang%2Fqone/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dntzhang%2Fqone/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dntzhang","download_url":"https://codeload.github.com/dntzhang/qone/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247256110,"owners_count":20909240,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ast","csharp","linq","qone","query","sql"],"created_at":"2024-08-09T13:01:57.540Z","updated_at":"2025-04-04T22:04:34.953Z","avatar_url":"https://github.com/dntzhang.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"[English](./README.md) | 简体中文\n\n\u003ca href=\"##qone\"\u003e\u003cimg src=\"./asset/qone.png\" alt=\"qone\"\u003e\u003c/a\u003e\n==============================\n[![npm version](https://badge.fury.io/js/qone.svg)](https://www.npmjs.com/package/qone) \n\n.NET LINQ 在 javascript 中的实现\n\n## 缘由\n\n最近刚好修改了腾讯文档 Excel 表格公式的一些 bug，主要是修改公式的 parser 。比如下面的脚本怎么转成 javascript 运行？\n\n``` js\n= IF(SUM(J6:J7) + SUM(J6:J7) \u003e 10, \"A2 是 foo\", \"A2 不是 foo\")\n```\n\n公式或一些脚本语言的实现包含几个主要步骤:\n\n    scanner \u003e lexer \u003e parser \u003e ast \u003e code string\n\n得到 code string 之后可以动态运行，比如 js 里使用 eval ，eval 能保留上下文信息，缺点是执行代码包含编译器代码，eval 的安全性等。\n得到 code string 之后也可直接使用生成的 code string 运行，缺点是依赖构建工具或者编辑器插件去动态替换源代码。\n\n比如 wind 同时支持 JIT 和 AOT, qone 的思路和上面类似，但不完全相同， qone 的如下:\n\n    scanner \u003e lexer \u003e parser \u003e ast \u003e method(ast)\n\n这个后面写原理时候再细说。\n\n总的来说，因为腾讯文档公式相关工作、早年的 kmdjs 开发 (uglify2) 和 .NET 开发，所以有了 qone 。    \n\n- [LINQ](#linq)\n- [qone 安装](#qone-安装)\n- [qone 关键字与运算符](#qone-关键字与运算符)\n- [qone 方法注入](#qone-方法注入)      \n- [qone select 输出](#qone-select-输出)   \n- [qone orderby](#qone-orderby)\n- [qone groupby](#qone-groupby)\n- [qone 多数据源](#qone-多数据源)\n- [qone 嵌套子数据源](#qone-嵌套子数据源)\n- [qone limit 与分页查询](#qone-limit-与分页查询)\n- [links](#star--fork--pr--repl--follow-me)\n---\n\n## LINQ\n\n LINQ (语言集成查询) 是 .NET Framework 3.5 版中引入的一项创新功能。在 Visual Studio 中，可以用 Visual Basic 或 C# 为以下数据源编写 LINQ 查询：SQL Server 数据库、XML 文档、ADO.NET 数据集，以及可枚举的 Objects(即 LINQ to Objects)。\n\nqone 是一款让 Web 前端工程师在 javascript 使用 .NET 平台下类似 LINQ 语法的前端库。qone 让 Web 前端工程师通过字符串的形式实现了 LINQ to Objects 的调用（下面统一叫做 qone to objects），Objects即 JSON 组成的 Array。举个简单的例子(qone 远比下面的例子强大):\n\n``` js\nvar list = [\n    { name: 'qone', age: 1 },\n    { name: 'linq', age: 18 },\n    { name: 'dntzhang', age: 28 }\n]\n\nvar result = qone({ list }).query(`\n            from n in list   \n            where n.age \u003e 18\n            select n\n        `)\n\nassert.deepEqual(result, [{ \"name\": \"dntzhang\", \"age\": 28 }])\n```\n\n与 LINQ 一样，和 SQL 不同，qone 的 from 也在前面，为后面语句能够有智能提示，qone 是基于 string 的实时编译，不是 javasript 的原生语法，所以虽然 from 写在前面但不支持智能提示，但可以专门为 qone 写个编辑器插件去实现智能提示，所以 qone 语法设计上依然把 from 放在前面。\n\n从根本上说，qone to objects 表示一种新的处理集合的方法。 采用旧方法，您必须编写指定如何从集合检索数据的复杂的 foreach 循环。 而采用 qone 方法，您只需编写描述要检索的内容的声明性代码。\n另外，与传统的 foreach 循环相比，qone 查询具有三大优势：\n\n* 它们更简明、更易读，尤其在筛选多个条件时\n* 它们使用最少的应用程序代码提供强大的筛选、排序和分组功能\n* 无需修改或只需做很小的修改即可将它们移植到其他数据源\n\n通常，您要对数据执行的操作越复杂，就越能体会到 qone 相较于传统迭代技术的优势。\n\n## qone 安装\n\n``` bash\nnpm install qone\n```\n\nCDN:\n\n* [https://unpkg.com/qone@1.0.0/qone.js](https://unpkg.com/qone@1.0.0/qone.js)\n* [https://unpkg.com/qone@1.0.0/qone.min.js](https://unpkg.com/qone@1.0.0/qone.min.js)\n\n## qone 关键字与运算符\n\n* from\n* in\n* where\n* select\n* orderby \n* desc\n* asc\n* groupby\n* limit\n\n其中 from 和 in 一起使用，orderby 和 desc 或者 asc 一起使用。\n\nfrom 也可以把子属性作为 dataSource:\n\n``` js\nfrom n in data.list   \n```\n\nqone 支持下面三类运算符:\n\n* 括号：( )\n* 比较运算符： = , \u003e , \u003c , \u003e= , \u003c= , != \n* 与或非: \u0026\u0026 , || , !\n\n条件判断语句也支持 null, undefined, true, false。\n\n通过上面各种组合，你可以写出很复杂的查询条件。比如:\n\n``` js\nqone({ list }).query(`\n    from n in list   \n    where !(n.age \u003e 17 || n.age \u003c 2) \u0026\u0026 n.name != 'dntzhang'\n    select n\n`)\n```\n\n也支持 bool 类型的查询:\n\n``` js\nvar list = [\n    { name: 'qone', age: 1, isBaby: true },\n    { name: 'linq', age: 18 },\n    { name: 'dntzhang', age: 28 }]\n\nvar result = qone({ list }).query(`\n        from a in list       \n        where a.isBaby \u0026\u0026 n.name = 'qone'\n        select a\n    `)\n\nassert.deepEqual(result, [{ name: 'qone', age: 1, isBaby: true }])\n```\n\n其中 isBaby 是 bool 类型，同样的写法:\na.isBaby = true   (等同于: a.isBaby)\na.isBaby = false  (等同于: !a.isBaby)\n\n## qone 方法注入\n\n通过上面介绍发现 qone 不支持加减乘除位求模运算？怎么才能图灵完备？方法注入搞定一切！如下所示:\n\n``` js\nQUnit.test(\"Method test 8\", function (assert) {\n    var arr = [1, 2, 3, 4, 5]\n\n    qone('square', function (num) {\n        return num * num\n    })\n\n    qone('sqrt', function (num) {\n        return Math.sqrt(num)\n    })\n\n    var result = qone({ arr }).query(`\n      from n in arr   \n      where  sqrt(n) \u003e= 2 \n      select { squareValue : square(n) }\n  `)\n\n    assert.deepEqual(result, [{ \"squareValue\": 16 }, { \"squareValue\": 25 }])\n})\n```\n\n方法也是支持多参数传入，所以可以写出任意的查询条件。其中select, where, orderby, groupby 语句都支持方法注入。\n\n## qone select 输出\n\n通过 select 可以输出各种格式和字段的数据:\n\n``` js\nQUnit.test(\"Select JSON test\", function (assert) {\n    var list = [\n        { name: 'qone', age: 1 },\n        { name: 'linq', age: 18 },\n        { name: 'dntzhang', age: 28 }\n    ]\n\n    var result = qone({ list }).query(`\n                from n in list   \n                where n.age \u003c 20\n                select {n.age, n.name}\n            `)\n\n    assert.deepEqual(result, [\n        { \"age\": 1 , \"name\": \"qone\" },\n        { \"age\": 18, \"name\": \"linq\" }\n    ])\n\n})\n``` \n\n把所有场景列举一下:\n\n* `select n` 输出源 item\n* `select n.name` 输出一维表\n* `select n.name, n.age` 输出二维表\n* `select { n.age, n.name }` 缺省方式输出 JSON Array(key自动使用 age 和 name)\n* `select { a: n.age, b: n.name }` 指定 key 输出 JSON Array\n* `select { a: methodName(n.age), b: n.name }` 注入方法\n* `select methodName(n.age), n.name` 注入方法\n* `select methodName(n.age, n.name, 1, true, 'abc')` 注入方法并传递参数\n\n\n## qone orderby\n\n\n``` js\nvar result = qone({ list }).query(`\n                from n in list   \n                where n.age \u003e 0\n                orderby n.age asc, n.name desc\n                select n\n            `)\n\n``` \n\n如果没有标记 asc 或者 desc，使用默认排序 asc。\n\n## qone groupby\n\n``` js\nQUnit.test(\"Simple groupby test 1\", function (assert) {\n    var list = [\n        { name: 'qone', age: 1 },\n        { name: 'linq', age: 18 },\n        { name: 'dntzhang1', age: 28 },\n        { name: 'dntzhang2', age: 28 },\n        { name: 'dntzhang3', age: 29 }\n    ]\n\n    var result = qone({ list }).query(`\n                from n in list   \n                where n.age \u003e 18\n                groupby n.age\n            `)\n\n    assert.deepEqual(result, [\n        [{ \"name\": \"dntzhang1\", \"age\": 28 }, { \"name\": \"dntzhang2\", \"age\": 28 }],\n        [{ \"name\": \"dntzhang3\", \"age\": 29 }]])\n\n})\n```\n\ngroupby 可以作为结束语句，不用跟着也不能跟着 select 语句，groupby 也可以支持方法注入。\n\n## qone 多数据源 \n\n``` js\nQUnit.test(\"Multi datasource with props condition\", function (assert) {\n\n    var listA = [\n        { name: 'qone', age: 1 },\n        { name: 'linq', age: 18 },\n        { name: 'dntzhang', age: 28 }]\n\n\n    var listB = [\n        { name: 'x', age: 11 },\n        { name: 'xx', age: 18 },\n        { name: 'xxx', age: 13 }\n    ]\n\n    var result = qone({ listA, listB }).query(`\n            from a in listA     \n            from b in listB      \n            where a.age = b.age\n            select a, b\n        `)\n\n    assert.deepEqual(result, [[{ \"name\": \"linq\", \"age\": 18 }, { \"name\": \"xx\", \"age\": 18 }]])\n})\n```\n\n多数据源会产生笛卡儿积。\n\n## qone 嵌套子数据源\n\n``` js\nQUnit.test(\"Multi deep from test \", function (assert) {\n\n    var list = [\n        { name: 'qone', age: 1, isBaby: true, colors: [{ xx: [1, 2, 3] }, { xx: [11, 2, 3] }] },\n        { name: 'linq', age: 18, colors: [{ xx: [100, 2, 3] }, { xx: [11, 2, 3] }] },\n        { name: 'dntzhang', age: 28, colors: [{ xx: [1, 2, 3] }, { xx: [11, 2, 3] }] }]\n\n    var result = qone({ list }).query(`\n            from a in list   \n            from c in a.colors   \n            from d in c.xx  \n            where d === 100\n            select a.name, c,d\n        `)\n\n    assert.deepEqual(result, [[\"linq\", { \"xx\": [100, 2, 3] }, 100]])\n})\n```\n\n也可以和自身的数据源会产生笛卡儿积。\n\n## qone limit 与分页查询\n\n通过 limit 可以应付最常见的两种查询场景 - top N 和 分页。\n\n查询top3:\n\n``` js\nQUnit.test(\"Limit top 3\", function (assert) {\n    var list = [\n        { name: 'dntzhang1', age: 1 },\n        { name: 'dntzhang2', age: 2 },\n        { name: 'dntzhang3', age: 3 },\n        { name: 'dntzhang4', age: 4 },\n        { name: 'dntzhang5', age: 5 },\n        { name: 'dntzhang6', age: 6 },\n        { name: 'dntzhang7', age: 7 },\n        { name: 'dntzhang8', age: 8 },\n        { name: 'dntzhang9', age: 9 },\n        { name: 'dntzhang10', age: 10 }\n    ]\n\n    var pageIndex = 1,\n        pageSize = 4\n    var result = qone({ list }).query(`\n                    from n in list   \n                    select n\n                    limit 0, 3\n                `)\n\n\n    assert.deepEqual(result, [\n\n        { name: 'dntzhang1', age: 1 },\n        { name: 'dntzhang2', age: 2 },\n        { name: 'dntzhang3', age: 3 }\n    ])\n\n})\n``` \n\n分页查询:\n\n``` js\nQUnit.test(\"Limit one page test\", function (assert) {\n    var list = [\n        { name: 'dntzhang1', age: 1 },\n        { name: 'dntzhang2', age: 2 },\n        { name: 'dntzhang3', age: 3 },\n        { name: 'dntzhang4', age: 4 },\n        { name: 'dntzhang5', age: 5 },\n        { name: 'dntzhang6', age: 6 },\n        { name: 'dntzhang7', age: 7 },\n        { name: 'dntzhang8', age: 8 },\n        { name: 'dntzhang9', age: 9 },\n        { name: 'dntzhang10', age: 10 }\n    ]\n\n    var pageIndex = 1,\n        pageSize = 4\n    var result = qone({ list }).query(`\n                    from n in list   \n                    where n.age \u003e 0\n                    select n\n                    limit ${pageIndex * pageSize}, ${pageSize}\n                `)\n\n\n    assert.deepEqual(result, [\n    { name: 'dntzhang5', age: 5 },\n    { name: 'dntzhang6', age: 6 },\n    { name: 'dntzhang7', age: 7 },\n    { name: 'dntzhang8', age: 8 }])\n\n})\n``` \n\n## star \u0026 fork \u0026 pr \u0026 repl \u0026 follow me\n\n* [https://github.com/dntzhang/qone](https://github.com/dntzhang/qone)\n* [https://dntzhang.github.io/qone](https://dntzhang.github.io/qone)\n* [https://github.com/dntzhang](https://github.com/dntzhang)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdntzhang%2Fqone","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdntzhang%2Fqone","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdntzhang%2Fqone/lists"}