MYear.ODA
C# .Net Database ORM for Oracle DB2 MySql SqlServer SQLite MariaDB; NYear.ODA 是一个数据库访问的 ORM 组件,能够通用常用的数据库 DB2、Oracle、SqlServer、MySql(MariaDB)、SQLite; 对不常用的数据库Informix、Sybase、Access也能简单的使用; 就目前而言,NYear.ODA 是支持 SQL 语法最完整的 C# ORM 组件;对于分库分表、或分布式数据库也留有很好的扩展空间。 分页、动态添加条件、子查询、无限连接查询、Union、Group by、having、In子查询、Exists、Insert子查询、Import高速导入都不在话下, 递归查询、case when、Decode、NullDefault、虚拟字段、数据库function、update 字段运算、表达式等都是 ODA 很有特色的地方。 允许用户注入SQL代码段,允许用户自己编写SQL代码等,同时也支持存储过程Procedure(或oracle的包) 由于很多开发者都比较喜欢 Lambda 的直观简单,ODA 的查询也扩展了此功能。
Install / Use
/learn @riwfnsse/MYear.ODAREADME
MYear.ODA
简介
C# .Net Database ORM for Oracle DB2 MySql SqlServer SQLite MariaDB;<br/> 纯c#开发,支持.NetCore 2.0,.Net standard <br/> MYear.ODA 是一个数据库访问的 ORM 组件,能够通用常用的数据库 DB2、Oracle、SqlServer、MySql(MariaDB)、SQLite; 对不常用的数据库Informix、Sybase、Access也能简单的使用;<br/> 就目前而言,MYear.ODA 是支持 SQL 语法最完整的 C# ORM 组件;对于分库分表、或分布式数据库也留有很好的扩展空间。 分页、动态添加条件、子查询、无限连接查询、Union、Group by、having、In子查询、Exists、Insert子查询、Import高速导入都不在话下, 递归查询、case when、Decode、NullDefault、虚拟字段、数据库function、update 字段运算、表达式等都是 ODA 很有特色的地方。 允许用户注入SQL代码段,允许用户自己编写SQL代码等,同时也支持存储过程Procedure(或oracle的包)<br/> 由于很多开发者都比较喜欢 Lambda 的直观简单,ODA 的查询也扩展了此功能。<br/>
ODA 可以在大型开发团队中,人员水平参差不齐的情况下,提高开发人员效率、规范代码编写方式及减少数据库访问问题。<br/> ODA 的性能、功能、运行稳定性及并发性能都是非常强悍的,比Dapper、EF、SqlSugar都有过之而无不及。<br/> 注: 已有实际项目应用,且Oracle、Mysql、SqlServer 三个数据库上随意切换,<br/> 当然,建表时需要避免各种数据库的关键字及要注意不同数据库对数据记录的大小写敏感问题。<br/>
MYear.ODA 语法
ODA使用的是链式编程语法,对SQL语句进行直接映射,所以ODA的编写方式与SQL语法神似,只是把Select、Insert、Update、Delete 后置了。<br/> 因为ODA是对SQL语句进行直接映射,本身没有什么转换,不会象EF那样转换太多,造成真正在数据库里执行的SQL语句复杂、性能低下,且难以优化。<br/> 使用ODA时如有迷茫之处,基本可以用SQL语句类推出ODA的对应写法,学习以成本极低,也极容易成上手,也不需要象EF那样花很多时间去学习。<br/> ODA为求通用各种数据库,转换出来的SQL都是标准通用的SQL语句;一些常用但数据不兼容的部分,在ODA内部实现(如递归树查询、分页等)。 <br/> MYear.ODA以 Select、Insert、Update、Delete、Procedure 方法为最终执行方法,调用这些方法时,ODA 将会把生成的SQL语句发送给数据库运行。
性能测试
在信息管理系统中,业务功能程序不会去追求极致的性能,满足一般的人机交互需求就可以;</br> 但在框架底层或影响全局的程序中(如 ORM 组件、AOP 容器等),特别是在并发量到达一定规模的时候,我们都会追求极致的性能。</br> ORM 本身的性能会影响系统整体的性能,而且它的使用方式,可维护性,及对程序开发人员的约束,对系统整体性能有全面的影响。</br> 由于 ORM 对程序的侵入性,不好的ORM会把开发人员带偏,走向万劫不复的深渊,EF就是一个很好的反面教材;</br> EF本身数据映性能就不好、SQL语句生成速度差,但要算最不靠谱的,应该就是生成出来的SQL语句了。</br> EF生成的SQL有很多的转换,语句非常复杂,经常走不上索引,还没办法优化,导致这些SQL语句在数据库里头运行的简直就是奇慢无比。</br> 再加上EF、Linq的语法需要花大力气去学,使用起来的开发效率也十分低下,代码难以维护,综合起来就是一个恶梦。</br> 要说 ORM 本身的性能其实是比较其次的(虽然ODA的性非常强悍),但关键是 ORM 不要带偏开发者,</br> 然后就是能够规范访问数据库程序代码,提高程序的可维护性同时也要提高开发效率,支持后续的延申应用(如分库、分表,多数据库混用等),</br> 而且 ORM 应该要做到数据库通,同时也要约束开发者不要去使用某个数据库特有的东西。</br> 这才是 ORM 的追求。
数据映射性
1000000条数据记录,查询出来并转成对应的实体对象。</br>
相较于EF 、Dapper 和 Sqlsugar,ODA 的性能是最好的 <br/>

