前言

Linq 是 C# 中一个非常好用的集合处理库,用好了能帮我们简化大量又臭又长的嵌套循环,使处理逻辑清晰可见。EF 查询主要也是依赖 Linq。但是 Linq 相对 sql 也存在一些缺点,最主要的就是动态构造查询的难度。sql 只需要简单进行字符串拼接,操作难度很低(当然出错也相当容易),而 Linq 表达式由于对强类型表达式树的依赖,动态构造查询表达式基本相当于手写 AST(抽象语法树),可以说难度暴增。

AST 已经进入编译原理的领域,对计算机系统的了解程度需求比一般 crud 写业务代码高了几个量级,也导致很多人觉得 EF 不好用,为了写个动态查询要学编译原理这个代价还是挺高的。后来也有一些类似 DynamicLinq 的类库能用表达式字符串写动态查询。

本着学习精神,研究了一段时间,写了一个在我的想象力范围内,可以动态构造任意复杂的 Where 表达式的辅助类。这个辅助类的过滤条件使用了 JqGrid 的高级查询的数据结构,这是我第一个知道能生成复杂嵌套查询,并且查询数据使用 json 方便解析的 js 表格插件。可以无缝根据 JqGrid 的高级查询生成 Where 表达式。

正文

实现

JqGrid 高级查询数据结构定义,用来反序列化:

     public class JqGridParameter
{
/// <summary>
/// 是否搜索,本来应该是bool,true
/// </summary>
public string _search { get; set; }
/// <summary>
/// 请求发送次数,方便服务器处理重复请求
/// </summary>
public long Nd { get; set; }
/// <summary>
/// 当页数据条数
/// </summary>
public int Rows { get; set; }
/// <summary>
/// 页码
/// </summary>
public int Page { get; set; }
/// <summary>
/// 排序列,多列排序时为排序列名+空格+排序方式,多个列之间用逗号隔开。例:id asc,name desc
/// </summary>
public string Sidx { get; set; }
/// <summary>
/// 分离后的排序列
/// </summary>
public string[][] SIdx => Sidx.Split(", ").Select(s => s.Split(" ")).ToArray();
/// <summary>
/// 排序方式:asc、desc
/// </summary>
public string Sord { get; set; }
/// <summary>
/// 高级搜索条件json
/// </summary>
public string Filters { get; set; } /// <summary>
/// 序列化的高级搜索对象
/// </summary>
public JqGridSearchRuleGroup FilterObject => Filters.IsNullOrWhiteSpace()
? new JqGridSearchRuleGroup { Rules = new[] { new JqGridSearchRule { Op = SearchOper, Data = SearchString, Field = SearchField } } }
: JsonSerializer.Deserialize<JqGridSearchRuleGroup>(Filters ?? string.Empty); /// <summary>
/// 简单搜索字段
/// </summary>
public string SearchField { get; set; }
/// <summary>
/// 简单搜索关键字
/// </summary>
public string SearchString { get; set; }
/// <summary>
/// 简单搜索操作
/// </summary>
public string SearchOper { get; set; } } /// <summary>
/// 高级搜索条件组
/// </summary>
public class JqGridSearchRuleGroup
{
/// <summary>
/// 条件组合方式:and、or
/// </summary>
public string GroupOp { get; set; }
/// <summary>
/// 搜索条件集合
/// </summary>
public JqGridSearchRule[] Rules { get; set; }
/// <summary>
/// 搜索条件组集合
/// </summary>
public JqGridSearchRuleGroup[] Groups { get; set; }
} /// <summary>
/// 高级搜索条件
/// </summary>
public class JqGridSearchRule
{
/// <summary>
/// 搜索字段
/// </summary>
public string Field { get; set; }
/// <summary>
/// 搜索字段的大驼峰命名
/// </summary>
public string PascalField => Field?.Length > ? Field.Substring(, ).ToUpper() + Field.Substring() : Field;
/// <summary>
/// 搜索操作
/// </summary>
public string Op { get; set; }
/// <summary>
/// 搜索关键字
/// </summary>
public string Data { get; set; }
}

Where 条件生成器,代码有点多,有点复杂。不过注释也很多,稍微耐心点应该不难看懂:

     /// <summary>
