ABP+AdminLTE+Bootstrap Table权限管理系统一期

Github:https://github.com/Jimmey-Jiang/ABP-ASP.NET-Boilerplate-Project-CMS

Quartz简介

Quartz.NET是一个开源的作业调度框架,是 OpenSymphonyQuartz API 的.NET移植,它用C#写成,可用于winformasp.net应用中。它提供了巨大的灵活性而不牺牲简单性。你能够用它来为执行一个作业而创建简单的或复杂的调度。它有很多特征,如:数据库支持,集群,插件,支持cron-like表达式等等。非常适合在平时的工作中,定时轮询数据库同步,定时邮件通知,定时处理数据等.

参考

对Quartz.NET不熟悉的可以先看下

官方学习文档:http://www.quartz-scheduler.net/documentation/index.html

使用实例介绍:http://www.quartz-scheduler.net/documentation/quartz-2.x/quick-start.html

官方的源代码下载:http://sourceforge.net/projects/quartznet/files/quartznet/

特性

它一些很好的特性:

  1. 支持集群,作业分组,作业远程管理。
  2. 自定义精细的时间触发器,使用简单,作业和触发分离。
  3. 数据库支持,可以寄宿Windows服务,WebSitewinform等。

Quartz框架的一些基础概念解释:

名称 描述
Scheduler 作业调度器。
IJob 作业接口,继承并实现Execute, 编写执行的具体作业逻辑。
JobBuilder 根据设置,生成一个详细作业信息(JobDetail)。
TriggerBuilder 根据规则,生产对应的Trig

实战

  • Web.config配置

基础概念我就懒得讲了,你也懒得听了,直接上代码吧。

首先配置一下Web.config,其实这步可有可无,可以直接跳过,只是为了配置一些常量,获取固定的时间,但是不是必要的。

    <sectionGroup name="JobList">
<section name="Job" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
</sectionGroup>
  <JobList>
<Job>
<!--这里是一个任务节点-->
<add key="Url" value="http://www.baidu.com" />
<!--需要访问的Url-->
<add key="Hour" value="10" />
<!--开始时间小时-->
<!--开始时间小时,注意:这里的小时为0-23,如果是1点的话就是1,而不是01-->
<add key="Minute" value="30" />
<!--开始时间分钟-->
<!--开始时间分钟,注意:同上0-59-->
</Job>
</JobList>
  • 创建service

    创建ISystemSchedulerService以及SystemSchedulerService,代码上面都有详细的注释,我就不重复了。

    接口:
public  interface ISystemSchedulerService: IApplicationService
{
void StartScheduler();
}

service: SystemSchedulerService

    public class SystemSchedulerService : ISystemSchedulerService
{
private IScheduler _scheduler;
public ILogger _Logger { get; set; }
public SystemSchedulerService()
{
_Logger = NullLogger.Instance;
} public void StopScheduler()
{
_scheduler.Shutdown();
} public void StartScheduler()
{
try
{ //这里读取配置文件中的任务开始时间
int hour = int.Parse(((NameValueCollection)ConfigurationManager.GetSection("JobList/Job"))["Hour"]);
int minute = int.Parse(((NameValueCollection)ConfigurationManager.GetSection("JobList/Job"))["Minute"]); ISchedulerFactory schedulerFactory = new StdSchedulerFactory();//内存调度
_scheduler = schedulerFactory.GetScheduler(); //创建一个Job来执行特定的任务
IJobDetail myLogJob = new JobDetailImpl("myLogJob", typeof(MyLogJob));
//创建并定义触发器的规则(每天执行一次时间为:时:分)
ITrigger trigger =
TriggerBuilder.Create()
.WithDailyTimeIntervalSchedule(
a => a.WithIntervalInHours(24).OnEveryDay().StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(hour, minute))).Build(); _scheduler.Clear();
//将创建好的任务和触发规则加入到Quartz中
_scheduler.ScheduleJob(myLogJob, trigger);
//开始
_scheduler.Start(); }
catch (Exception ex)
{
_Logger.Info(ex.Message);
}
} }

Quartz API