读取单条数据记录
Dapper比ODA 略胜一点,但预热时间(第一条)比较长,ODA 比 EF与SqlSugar有很明显的优势;</br>
特别是EF相较其他差了好几倍。</br>

SQL生成性能
Dapper、没有此功能不作对比;EF 语句生成速度,简直不忍直视。ODA 比 Sqlsugar 好几倍。

查询分页
Dapper、没有处功能不作对比;EF 、ODA、Sqlsugar 性能相差无几。

用法示例
连接数据库
// 全局设定
ODAContext.SetODAConfig(DbAType.MsSQL, "server=localhost;database=master;uid=sa;pwd=sa;");
// 单个上下文设定
var ctx = new ODAContext(DbAType.MsSQL,"server=localhost;database=master;uid=sa;pwd=sa;");
查询
简单查询
SQL的关键字,在ODA中一般都会有对应的方法或属性。</br> 查询指定的字段,字段别名,数据库函数,ODA中都可以很方便地使用。</br>
ODAContext ctx = new ODAContext();
var U = ctx.GetCmd<CmdSysUser>();
DataTable data = U.Where(U.ColUserAccount == "User1")
.And(U.ColIsLocked == "N")
.And(U.ColStatus == "O")
.And(U.ColEmailAddr.IsNotNull)
.Select(U.ColUserAccount, U.ColUserPassword.As("PWD"), U.ColUserName, U.ColPhoneNo, U.ColEmailAddr);
SELECT T0.USER_ACCOUNT,
T0.USER_PASSWORD AS PWD,
T0.USER_NAME,
T0.PHONE_NO,
T0.EMAIL_ADDR
FROM SYS_USER T0
WHERE T0.USER_ACCOUNT = @T1
AND T0.IS_LOCKED = @T2
AND T0.STATUS = @T3
AND T0.EMAIL_ADDR IS NOT NULL;
查询默认实体
为简化单表查询转为实体写法,ODA提供 SelectM 方法,返回默认实体数据。</br> 此方法是泛型方法 Select<> 的再次封装,它与调用Select<>是一样的。
ODAContext ctx = new ODAContext();
var U = ctx.GetCmd<CmdSysUser>();
List<SYS_USER> data = U.Where(U.ColUserAccount == "User1", U.ColIsLocked == "N", U.ColStatus == "O", U.ColEmailAddr.IsNotNull)
.SelectM(U.ColUserAccount, U.ColUserName, U.ColPhoneNo, U.ColEmailAddr);
SELECT T0.USER_ACCOUNT, T0.USER_NAME, T0.PHONE_NO, T0.EMAIL_ADDR
FROM SYS_USER T0
WHERE T0.USER_ACCOUNT = @T1
AND T0.IS_LOCKED = @T2
AND T0.STATUS = @T3
AND T0.EMAIL_ADDR IS NOT NULL;
查询并返回指定实体类型
返回的实体类型可以是任意自定义类型,并不一定是对应数据库的实体.</br> 泛型方法 Select<> 对传入的实体类型没有大多要求,它的可写属性与查询的字段名称一致时将赋值。</br> 实体的属性与查询的字段个数无需一致。
ODAContext ctx = new ODAContext();
var U = ctx.GetCmd<CmdSysUser>();
List<SYS_USER> data = U.Where(U.ColUserAccount == "User1",U.ColIsLocked == "N",U.ColStatus == "O",U.ColEmailAddr.IsNotNull)
.Select<SYS_USER>(U.ColUserAccount, U.ColUserName, U.ColPhoneNo, U.ColEmailAddr);
SELECT T0.USER_ACCOUNT, T0.USER_NAME, T0.PHONE_NO, T0.EMAIL_ADDR
FROM SYS_USER T0
WHERE T0.USER_ACCOUNT = @T1
AND T0.IS_LOCKED = @T2
AND T0.STATUS = @T3
AND T0.EMAIL_ADDR IS NOT NULL;
查询分页
ODAContext ctx = new ODAContext();
int total = 0;
var U = ctx.GetCmd<CmdSysUser>();
var data = U.Where(U.ColUserAccount == "User1", U.ColIsLocked == "N", U.ColEmailAddr.IsNotNull)
.SelectM(0,20,out total, U.ColUserAccount, U.ColUserName, U.ColPhoneNo, U.ColEmailAddr);
SELECT T0.USER_ACCOUNT, T0.USER_NAME, T0.PHONE_NO, T0.EMAIL_ADDR
FROM SYS_USER T0
WHERE T0.USER_ACCOUNT = @T1
AND T0.IS_LOCKED = @T2
AND T0.EMAIL_ADDR IS NOT NULL;
SELECT COUNT(*) AS TOTAL_RECORD
FROM SYS_USER T0
WHERE T0.USER_ACCOUNT = @T4
AND T0.IS_LOCKED = @T5
AND T0.EMAIL_ADDR IS NOT NULL;
查询第一行
很多时候我们查询数据库只需取第一行的数据。ODA为简化应用,提供了查询第一行数据返回动态类型数据的方法。</br> 返回结果是动态类型,按查询的字段名取值即可。
ODAContext ctx = new ODAContext();
var U = ctx.GetCmd<CmdSysUser>();
var data = U.Where(U.ColUserAccount == "User1", U.ColIsLocked == "N", U.ColEmailAddr.IsNotNull)
.SelectDynamicFirst(U.ColUserAccount, U.ColUserName, U.ColPhoneNo, U.ColEmailAddr);
string UserName = data.USER_NAME;///属性 USER_NAME 与 ColUserName 的ColumnName一致,如果没有数据则返回null
SELECT T0.USER_ACCOUNT, T0.USER_NAME, T0.PHONE_NO, T0.EMAIL_ADDR
FROM SYS_USER T0
WHERE T0.USER_ACCOUNT = @T1
AND T0.IS_LOCKED = @T2
AND T0.EMAIL_ADDR IS NOT NULL;
SELECT COUNT(*) AS TOTAL_RECORD
FROM SYS_USER T0
WHERE T0.USER_ACCOUNT = @T4
AND T0.IS_LOCKED = @T5
AND T0.EMAIL_ADDR IS NOT NULL;
返回动态数据模型
很多时候为一种查询编写一个实体类,实在是很麻烦。</br> ODA本身提供了动态类型的返回值,这样对取小量数据的程序来说是很方便的了。
ODAContext ctx = new ODAContext();
var U = ctx.GetCmd<CmdSysUser>();
var data = U.Where(U.ColUserAccount == "User1", U.ColIsLocked == "N", U.ColEmailAddr.IsNotNull)
.SelectDynamic(U.ColUserAccount, U.ColUserName, U.ColPhoneNo, U.ColEmailAddr);
string UserName = "";
if (data.Count > 0)
UserName = data[0].USER_NAME; ///与 ColUserName 的 ColumnName一致.
SELECT T0.USER_ACCOUNT, T0.USER_NAME, T0.PHONE_NO, T0.EMAIL_ADDR
FROM SYS_USER T0
WHERE T0.USER_ACCOUNT = @T1
AND T0.IS_LOCKED = @T2
AND T0.EMAIL_ADDR IS NOT NULL;
去重复 Distinct
ODAContext ctx = new ODAContext();
var U = ctx.GetCmd<CmdSysUser>();
var data = U.Where( U.ColIsLocked == "N", U.ColEmailAddr.IsNotNull)
.Distinct.Select(U.ColUserAccount, U.ColUserName, U.ColPhoneNo, U.ColEmailAddr);
SELECT DISTINCT T0.USER_ACCOUNT, T0.USER_NAME, T0.PHONE_NO, T0.EMAIL_ADDR
FROM SYS_USER T0
WHERE T0.IS_LOCKED = @T1
AND T0.EMAIL_ADDR IS NOT NULL;
连接查询
ODA支持 InnerJoin、LeftJoin、RightJion,且可以无限的Join。</br> On 的条件也十分灵活,SQL语句支持的条件方式,ODA基本支持。
ODAContext ctx = new ODAContext();
var U = ctx.GetCmd<CmdSysUser>();
var R = ctx.GetCmd<CmdSysRole>();
var UR = ctx.GetCmd<CmdSysUserRole>();
var data = U.InnerJoin(UR, U.ColUserAccount == UR.ColUserAccount, UR.ColStatus == "O")
.LeftJoin(R, UR.ColRoleCode == R.ColRoleCode, R.ColStatus == "O")
.Where(U.ColStatus == "O",R.ColRoleCode == "Administrator")
.Select<UserDefineModel>(U.ColUserAccount.As("UserAccount"), U.ColUserName.As("UserName"),R.ColRoleCode.As("Role"), R.ColRoleName.As("RoleName"));
SELECT T0.USER_ACCOUNT AS UserAccount,
T0.USER_NAME AS UserName,
T1.ROLE_CODE AS Role,
T1.ROLE_NAME AS RoleName
FROM SYS_USER T0
INNER JOIN SYS_USER_ROLE T2
ON T0.USER_ACCOUNT = T2.USER_ACCOUNT
AND T2.STATUS = 'O'
INNER JOIN SYS_ROLE T1
ON T2.ROLE_CODE = T1.ROLE_CODE
AND T1.STATUS ='O'
WHERE T0.STATUS = 'O'
AND T1.ROLE_CODE = 'Administrator';
简单内连接
内连接有很多人只使用 join , 但对形如 SELECT t1.* FROM TABLE1 T1,TABLE2 T2,TABLE3 T3,TABLE4 这种写法比较陌生,<br/> 但个人觉得这种连接的写法比较简单,而且容易阅读。
ODAContext ctx = new ODAContext();
var U = ctx.GetCmd<CmdSysUser>();
var R = ctx.GetCmd<CmdSysRole>();
var UR = ctx.GetCmd<CmdSysUserRole>();
var data = U.ListCmd(UR,R)
.Where(U.ColUserAccount == UR.ColUserAccount,
UR.ColStatus == "O",
UR.ColRoleCode == R.ColRoleCode,
R.ColStatus == "O",
U.ColStatus == "O",
R.ColRoleCode == "Administrator")
.Select< UserDefineModel>(U.ColUserAccount.As("UserAccount"), U.ColUserName.As("UserName"),U.ColEmailAddr.As("Email"), R.ColRoleCode.As("Role"), R.ColRoleName.As("RoleName"));
SELECT T0.USER_ACCOUNT AS UserAccount,
T0.USER_NAME AS UserName,
T0.EMAIL_ADDR AS Email,
T1.ROLE_CODE AS Role,
T1.ROLE_NAME AS RoleName
FROM SYS_USER T0, SYS_USER_ROLE T2, SYS_ROLE T1
WHERE T0.USER_ACCOUNT = T2.USER_ACCOUNT
AND T2.STATUS = @T3
AND T2.ROLE_CODE = T1.ROLE_CODE
AND T1.STATUS = @T4
AND T0.STATUS = @T5
AND T1.ROLE_CODE = @T6;
嵌套子查询
嵌套子查询需要把一个查询子句转换成视图(ToView方法),转换成视图之后可以把它视作普通的Cmd使用。<br/> 视图里ViewColumns是视图字段的集合。
ODAContext ctx = new ODAContext();
var U = ctx.GetCmd<CmdSysUser>();
var R = ctx.GetCmd<CmdSysRole>();
var UR = ctx.GetCmd<CmdSysUserRole>();
var UA = ctx.GetCmd<CmdSysUserAuthorization>();
var RA = ctx.GetCmd<CmdSysRoleAuthorization>();
var Admin = U.InnerJoin(UR, U.ColUserAccount == UR.ColUserAccount, UR.ColStatus == "O")
.InnerJoin(R, UR.ColRoleCode == R.ColRoleCode, R.ColStatus == "O")
.Where(U.ColStatus == "O")
.ToView(U.ColUserAccount.As("SYS_USER"), U.ColUserName, R
Related Skills
feishu-drive
338.0k|
things-mac
338.0kManage Things 3 via the `things` CLI on macOS (add/update projects+todos via URL scheme; read/search/list from the local Things database)
clawhub
338.0kUse the ClawHub CLI to search, install, update, and publish agent skills from clawhub.com
yu-ai-agent
1.9k编程导航 2025 年 AI 开发实战新项目,基于 Spring Boot 3 + Java 21 + Spring AI 构建 AI 恋爱大师应用和 ReAct 模式自主规划智能体YuManus,覆盖 AI 大模型接入、Spring AI 核心特性、Prompt 工程和优化、RAG 检索增强、向量数据库、Tool Calling 工具调用、MCP 模型上下文协议、AI Agent 开发(Manas Java 实现)、Cursor AI 工具等核心知识。用一套教程将程序员必知必会的 AI 技术一网打尽,帮你成为 AI 时代企业的香饽饽,给你的简历和求职大幅增加竞争力。