/// JqGrid搜索表达式扩展
/// </summary>
public static class JqGridSearchExtensions
{
//前端的(不)属于条件搜索需要传递一个json数组的字符串作为参数
//为了避免在搜索字符串的时候分隔符是搜索内容的一部分导致搜索关键字出错
//无论定义什么分隔符都不能完全避免这种尴尬的情况,所以使用标准的json以绝后患
/// <summary>
/// 根据搜索条件构造where表达式,支持JqGrid高级搜索
/// </summary>
/// <typeparam name="T">搜索的对象类型</typeparam>
/// <param name="ruleGroup">JqGrid搜索条件组</param>
/// <param name="propertyMap">属性映射,把搜索规则的名称映射到属性名称,如果属性是复杂类型,使用点号可以继续访问内部属性</param>
/// <returns>where表达式</returns>
public static Expression<Func<T, bool>> BuildWhere<T>(JqGridSearchRuleGroup ruleGroup, IDictionary<string, string> propertyMap)
{
ParameterExpression parameter = Expression.Parameter(typeof(T), "searchObject"); return Expression.Lambda<Func<T, bool>>(BuildGroupExpression<T>(ruleGroup, parameter, propertyMap), parameter);
} /// <summary>
/// 构造搜索条件组的表达式(一个组中可能包含若干子条件组)
/// </summary>
/// <typeparam name="T">搜索的对象类型</typeparam>
/// <param name="group">条件组</param>
/// <param name="parameter">参数表达式</param>
/// <param name="propertyMap">属性映射</param>
/// <returns>返回bool的条件组的表达式</returns>
private static Expression BuildGroupExpression<T>(JqGridSearchRuleGroup group, ParameterExpression parameter, IDictionary<string, string> propertyMap)
{
List<Expression> expressions = new List<Expression>();
foreach (var rule in group.Rules ?? new JqGridSearchRule[])
{
expressions.Add(BuildRuleExpression<T>(rule, parameter, propertyMap));
} foreach (var subGroup in group.Groups ?? new JqGridSearchRuleGroup[])
{
expressions.Add(BuildGroupExpression<T>(subGroup, parameter, propertyMap));
} if (expressions.Count == )
{
throw new InvalidOperationException("构造where子句异常,生成了0个比较条件表达式。");
} if (expressions.Count == )
{
return expressions[];
} var expression = expressions[];
switch (group.GroupOp)
{
case "AND":
foreach (var exp in expressions.Skip())
{
expression = Expression.AndAlso(expression, exp);
}
break;
case "OR":
foreach (var exp in expressions.Skip())
{
expression = Expression.OrElse(expression, exp);
}
break;
default:
throw new InvalidOperationException($"不支持创建{group.GroupOp}类型的逻辑运算表达式");
} return expression;
} private static readonly string[] SpecialRuleOps = {"in", "ni", "nu", "nn"}; /// <summary>
/// 构造条件表达式
/// </summary>
/// <typeparam name="T">搜索的对象类型</typeparam>
/// <param name="rule">条件</param>
/// <param name="parameter">参数</param>
/// <param name="propertyMap">属性映射</param>
/// <returns>返回bool的条件表达式</returns>
private static Expression BuildRuleExpression<T>(JqGridSearchRule rule, ParameterExpression parameter,
IDictionary<string, string> propertyMap)
{
Expression l; string[] names = null;
//如果实体属性名称和前端名称不一致,或者属性是一个自定义类型,需要继续访问其内部属性,使用点号分隔
if (propertyMap?.ContainsKey(rule.Field) == true)
{
names = propertyMap[rule.Field].Split('.', StringSplitOptions.RemoveEmptyEntries);
l = Expression.Property(parameter, names[]);
foreach (var name in names.Skip())
{
l = Expression.Property(l, name);
}
}
else
{
l = Expression.Property(parameter, rule.PascalField);
} Expression r = null; //值表达式
Expression e; //返回bool的各种比较表达式 //属于和不属于比较是多值比较,需要调用Contains方法,而不是调用比较操作符
//为空和不为空的右值为常量null,不需要构造
var specialRuleOps = SpecialRuleOps; var isNullable = false;
var pt = typeof(T);
if(names != null)
{
foreach(var name in names)
{
pt = pt.GetProperty(name).PropertyType;
}
}
else
{
pt = pt.GetProperty(rule.PascalField).PropertyType;
} //如果属性类型是可空值类型,取出内部类型
if (pt.IsDerivedFrom(typeof(Nullable<>)))
{
isNullable = true;
pt = pt.GenericTypeArguments[];
} //根据属性类型创建要比较的常量值表达式(也就是r)
if (!specialRuleOps.Contains(rule.Op))
{
switch (pt)
{
case Type ct when ct == typeof(bool):
r = BuildConstantExpression(rule, bool.Parse);
break; #region 文字 case Type ct when ct == typeof(char):
r = BuildConstantExpression(rule, str => str[]);
break;
case Type ct when ct == typeof(string):
r = BuildConstantExpression(rule, str => str);
break; #endregion #region 有符号整数 case Type ct when ct == typeof(sbyte):
r = BuildConstantExpression(rule, sbyte.Parse);
break;
case Type ct when ct == typeof(short):
r = BuildConstantExpression(rule, short.Parse);
break;
case Type ct when ct == typeof(int):
r = BuildConstantExpression(rule, int.Parse);
break;
case Type ct when ct == typeof(long):
r = BuildConstantExpression(rule, long.Parse);
break; #endregion #region 无符号整数 case Type ct when ct == typeof(byte):
r = BuildConstantExpression(rule, byte.Parse);
break;
case Type ct when ct == typeof(ushort):
r = BuildConstantExpression(rule, ushort.Parse);
break;
case Type ct when ct == typeof(uint):
r = BuildConstantExpression(rule, uint.Parse);
break;
case Type ct when ct == typeof(ulong):
r = BuildConstantExpression(rule, ulong.Parse);
break; #endregion #region 小数 case Type ct when ct == typeof(float):
r = BuildConstantExpression(rule, float.Parse);
break;
case Type ct when ct == typeof(double):
r = BuildConstantExpression(rule, double.Parse);
break;
case Type ct when ct == typeof(decimal):
r = BuildConstantExpression(rule, decimal.Parse);
break; #endregion #region 其它常用类型 case Type ct when ct == typeof(DateTime):
r = BuildConstantExpression(rule, DateTime.Parse);
break;
case Type ct when ct == typeof(DateTimeOffset):
r = BuildConstantExpression(rule, DateTimeOffset.Parse);
break;
case Type ct when ct == typeof(Guid):
r = BuildConstantExpression(rule, Guid.Parse);
break;
case Type ct when ct.IsEnum:
r = Expression.Constant(rule.Data.ToEnumObject(ct));
break; #endregion default:
throw new InvalidOperationException($"不支持创建{pt.FullName}类型的数据表达式");
}
} if (r != null && pt.IsValueType && isNullable)
{
var gt = typeof(Nullable<>).MakeGenericType(pt);
r = Expression.Convert(r, gt);
} switch (rule.Op)
{
case "eq": //等于
e = Expression.Equal(l, r);
break;
case "ne": //不等于
e = Expression.NotEqual(l, r);
break;
case "lt": //小于
e = Expression.LessThan(l, r);
break;
case "le": //小于等于
e = Expression.LessThanOrEqual(l, r);
break;
case "gt": //大于
e = Expression.GreaterThan(l, r);
break;
case "ge": //大于等于
e = Expression.GreaterThanOrEqual(l, r);
break;
case "bw": //开头是(字符串)
if (pt == typeof(string))
{
e = Expression.Call(l, pt.GetMethod(nameof(string.StartsWith), new[] {typeof(string)}), r);
}
else
{
throw new InvalidOperationException($"不支持创建{pt.FullName}类型的开始于表达式");
} break;
case "bn": //开头不是(字符串)
if (pt == typeof(string))
{
e = Expression.Not(Expression.Call(l, pt.GetMethod(nameof(string.StartsWith), new[] {typeof(string)}), r));
}
else
{
throw new InvalidOperationException($"不支持创建{pt.FullName}类型的不开始于表达式");
} break;
case "ew": //结尾是(字符串)
if (pt == typeof(string))
{
e = Expression.Call(l, pt.GetMethod(nameof(string.EndsWith), new[] {typeof(string)}), r);
}
else
{
throw new InvalidOperationException($"不支持创建{pt.FullName}类型的结束于表达式");
} break;
case "en": //结尾不是(字符串)
if (pt == typeof(string))
{
e = Expression.Not(Expression.Call(l, pt.GetMethod(nameof(string.EndsWith), new[] {typeof(string)}), r));
}
else
{
throw new InvalidOperationException($"不支持创建{pt.FullName}类型的不结束于表达式");
} break;
case "cn": //包含(字符串)
if (pt == typeof(string))
{
e = Expression.Call(l, pt.GetMethod(nameof(string.Contains), new[] {typeof(string)}), r);
}
else
{
throw new InvalidOperationException($"不支持创建{pt.FullName}类型的包含表达式");
} break;
case "nc": //不包含(字符串)
if (pt == typeof(string))
{
e = Expression.Not(Expression.Call(l, pt.GetMethod(nameof(string.Contains), new[] {typeof(string)}), r));
}
else
{
throw new InvalidOperationException($"不支持创建{pt.FullName}类型的包含表达式");
} break;
case "in": //属于(是候选值列表之一)
e = BuildContainsExpression(rule, l, pt);
break;
case "ni": //不属于(不是候选值列表之一)
e = Expression.Not(BuildContainsExpression(rule, l, pt));
break;
case "nu": //为空
r = Expression.Constant(null);
e = Expression.Equal(l, r);
break;
case "nn": //不为空
r = Expression.Constant(null);
e = Expression.Not(Expression.Equal(l, r));
break;
case "bt": //区间
throw new NotImplementedException($"尚未实现创建{rule.Op}类型的比较表达式");
default:
throw new InvalidOperationException($"不支持创建{rule.Op}类型的比较表达式");
} return e; static Expression BuildConstantExpression<TValue>(JqGridSearchRule jRule, Func<string, TValue> valueConvertor)
{
var rv = valueConvertor(jRule.Data);
return Expression.Constant(rv);
}
} /// <summary>
/// 构造Contains调用表达式
/// </summary>
/// <param name="rule">条件</param>
/// <param name="parameter">参数</param>
/// <param name="parameterType">参数类型</param>
/// <returns>Contains调用表达式</returns>
private static Expression BuildContainsExpression(JqGridSearchRule rule, Expression parameter, Type parameterType)
{
Expression e = null; var genMethod = typeof(Queryable).GetMethods()
.Single(m => m.Name == nameof(Queryable.Contains) && m.GetParameters().Length == ); var jsonArray = JsonSerializer.Deserialize<string[]>(rule.Data); switch (parameterType)
{
#region 文字 case Type ct when ct == typeof(char):
if (jsonArray.Any(o => o.Length != )) {throw new InvalidOperationException("字符型的候选列表中存在错误的候选项");}
e = CallContains(parameter, jsonArray, str => str[], genMethod, ct);
break;
case Type ct when ct == typeof(string):
e = CallContains(parameter, jsonArray, str => str, genMethod, ct);
break; #endregion #region 有符号整数 case Type ct when ct == typeof(sbyte):
e = CallContains(parameter, jsonArray, sbyte.Parse, genMethod, ct);
break;
case Type ct when ct == typeof(short):
e = CallContains(parameter, jsonArray, short.Parse, genMethod, ct);
break;
case Type ct when ct == typeof(int):
e = CallContains(parameter, jsonArray, int.Parse, genMethod, ct);
break;
case Type ct when ct == typeof(long):
e = CallContains(parameter, jsonArray, long.Parse, genMethod, ct);
break; #endregion #region 无符号整数 case Type ct when ct == typeof(byte):
e = CallContains(parameter, jsonArray, byte.Parse, genMethod, ct);
break;
case Type ct when ct == typeof(ushort):
e = CallContains(parameter, jsonArray, ushort.Parse, genMethod, ct);
break;
case Type ct when ct == typeof(uint):
e = CallContains(parameter, jsonArray, uint.Parse, genMethod, ct);
break;
case Type ct when ct == typeof(ulong):
e = CallContains(parameter, jsonArray, ulong.Parse, genMethod, ct);
break; #endregion #region 小数 case Type ct when ct == typeof(float):
e = CallContains(parameter, jsonArray, float.Parse, genMethod, ct);
break;
case Type ct when ct == typeof(double):
e = CallContains(parameter, jsonArray, double.Parse, genMethod, ct);
break;
case Type ct when ct == typeof(decimal):
e = CallContains(parameter, jsonArray, decimal.Parse, genMethod, ct);
break; #endregion #region 其它常用类型 case Type ct when ct == typeof(DateTime):
e = CallContains(parameter, jsonArray, DateTime.Parse, genMethod, ct);
break;
case Type ct when ct == typeof(DateTimeOffset):
e = CallContains(parameter, jsonArray, DateTimeOffset.Parse, genMethod, ct);
break;
case Type ct when ct == typeof(Guid):
e = CallContains(parameter, jsonArray, Guid.Parse, genMethod, ct);
break;
case Type ct when ct.IsEnum:
e = CallContains(Expression.Convert(parameter, typeof(object)), jsonArray, enumString => enumString.ToEnumObject(ct), genMethod, ct);
break; #endregion
} return e; static MethodCallExpression CallContains<T>(Expression pa, string[] jArray, Func<string, T> selector, MethodInfo genericMethod, Type type)
{
var data = jArray.Select(selector).ToArray().AsQueryable();
var method = genericMethod.MakeGenericMethod(type); return Expression.Call(null, method, new[] { Expression.Constant(data), pa });
}
}
}