Quartz API的关键接口和类是:

  • IScheduler - 与调度程序交互的主要API。

    -IJob - 您希望由调度程序执行的组件实现的接口。
  • IJobDetail - 用于定义作业的实例。
  • ITrigger - 定义执行给定Job的时间表的组件。
  • JobBuilder - 用于定义/构建obDetail实例,它定义了Jobs的实例。
  • TriggerBuilder - 用于定义/构建触发器实例。

一个调度程序的生命周期是由它为界的创作,通过SchedulerFactory和其有关方法的调用。一旦创建了IScheduler接口,就可以使用添加,删除和列出作业和触发器,并执行其他与调度相关的操作(例如暂停触发器)。但是,调度程序不会实际上对任何触发器(执行作业)执行操作,直到使用Start()方法启动它。

构建作业定义的代码块使用使用流畅接口的JobBuilder创建产品IJobDetail。同样,构建触发器的代码块使用TriggerBuilder流畅的接口和特定于触发器类型的扩展方法。可能的时间延长方法是:

  • WithCalendarIntervalSchedule
  • WithCronSchedule
  • WithDailyTimeIntervalSchedule
  • WithSimpleSchedule

示例中的ITrigger 其实可以不读取配置信息的。这就是我说的不用配置Web.config也可以。效果如下,每隔10秒执行一次。

  ITrigger trigger =
TriggerBuilder.Create()
.WithDailyTimeIntervalSchedule(
a => a.WithIntervalInSeconds(10)).Build();

工作和触发器

代码:

  public class MyLogJob : JobBase, ITransientDependency
{
public ILogger _Logger { get; set; }
public MyLogJob()
{
_Logger = NullLogger.Instance; } public override void Execute(IJobExecutionContext context)
{
try
{
_Logger.Info("QuartzJob 任务开始运行"); for (int i = 0; i < 10; i++)
{
_Logger.InfoFormat("QuartzJob 正在运行{0}", i);
} _Logger.Info("QuartzJob任务运行结束");
}
catch (Exception ex)
{
_Logger.Error("运行异常:"+ex.Message, ex);
} }
}

当作业的触发器触发,Execute(..)方法由调度程序的工作线程之一调用。传递给此方法的JobExecutionContext对象为作业实例提供有关其“运行时”环境的信息 ( 调度程序执行的逻辑),触发执行的触发器的逻辑。

JobDetail对象是在Job添加到调度器时由Quartz.NET客户端创建的。它包含Job的各种属性设置,以及一个JobDataMap,它可以用来存储作业类的给定实例的状态信息

触发器对象用于触发作业的执行(或“触发”)。当你想安排一个工作,你实例化一个触发器并“调整”它的属性来提供你想要的调度。触发器也可能有一个与它们相关的JobDataMap - 这对于传递参数到一个特定于触发器的触发的Job是很有用的。Quartz提供了一些不同的触发器类型,但最常用的类型是SimpleTrigger(接口ISimpleTrigger)和CronTrigger(接口ICronTrigger)。

如果您需要“一次性”执行(在某个特定时间只执行一项作业),或者您需要在给定时间开始工作,并且重复执行N次,SimpleTrigger会很方便的T之间执行。如果您希望基于类似日历的时间表(例如“每个星期五,中午”或“每个月的第10天的10:15”)触发,则CronTrigger非常有用。

    public class UserInfoController : ABPCMSControllerBase
{ private readonly ISystemSchedulerService _iSystemSchedulerService;
public UserInfoController(ISystemSchedulerService iSystemSchedulerService)
{ _iSystemSchedulerService = iSystemSchedulerService;
} [HttpGet]
[DontWrapResult]
public async Task<ActionResult> GetUserInfo()
{
_iSystemSchedulerService.StartScheduler();
}
}

然后我们在前台控制器里面调用ISystemSchedulerService StartScheduler()方法。然后运行项目。

然后我们打开日志Web\App_Data\Logs\Logs.txt,看下效果:



每隔十秒钟执行一次。

Abp.Quartz

ABP有內建的持久化后台job队列和后台worker系统。如果对于后台workers你有更高级的计划安排需求,Quartz会是一个更好的选择。对于持久化后台job队列,Hangfire也是一个好的选择。

