项目里遇到一个数据交换的需求,申报端和审批端的系统和数据库都不在同一个网段;甲方提供一个msmq队列;我们把申报和审批产生的变化数据用sql记录到xml报文中,通过交换xml文件再解析出sql语句来实现数据同步。

  我们用的数据库是sqlserver,orm框架是EntityFramework。这就需要在每次提交增删改数据的时候都记录下操作数据库的sql。起初想用DbCommandInterceptor来拦截每个DbCommand中的CommandText,但是测试下来有个问题没能解决。业务中的数据基本都是批量提交的,而DbCommandInterceptor每次拦截到的都是单个命令,这样我就不能识别哪些CommandText是在同一个提交中产生的,这样就没法保证批量提交、事务提交中的若干条sql在交换到另外一端的时候也是批量执行的。

  另一种尝试。后来在上下文对象DbContext中找到了ChangeTracker,ChangeTracker里面的Entries是一个集合数据类型,每次Commit时有变化的数据都在这个Entries里面,遍历Entries时发现有State这个字段,State的值是一种枚举类型,Detached、Unchanged、Added、Deleted、Modified增删改都在里面了,于是有了办法。每次commit数据前,遍历context.ChangeTracker.Entries(),判断每个实体的变化状态按insert,delete,update不同分别生成sql。生成sql需要反射拿到每个实体的表名,字段名,还有数据类型,在ColumnAttribute类型里面都有。

DbUtils类的重要方法说明:

核心类:

  public class DbUtils
{
/// <summary>
/// 生成context里做了增删改操作的sql
/// </summary>
public List<string> CreateEditSql(DbContext context)
{
if (context == null) throw new ArgumentNullException("参数context为null");
List<string> list = new List<string>();
var ents = context.ChangeTracker.Entries();
if (ents != null)
{
foreach (var ent in ents)
{
string sql = null;
if (ent.State == EntityState.Added)
{
Type type = ent.Entity.GetType();
sql = CreateInsertSql(type, ent.Entity);
}
else if (ent.State == EntityState.Modified)
{
//如果dbcontext的ProxyCreationEnabled属性为true,则这里取到的type可能是System.Data.Entity.DynamicProxies.xxx类型的,要再取BaseType才是对应的实体的类型
//Type type = ent.Entity.GetType().BaseType;
//关闭ProxyCreationEnabled后这里取到的type就是实体本身的类型
//关闭ProxyCreationEnabled可在构造中设置Configuration.ProxyCreationEnabled = false;
Type type = ent.Entity.GetType();
sql = CreateUpdateSql(type, ent.Entity);
}
else if (ent.State == EntityState.Deleted)
{
//如果dbcontext的ProxyCreationEnabled属性为true,则这里取到的type可能是System.Data.Entity.DynamicProxies.xxx类型的,要再取BaseType才是对应的实体的类型
//Type type = ent.Entity.GetType().BaseType;
//关闭ProxyCreationEnabled后这里取到的type就是实体本身的类型
//关闭ProxyCreationEnabled可在构造中设置Configuration.ProxyCreationEnabled = false;
Type type = ent.Entity.GetType();
sql = CreateDeleteSql(type, ent.Entity);
}
if (sql.IsNotNullOrEmpty()) list.Add(sql);
}
}
return list;
} private string CreateInsertSql(Type type, object ent)
{
if (ent == null) return null;
string sql = null;
//判断正在操作的数据是不是从项目的基础类型继承的
if (typeof(BaseEntity).IsAssignableFrom(type))
{
List<AttrInfo> list = GetAttrInfos(ent);
var attrs = list.Where(l => l.AttrValue != null);
var kv = attrs.Select(a => new SqlKeyValue { FieldName = a.AttrName, FieldValue = GetSqlCodeValue(a.DbType, a.AttrValue) });
string names = kv.Select(k => k.FieldName).Aggregate((a, b) => $"{a},{b}");
string values = kv.Select(k => k.FieldValue).Aggregate((a, b) => $"{a},{b}");
sql = $"insert into {GetTableName(type)}({names}) values({values})";
}
return sql;
} private string CreateUpdateSql(Type type, object ent)
{
if (ent == null) return null;
string sql = null;
//判断正在操作的数据是不是从项目的基础类型继承的
if (typeof(BaseEntity).IsAssignableFrom(type))
{
string guid = GetPk(ent);
if (guid.IsNotNullOrEmpty())
{
List<AttrInfo> list = GetAttrInfos(ent);
var attrs = list.Where(l => !l.AttrName.Equals("Guid"));
//var kv = attrs.Select(a => new SqlKeyValue { FieldName = a.AttrName, FieldValue = GetSqlCodeValue(a.DbType, a.AttrValue) }).Select(s => $"{s.FieldName}={s.FieldValue}").Aggregate((a, b) => $"{a},{b}");
//优化,只更新不是null的属性值
var kv = attrs.Where(a => a.AttrValue != null).Select(a => new SqlKeyValue { FieldName = a.AttrName, FieldValue = GetSqlCodeValue(a.DbType, a.AttrValue) }).Select(s => $"{s.FieldName}={s.FieldValue}").Aggregate((a, b) => $"{a},{b}");
if (kv.IsNotNullOrEmpty())
sql = $"update {GetTableName(type)} set {kv} where guid = '{guid}'";
}
}
return sql;
} private string CreateDeleteSql(Type type, object ent)
{
if (ent == null) return null;
string sql = null;
//判断正在操作的数据是不是从项目的基础类型继承的
if (typeof(BaseEntity).IsAssignableFrom(type))
{
string guid = GetPk(ent);
if (guid.IsNotNullOrEmpty())
{
sql = $"delete from {GetTableName(type)} where guid = '{guid}'";
}
}
return sql;
} //根据不同数据类型拼sql的value值
private string GetSqlCodeValue(DbType dt, object value)
{
string rtn = "";
switch (dt)
{
case DbType.AnsiString: rtn = value == null ? "null" : $"'{value.ToString().Replace("'", "''")}'"; break;
case DbType.DateTime:
rtn = value == null ? "null" : $"'{Convert.ToDateTime(value).ToString("yyyy-MM-dd HH:mm:ss:fff")}'"; break;
case DbType.Int16:
case DbType.Int32:
case DbType.Int64:
case DbType.Decimal:
case DbType.Double: rtn = value == null ? "null" : value.ToString(); break;
case DbType.Boolean: rtn = value == null ? "null" : Convert.ToBoolean(value) ? "1" : "0"; break;
}
return rtn;
} /// <summary>
/// 取实体对应的表名
/// </summary>
private string GetTableName(Type t)
{
object[] objs = t.GetCustomAttributes(typeof(TableAttribute), false);
if (objs == null || objs.Count() <= 0)
{
throw new Exception($"{t.Name}未指定表名");
}
else
{
return ((TableAttribute)objs[0]).Name;
}
} /// <summary>
/// 获取实体主键值
/// </summary>
private string GetPk(object obj)
{
if (obj == null) return null;
PropertyInfo prop = obj.GetType().GetProperty("Guid");
if (prop == null) throw new Exception($"{obj.GetType()}没有主键字段");
object o = prop.GetValue(obj, null);
if (o != null) return Convert.ToString(o);
else return null;
} /// <summary>
/// 字段:名称与值的对应关系
/// </summary>
private List<AttrInfo> GetAttrInfos(object ent)
{
List<AttrInfo> list = new List<AttrInfo>();
if (ent != null)
{
DataType dtc = new DataType();
PropertyInfo[] props = ent.GetType().GetProperties();
if (props != null && props.Count() > 0)
{
//排除关联属性
props = props.Where(p => dtc.IsNormalBaseType(p.PropertyType.ToString())).ToArray();
list.AddRange(props.Select(p => new AttrInfo { AttrName = GetFieldName(p), AttrValue = p.GetValue(ent, null), DbType = dtc.PropertyTypeToDbType(p.PropertyType.ToString()) }));
}
}
return list;
} /// <summary>
/// 取属性对应的字段名,如果有Column标记就取Column标记里的字段名,否则取属性名
/// </summary>
private string GetFieldName(PropertyInfo prop)
{
string name = string.Empty;
if (prop != null)
{
var attr = prop.GetCustomAttribute<ColumnAttribute>();
if (attr != null)
{
name = attr.Name;
}
else
{
name = prop.Name;
}
}
return name;
} /// <summary>
/// 分面查询方法
/// </summary>
public IQueryable<T> GetPagedData<T>(IQueryable<T> orderQuery, int pageIndex, int pageSize)
{
int skip = (pageIndex - 1) * pageSize;
return orderQuery.Skip(skip).Take(pageSize);
}
}

  

辅助类:

internal class AttrInfo
{
/// <summary>
/// 数据类型
/// </summary>
public DbType DbType { get; set; } /// <summary>
/// 实体属性名
/// </summary>
public string AttrName { get; set; } /// <summary>
/// 实体属性值
/// </summary>
public object AttrValue { get; set; }
} internal class SqlKeyValue
{
public string FieldName { get; set; }
public string FieldValue { get; set; }
}

  

转自公众号:

记录EntityFramework增删改产生的SQL语句的更多相关文章

  1. 使用JDBC分别利用Statement和PreparedStatement来对MySQL数据库进行简单的增删改查以及SQL注入的原理

    一.MySQL数据库的下载及安装 https://www.mysql.com/ 点击DOWNLOADS,拉到页面底部,找到MySQL Community(GPL)Downloads,点击 选择下图中的 ...

  2. Django项目的创建与介绍.应用的创建与介绍.启动项目.pycharm创建启动项目.生命周期.三件套.静态文件.请求及数据.配置Mysql完成数据迁移.单表ORM记录的增删改查

    一.Django项目的创建与介绍 ''' 安装Django #在cmd中输入pip3 #出现这个错误Fatal error in launcher: Unable to create process ...

  3. mysql对库,表及记录的增删改查

    破解密码 #1.关闭mysqlnet stop mysqlmysql还在运行时需要输入命令关闭,也可以手动去服务关闭 #2.重新启动mysqld --skip-grant-tables跳过权限 #3m ...

  4. MySQL增删改查的常用语句汇总

    MySQL增删改查的常用语句汇总 以下是总结的mysql的常用语句,欢迎指正和补充~ 一.创建库,删除库,使用库 1.创建数据库:create database 库名; 2.删除数据库:drop da ...

  5. MySQL:记录的增删改查、单表查询、约束条件、多表查询、连表、子查询、pymysql模块、MySQL内置功能

    数据操作 插入数据(记录): 用insert: 补充:插入查询结果: insert into 表名(字段1,字段2,...字段n) select (字段1,字段2,...字段n) where ...; ...

  6. MySQL常用增删改查等操作语句

    修改数据库的字符集    mysql>use mydb    mysql>alter database mydb character set utf8;创建数据库指定数据库的字符集    ...

  7. MySQL数据库初识、下载使用(针对库、表、记录的增删改查)

    今日内容概要 数据演变史 数据库软件的本质 MySQL简介 下载与安装 基本配置 基本SQL语句 内容详细 1.数据演变史 # 1.单独的文本文件 没有固定的存放位置和格式 文件名:user.txt ...

  8. mysql 记录的增删改查

    MySQL数据操作: DML ======================================================== 在MySQL管理软件中,可以通过SQL语句中的DML语言 ...

  9. mysql基本的增删改查和条件语句

    增 insert into 表名(列名,列名......) values("test1",23),("test2",23),("test3" ...

  10. MySQL记录操作(增删改)

    概览 MySQL数据操作: DML 在MySQL管理软件中,可以通过SQL语句中的DML语言来实现数据的操作,包括 使用INSERT实现数据的插入 UPDATE实现数据的更新 使用DELETE实现数据 ...

随机推荐

  1. Docker部署rabbitmq遇到的问题

    1.背景 Docker部署rabbitmq遇到的如下两个问题 问题一:访问交换机时报错 Management API returned status code 500 问题二:访问channel时报错 ...

  2. maven实战教程-含视频讲解

    1.背景 2.什么是maven? 通俗的说就是,不用手动拷贝jar包,帮我们管理项目结构,只需要配置坐标,自动从中央仓库下载(其他介绍请百度...). 3.Maven的安装与配置 注意:Maven在使 ...

  3. 2023 ICPC网络赛第一场(A,D,J,L)

    2023 ICPC网络赛第一场(A,D,J,L) A Qualifiers Ranking Rules 先把两场比赛的学校排名处理出来,然后两场比赛的同位次进行合并即可 #include <bi ...

  4. 将整个工程的GBK转为utf-8格式

    eclipse将整个工程转为utf-8时原先中文注释会变为乱码,13年时写了个脚本将整个文件的java以及配置文件转为utf-8格式,下面是代码 package com.code.pd; import ...

  5. 十五分钟两百行代码,手写一个vue项目全局通用的弹框

    前言: 我们在写vue项目时,弹框是非常常用的组件,并且在同一个项目中,弹框大多类似.所以我们可以抽离封装出一个通用的弹框: 因为vue3可向下兼容,所以作者这边会使用vue2的写法,vue3写法大同 ...

  6. pc 移动端 双端切换-路由判断

    该封装主要以分类形式,实现对路由的简易区分.便于项目管理. 创建好项目,勾选路由插件,会自动生成 router文件夹与index.ts . index.ts 初始内容 创建项目 自动生成的router ...

  7. iptables 命令使用帮助总结

    本文为博主原创,转载请注明出处: 1.iptables 命令帮助参数 root@controller1:~# iptables --help iptables v1.6.1 Usage: iptabl ...

  8. manim边学边做--曲线类

    manim中曲线,除了前面介绍的圆弧类曲线,也可以绘制任意的曲线. manim中提供的CubicBezier模块,可以利用三次贝塞尔曲线的方式绘制任意曲线. 关于贝塞尔曲线的介绍,可以参考:https ...

  9. 【YashanDB数据库】yasql登录有特殊字符@导致无法登录

    问题备机 Linux bash shell环境下,使用yasql登录数据库没有使用转义导致登录失败.报错信息如下 问题分析 linux特殊字符转义问题,多加几层转义可以解决问题. 解决办法 su - ...

  10. Angular 18+ 高级教程 – Angular Configuration (angular.json)

    前言 记入一些基本的配置. Setup IP Address.SSL.Self-signed Certificate 如果你对 IP Address.SSL.Self-signed Certifica ...