使用

此处是在 Razor Page 中使用,内部使用的其他辅助类和前端页面代码就不贴了,有兴趣的可以在我的文章末尾找到 GitHub 项目链接:

         public async Task<IActionResult> OnGetUserListAsync([FromQuery]JqGridParameter jqGridParameter)
{
var usersQuery = _userManager.Users.AsNoTracking();
if (jqGridParameter._search == "true")
{
usersQuery = usersQuery.Where(BuildWhere<ApplicationUser>(jqGridParameter.FilterObject, null));
} var users = usersQuery.Include(u => u.UserRoles).ThenInclude(ur => ur.Role).OrderBy(u => u.InsertOrder)
.Skip((jqGridParameter.Page - ) * jqGridParameter.Rows).Take(jqGridParameter.Rows).ToList();
var userCount = usersQuery.Count();
var pageCount = Ceiling((double) userCount / jqGridParameter.Rows);
return new JsonResult(
new
{
rows //数据集合
= users.Select(u => new
{
u.UserName,
u.Gender,
u.Email,
u.PhoneNumber,
u.EmailConfirmed,
u.PhoneNumberConfirmed,
u.CreationTime,
u.CreatorId,
u.Active,
u.LastModificationTime,
u.LastModifierId,
u.InsertOrder,
u.ConcurrencyStamp,
//以下为JqGrid中必须的字段
u.Id //记录的唯一标识,可在插件中配置为其它字段,但是必须能作为记录的唯一标识用,不能重复
}),
total = pageCount, //总页数
page = jqGridParameter.Page, //当前页码
records = userCount //总记录数
}
);
}