ABP中Quartz应用其实蛮简单的。

  1. 首先Abp.Quartz nuget包
  2. 然后加上引入到项目中。
   [DependsOn(
typeof(AbpQuartzModule)
)]
public class ABPCMSWebModule : AbpModule
{
}
  1. 创建Jobs

     这里我们就用上面例子中的job,当然创建一个新job,你可以实现QuartzIJob接口,或者继承JobBase类(定义在Abp.Quartz包),这个类包含一些帮助属性和方法(例如日志和本地化)。
public class MyLogJob : JobBase, ITransientDependency
{
public override void Execute(IJobExecutionContext context)
{
Logger.Info("Executed MyLogJob :)");
}
}

4.创建调度作业

IQuartzScheduleJobManager 接口被用来创建调度作业。你可以在类中注入该接口(或者你可以在你的模块的PostInitialize方法中解析和使用它)来调度作业。这里我们把之前创建的MyLogJob引用进来。

效果如下:

  public async Task<ActionResult> ScheduleJob()
{
await _jobManager.ScheduleAsync<MyLogJob>(
job =>
{
job.WithIdentity("MyLogJobIdentity", "MyGroup")
.WithDescription("A job to simply write logs.");
},
trigger =>
{
trigger.StartNow()
.WithSimpleSchedule(schedule =>
{
schedule.RepeatForever()
.WithIntervalInSeconds(5)
.Build();
});
});
_jobManager.Start();
return Content("OK, scheduled!");
}

效果一样。



经过上面四步,就完成Abp.Quartz运用,是挺简单的。

扩展

SimpleTrigger

说一下SimpleTriggerWithCronSchedule()这里有三种方式,一种就是上面说到的Web.config配置,一种是WithCronSchedule设置时间参数。还有一种是WithCronSchedule("") 拥有强大的Cron时间表达式。

SimpleTrigger应满足你的日程安排需求,如果你需要在某个特定时刻及时执行一次作业,或者在特定时间执行一次,然后在特定时间间隔重复执行。如果你想让触发器在2018年1月13日上午11点23分54秒执行,然后再执行5次,每10秒执行一次。

通过这个描述,你可能不会觉得奇怪的是,SimpleTrigger的属性包括:开始时间和结束时间,重复计数和重复间隔。所有这些属性都与您所期望的完全相同,只有一些与结束时间属性相关的特殊注释。

SimpleTrigger实例是使用TriggerBuilder(用于触发器的主属性)和WithSimpleSchedule扩展方法(用于SimpleTrigger特有的属性)构建的。

在特定的时刻建立一个触发器,不要重复:

// trigger builder creates simple trigger by default, actually an ITrigger is returned
ISimpleTrigger trigger = (ISimpleTrigger) TriggerBuilder.Create()
.WithIdentity("trigger1", "group1")
.StartAt(myStartTime) // some Date
.ForJob("job1", "group1") // identify job with name, group strings
.Build();

建立一个特定时刻的触发器,然后每十秒钟重复十次:

trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.StartAt(myTimeToStartFiring) // if a start time is not given (if this line were omitted), "now" is implied
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(10)
.WithRepeatCount(10)) // note that 10 repeats will give a total of 11 firings
.ForJob(myJob) // identify job with handle to its JobDetail itself
.Build();

建立一个触发器,将在未来五分钟内触发一次:

trigger = (ISimpleTrigger) TriggerBuilder.Create()
.WithIdentity("trigger5", "group1")
.StartAt(DateBuilder.FutureDate(5, IntervalUnit.Minute)) // use DateBuilder to create a date in the future
.ForJob(myJobKey) // identify job with its JobKey
.Build();

建立一个触发器,现在会触发,然后每隔五分钟重复一次,直到22:00:

trigger = TriggerBuilder.Create()
.WithIdentity("trigger7", "group1")
.WithSimpleSchedule(x => x
.WithIntervalInMinutes(5)
.RepeatForever())
.EndAt(DateBuilder.DateOf(22, 0, 0))
.Build();

建立一个触发器,将在下一个小时的顶部执行,然后每2小时重复一次:

trigger = TriggerBuilder.Create()
.WithIdentity("trigger8") // because group is not specified, "trigger8" will be in the default group
.StartAt(DateBuilder.EvenHourDate(null)) // get the next even-hour (minutes and seconds zero ("00:00"))
.WithSimpleSchedule(x => x
.WithIntervalInHours(2)
.RepeatForever())
// note that in this example, 'forJob(..)' is not called
// - which is valid if the trigger is passed to the scheduler along with the job
.Build(); await scheduler.scheduleJob(trigger, job);

另外一些常用的停止作业的指令常量

  • MisfireInstruction.IgnoreMisfirePolicy
  • MisfirePolicy.SimpleTrigger.FireNow
  • MisfirePolicy.SimpleTrigger.RescheduleNowWithExistingRepeatCount
  • MisfirePolicy.SimpleTrigger.RescheduleNowWithRemainingRepeatCount
  • MisfirePolicy.SimpleTrigger.RescheduleNextWithRemainingCount
  • MisfirePolicy.SimpleTrigger.RescheduleNextWithExistingCount

在构建SimpleTriggers时,可以将简单的时间表(通过SimpleSchedulerBuilder)指定为停止作业指令:

trigger = TriggerBuilder.Create()
.WithIdentity("trigger7", "group1")
.WithSimpleSchedule(x => x
.WithIntervalInMinutes(5)
.RepeatForever()
.WithMisfireHandlingInstructionNextWithExistingCount())
.Build();

Cron时间表达式。

Cron-Expressions用于配置CronTrigger的实例。Cron-Expressions是由七个子表达式组成的字符串,它们描述了计划的各个细节。这些子表达式用空格分隔,表示:

  • Seconds
  • Minutes
  • Hours
  • Day-of-Month
  • Month
  • Day-of-Week
  • Year (optional field)

示例Cron表达式

下面是一些表达式及其含义的例子 - 你可以在CronTrigger的API文档中找到更多的例子

CronTrigger示例1 - 创建一个触发器的表达式,每5分钟触发一次

"0 0/5 * * * ?"

CronTrigger示例2 - 一个表达式,用于创建在分钟后10秒(即上午10:00:10,上午10:05:10等)每5分钟触发一次的触发器。

"10 0/5 * * * ?"

CronTrigger示例3 - 一个表达式,用于在每个星期三和星期五的10:30,11:30,12:30和13:30创建一个触发器。

"0 30 10-13 ? * WED,FRI"

CronTrigger示例4 - 一个表达式,用于创建一个触发器,在每个月的第5天和第20天的上午8点到上午10点之间每隔半小时触发一次。请注意,触发器不会在上午10点,仅在8点,8点,9点和9点30分

"0 0/30 8-9 5,20 * ?"

请注意,一些调度要求过于复杂,无法用一个触发器来表示 - 例如“上午9点至上午10点之间每5分钟一次,下午1点至10点之间每20分钟一次”。在这种情况下解决方案是简单地创建两个触发器,并注册他们两个运行相同的工作。

CronTrigger实例使用TriggerBuilder(用于触发器的主属性)和WithCronSchedule扩展方法(用于CronTrigger特定的属性)构建。

您也可以使用CronScheduleBuilder的静态方法来创建计划。

建立一个触发器,每隔上午8点到下午5点,每隔一分钟一次:

trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithCronSchedule("0 0/2 8-17 * * ?")
.ForJob("myJob", "group1")
.Build();

建立一个触发器,每天在上午10:42开始:

// we use CronScheduleBuilder's static helper methods here
trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithSchedule(CronScheduleBuilder.DailyAtHourAndMinute(10, 42))
.ForJob(myJobKey)
.Build();

要么 -

trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithCronSchedule("0 42 10 * * ?")
.ForJob("myJob", "group1")
.Build();

建立一个触发器,将在星期三上午10:42,在系统默认的时区以外的其他时间触发:

trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithSchedule(CronScheduleBuilder
.WeeklyOnDayAndHourAndMinute(DayOfWeek.Wednesday, 10, 42)
.InTimeZone(TimeZoneInfo.FindSystemTimeZoneById("Central America Standard Time")))
.ForJob(myJobKey)
.Build();