启动项目后访问 /Identity/Manage/Users/Index 可以尝试使用。

结语

通过这次实践,深入了解了很多表达式树的相关知识,表达式树在编译流程中还算是高级结构了,耐点心还是能看懂,IL 才是真的晕,比原生汇编也好不到哪里去。C# 确实很有意思,入门简单,内部却深邃无比,在小白和大神手上完全是两种语言。Java 在 Java 8 时增加了 Stream 和 Lambda 表达式功能,一看就是在对标 Linq,不过那名字取的真是一言难尽,看代码写代码感觉如鲠在喉,相当不爽。由于 Stream 体系缺少表达式树,这种动态构造查询表达式的功能从一开始就不可能支持。再加上 Java 没有匿名类型,没有对象初始化器,每次用 Stream 就难受的一批,中间过程的数据结构也要专门写类,每个中间类还要独占一个文件,简直晕死。抄都抄不及格!

C# 引入 var 关键字核心是为匿名类型服务,毕竟是编译器自动生成的类型,写代码的时候根本没有名字,不用 var 用什么?简化变量初始化代码只是顺带的。结果 Java 又抄一半,还是最不打紧的一半,简化变量初始化代码。真不知道搞 Java 的那帮人在想些什么。

转载请完整保留以下内容并在显眼位置标注,未经授权删除以下内容进行转载盗用的,保留追究法律责任的权利!

  本文地址:https://www.cnblogs.com/coredx/p/12423929.html

  完整源代码:Github

  里面有各种小东西,这只是其中之一,不嫌弃的话可以Star一下。