要么 -

trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithCronSchedule("0 42 10 ? * WED", x => x
.InTimeZone(TimeZoneInfo.FindSystemTimeZoneById("Central America Standard Time")))
.ForJob(myJobKey)
.Build();

以下指令可用于通知QuartzCronTrigger发生停止作业时应该执行的操作。(本教程的“更多关于触发器”部分介绍了停止作业情况)。这些指令被定义为常量(并且API文档具有对其行为的描述)。说明包括:

  • MisfireInstruction.IgnoreMisfirePolicy
  • MisfireInstruction.CronTrigger.DoNothing
  • MisfireInstruction.CronTrigger.FireOnceNow

所有触发器都有MisfireInstrution.SmartPolicy指令可供使用,并且此指令也是所有触发器类型的默认值。CronTrigger将“智能策略”指令解释为MisfireInstruction.CronTrigger.FireOnceNow。CronTrigger.UpdateAfterMisfire()方法的API文档解释了此行为的确切详细信息。

在构建CronTriggers时,您可以将缺火指令指定为cron时间表的一部分(通过WithCronSchedule扩展方法):

trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithCronSchedule("0 0/2 8-17 * * ?", x => x
.WithMisfireHandlingInstructionFireAndProceed())
.ForJob("myJob", "group1")
.Build();

其他

我们可以看一下,源码中对IQuartzScheduleJobManagerScheduleAsync方法的封装,其实就是Scheduler.ScheduleJob处理了一下。

如果要使用模块的PostInitialize方法中解析和使用它来调度作业,也是可以的。

using System.Reflection;
using Abp.Dependency;
using Abp.Modules;
using Abp.Quartz.Configuration;
using Abp.Threading.BackgroundWorkers;
using Quartz; namespace Abp.Quartz
{
[DependsOn(typeof (AbpKernelModule))]
public class AbpQuartzModule : AbpModule
{
public override void PreInitialize()
{
IocManager.Register<IAbpQuartzConfiguration, AbpQuartzConfiguration>(); Configuration.Modules.AbpQuartz().Scheduler.JobFactory = new AbpQuartzJobFactory(IocManager);
} public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
} public override void PostInitialize()
{
IocManager.RegisterIfNot<IJobListener, AbpQuartzJobListener>(); Configuration.Modules.AbpQuartz().Scheduler.ListenerManager.AddJobListener(IocManager.Resolve<IJobListener>()); if (Configuration.BackgroundJobs.IsJobExecutionEnabled)
{
IocManager.Resolve<IBackgroundWorkerManager>().Add(IocManager.Resolve<IQuartzScheduleJobManager>());
}
}
}
}

JobBase封装的一些方法,继承自IJob

ABP+AdminLTE+Bootstrap Table权限管理系统一期

Github:https://github.com/Jimmey-Jiang/ABP-ASP.NET-Boilerplate-Project-CMS