动态构造任意复杂的 Linq Where 表达式的更多相关文章

  1. Linq to Sql : 动态构造Expression进行动态查询

    原文:Linq to Sql : 动态构造Expression进行动态查询 前一篇在介绍动态查询时,提到一个问题:如何根据用户的输入条件,动态构造这个过滤条件表达式呢?Expression<Fu ...

  2. [2014-12-30]如何动态构造Lambda表达式(动态构造Lambda查询条件表达式)

    声明 本文对Lambda表达式的扩展,示例代码来源于网络. 场景描述 web开发查询功能的时候,如果查询条件比较多,就会遇到动态组合查询条件的情况.在手写sql的情况下,我们一般会根据传入的参数,针对 ...

  3. [C#.NET 拾遗补漏]13:动态构建LINQ查询表达式

    最近工作中遇到一个这样的需求:在某个列表查询功能中,可以选择某个数字列(如商品单价.当天销售额.当月销售额等),再选择 小于或等于 和 大于或等于 ,再填写一个待比较的数值,对数据进行查询过滤. 如果 ...

  4. EntityFramework 动态构造排序 Func<IQueryable<T>, IOrderedQueryable<T>> Dynamic

    using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; us ...

  5. LINQ查询表达式---------where子句

    LINQ查询表达式---------where子句 where 子句用在查询表达式中,用于指定将在查询表达式中返回数据源中的哪些元素. 它将一个布尔条件(“谓词”)应用于每个源元素(由范围变量引用), ...

  6. C# LINQ查询表达式用法对应Lambda表达式

    C#编程语言非常优美,我个人还是非常赞同的.特别是在学习一段时间C#后发现确实在它的语法和美观度来说确实要比其它编程语言强一些(也可能是由于VS编译器的加持)用起来非常舒服,而且对于C#我觉得他最优美 ...

  7. Linq专题之创建Linq查询表达式

    本节我们主要介绍一下如何创建查询集合类型,关系数据库类型,DataSet对象类型和XML类型的数据源的Linq查询表达式. 下面在实例代码ReadyCollectionData()函数创建了准备的数据 ...

  8. Linq lamda表达式Single和First方法

      让我们来看看如何对一个整数数组使用 Single 操作符.这个整数数组的每个元素代表 2 的 1 到 10 次方.先创建此数组,然后使用 Single 操作符来检索满足 Linq Lambda表达 ...

  9. 2.3 LINQ查询表达式中 使用select子句 指定目标数据

    本篇讲解LINQ查询的三种形式: 查询对象 自定义查询对象某个属性 查询匿名类型结果 [1.查询结果返回集合元素] 在LINQ查询中,select子句和from子句都是必备子句.LINQ查询表达式必须 ...

随机推荐

  1. Matlab高级教程_第二篇:MATLAB和C#一些常用的矩阵运算方法的转换

    1.相关方法已经生产引用,直接调用的结果如下: 2. 相关调用代码如下: using System; using System.Collections.Generic; using System.Li ...

  2. Maven 仓库搜索服务和私服搭建

    Maven 仓库搜索服务 使用maven进行日常开发的时候,一个常见问题就是如何寻找需要的依赖,我们可能只知道需要使用类库的项目名称,但是添加maven依赖要求提供确切的maven坐标,这时就可以使用 ...

  3. 吴裕雄--天生自然 JAVA开发学习: 泛型

    public class GenericMethodTest { // 泛型方法 printArray public static < E > void printArray( E[] i ...

  4. EXAM-2018-7-24

    EXAM-2018-7-24 未完成 [ ] G 签到水题 A J F A:英文字母有2426个 J:注意long long D:Transit Tree Path 我直接套了单源最短路的一个模板,有 ...

  5. Facebook要做约会服务,国内社交眼红吗?

    看看现在的各种相亲趣事就能深深感悟到,中国还是以家庭为重的国家.在传统文化的浸染下,国人始终是将家庭摆在第一位.而对于欧美等发达国家来说,他们固然也以家庭为重,但更注重的是男女之间的关系定位--恋爱也 ...

  6. VM Storage Policies深度解析

  7. import org.apache.commons.codec.binary.Base64;

    import org.apache.commons.codec.binary.Base64;

  8. python3下应用pymysql(第三卷)(数据自增-用于爬虫)

    在上卷中我说出两种方法进行数据去重自增,第一种就是在数据库的字段中设置唯一字段,二是在脚本语言中设置重复判断再添加(建议,二者同时使用,真正开发中就会用到) 话不多说先上代码 第一步: 确定那一字段的 ...

  9. jenkins使用(3)-设置定时任务

    配置时间如果错误,代码不会运行 *表示任意时刻

  10. 吴裕雄--天生自然HTML学习笔记:HTML 框架

    通过使用框架,你可以在同一个浏览器窗口中显示不止一个页面. iframe语法: <iframe src="URL"></iframe> 该URL指向不同的网 ...