ABP module-zero +AdminLTE+Bootstrap Table+jQuery权限管理系统第十七节--Quartz与ABP框架Abp.Quartz及扩展的更多相关文章

  1. ABP module-zero +AdminLTE+Bootstrap Table+jQuery权限管理系统第十二节--小结,Bootstrap Table之角色管理

    返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期 很多人说ABP不适合高并发大型,有一定的道理,但是我觉得还是可以的,就看架构师的能力了,哈哈,我之前公司就是ABP ...

  2. ABP module-zero +AdminLTE+Bootstrap Table+jQuery权限管理系统第十六节--SignalR与ABP框架Abp.Web.SignalR及扩展

    SignalR简介 SignalR是什么? ASP.NET SignalR 是为 ASP.NET 开发人员提供的一个库,可以简化开发人员将实时 Web 功能添加到应用程序的过程.实时 Web 功能是指 ...

  3. ABP module-zero +AdminLTE+Bootstrap Table+jQuery权限管理系统第十五节--缓存小结与ABP框架项目中 Redis Cache的实现

    返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期 缓存 为什么要用缓存 为什么要用缓存呢,说缓存之前先说使用缓存的优点. 减少寄宿服务器的往返调用(round-tr ...

  4. ABP module-zero +AdminLTE+Bootstrap Table+jQuery权限管理系统第十三节--RBAC模式及ABP权限管理(附送福利)

    ABP+AdminLTE+Bootstrap Table权限管理系统一期 Github:https://github.com/Jimmey-Jiang/ABP-ASP.NET-Boilerplate- ...

  5. ABP module-zero +AdminLTE+Bootstrap Table+jQuery权限管理系统第十四节--后台工作者HangFire与ABP框架Abp.Hangfire及扩展

    返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期 HangFire与Quartz.NET相比主要是HangFire的内置提供集成化的控制台,方便后台查看及监控,对于 ...

  6. ABP+AdminLTE+Bootstrap Table权限管理系统一期

       学而时习之,不亦说乎,温顾温知新,可以为师矣. 这也是算是一种学习的方法和态度吧,经常去学习和总结,在博客园看了很多大神的文章,写下一点对于ABP(ABP是“ASP.NET Boilerplat ...

  7. ABP+AdminLTE+Bootstrap Table权限管理系统第十一节--Bootstrap Table用户管理列表以及Module Zero之用户管理

    返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期 用户实体 用户实体代表应用的一个用户,它派生自AbpUser类,如下所示: public class User : ...

  8. ABP+AdminLTE+Bootstrap Table权限管理系统第十一节--bootstrap table之用户管理列表

    这张开始bootstrap table,引入项目有两种方法,一种是直接去官网下载 地址:http://bootstrap-table.wenzhixin.net.cn/ 另一种是Nuget引入. 然后 ...

  9. ABP+AdminLTE+Bootstrap Table权限管理系统第九节--AdminLTE模板页搭建

    AdminLTE 官网地址:https://adminlte.io/themes/AdminLTE/index2.html 首先去官网下载包下来,然后引入项目. 然后我们在web层添加区域Admin以 ...

随机推荐

  1. What Are You Talking About

    What Are You Talking About Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 102400/204800 K (Ja ...

  2. awk巩固练习题

    第1章 awk数组练习题 1.1 文件内容(仅第一行) [root@znix test]# head -1 secure-20161219 access.log ==> secure-20161 ...

  3. mysql别名的使用

    在项目中遇到别名的问题,抽时间整理了一下 在sql中,合理的使用别名可以让sql更容易写并且提高可读性.别名使用 as 来表示,可以分为表别名和列别名. 别名应该是先定义后使用才对,所以首先要了解sq ...

  4. IOS学习——iphone X的适配

    说实话,对于一个刚入门iOS两个月的新手而言,在拿到这个任务的时候整个人都是懵逼的,怎么做适配?哪些地方需要适配?该怎么做?一个个问题搞得头都大了. 首先,啥都不管,先在iPhone X上运行起来看看 ...

  5. Problem E: 动物爱好者

    Problem E: 动物爱好者 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 882  Solved: 699[Submit][Status][Web ...

  6. WebGL学习(1) - 三角形

    原文地址:WebGL学习(1) - 三角形 还记得第一次看到canvas的粒子特效的时候,真的把我给惊艳到了,原来在浏览器也能做出这么棒的效果.结合<HTML5 Canvas核心技术>和网 ...

  7. .NET MVC 二级域名路由的实现

    .NET MVC 5以下版本: http://www.cnblogs.com/luanwey/archive/2009/08/12/1544444.html http://blog.maartenba ...

  8. 使用SQLPLUS创建用户名和表空间

    用sqlplus为oracle创建用户和表空间用sqlplus为oracle创建用户和表空间用Oracle10g自带的企业管理器或PL/SQL图形化的方法创建表空间和用户以及分配权限是相对比较简单的, ...

  9. SQL Server分组查询某最大值的整条数据(包含linq写法)

    想实现如下效果,就是分组后时间最大的那一条数据: 1.SQL SELECT * FROM ( SELECT * , ROW_NUMBER() OVER ( PARTITION BY RIP_GUID ...

  10. net core 使用tagHelper将 enum枚举类型转换为下拉列表select

    [HtmlTargetElement("enums")] //[HtmlTargetElement("enums", TagStructure = TagStr ...