当前位置: 首页 > news >正文

快速创建一个网站网站模板制作

快速创建一个网站,网站模板制作,建设网站需要什么硬件,合肥网站开发培训学校主要结论 如果需要执行基本CURD之外的其他操作#xff0c;此时就有必要使用仓储#xff08;Repository#xff09;。为了促进测试工作并改善可靠性#xff0c;应将仓储视作可重复使用的库#xff08;Library#xff09;。将安全和审计功能放入仓储中可减少Bug并简化应用程… 主要结论 如果需要执行基本CURD之外的其他操作此时就有必要使用仓储Repository。为了促进测试工作并改善可靠性应将仓储视作可重复使用的库Library。将安全和审计功能放入仓储中可减少Bug并简化应用程序。对ORM的选择不会限制仓储的用途只会影响仓储承担的工作量。 在之前发布的文章使用实体框架、Dapper和Chain的仓储模式实现策略中我们介绍了实现仓储所需的基本模式。很多情况下这些模式只是围绕底层数据访问技术本质上并非完全必要的薄层。然而通过构建这样的仓储将获得很多新的机会。 在设计仓储时需要从“必须发生的事”这个角度来思考。例如假设制订了一条规则每当一条记录被更新后其“LastModifiedBy”列必须设置为当前用户。但我们并不需要在每次保存前更新应用程序代码中的LastModifiedBy可以直接将相关函数放在仓储中。 通过将数据访问层视作管理所有“必须发生的事情”细节的独立库即可大幅减少实现过程中的错误数量。与此同时可以简化基于仓储构建的代码因为已经不再需要考虑“记账”之类的任务。 注意本文会尽量提供适用于实体框架Entity Framework、Dapper和/或Tortuga Chain的代码范例然而大部分仓储功能均可通过不依赖具体ORM的方式实现。 审计列 大部分应用程序最终需要追踪谁在什么时间更改了数据库。对于简单的数据库这是通过审计列Audit column的形式实现的。虽然名称可能各不相同但审计列通常主要承担下列四个角色 创建者的User Key创建日期/时间最后修改者的User Key最后修改日期/时间 取决于应用程序的安全需求可能还存在其他审计列例如 删除者的User Key删除日期/时间[创建 | 最后修改 | 删除] 者的Application Key[创建 | 最后修改 | 删除] 者的IP地址 从技术角度来看日期列很容易处理但User Key的处理就需要费些功夫了这里需要的是“可感知上下文的仓储”。 常规的仓储是无法感知上下文的这意味着除了连接数据库时绝对必要的信息仓储无法获知其他任何信息。如果能正确地设计仓储可以是彻底无状态Stateless的这样即可在整个应用程序中共享一个实例。 可感知上下文的仓储略微复杂。除非了解上下文否则无法创建这种仓储而上下文至少要包含当前活跃用户的ID和Key。对于某些应用程序这就够了但对于其他应用程序我们可能还需要传递整个用户对象和/或代表运行中应用程序的对象。 Chain Chain通过一种名为审计规则Audit rule的功能为此提供了内建的支持。审计规则可供我们根据列名指定要覆盖Override的值。该功能包含了拆箱即用的基于日期的规则以及从用户对象将属性复制到列的规则。范例 dataSource dataSource.WithRules(new UserDataRule(CreatedByKey, UserKey, OperationType.Insert),new UserDataRule(UpdatedByKey, UserKey, OperationType.InsertOrUpdate),new DateTimeRule(CreatedDate, DateTimeKind.Local, OperationType.Insert), new DateTimeRule(UpdatedDate, DateTimeKind.Local, OperationType.InsertOrUpdate)); 如上所述为了实现这一点我们需要一种可感知上下文的仓储。从下列构造函数中可以看到如何将上下文传递给不可变数据源并使用必要信息新建数据源。 public EmployeeRepository(DataSource dataSource, User user) {m_DataSource dataSource.WithUser(user); } 借此即可使用自行选择的DI框架针对每个请求自动创建并填写仓储。 实体框架 为了在实体框架中实现审计列的全局应用我们需要利用ObjectStateManager并创建一个专用接口。该接口如果愿意也可以称之为“基类Base class”看起来类似这样 public interface IAuditableEntity {DateTime CreatedDate {get; set;}DateTime UpdatedDate {get; set;}DateTime CreatedDate {get; set;}DateTime CreatedDate {get; set;} } 随后该接口或基类会应用给数据库中与审计列匹配的每个实体。 随后需要通过下列方式对DataContext类的Save方法进行覆盖Override。 public override int SaveChanges() {// Get added entriesIEnumerableObjectStateEntry addedEntryCollection Context.ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added).Where(m m ! null m.Entity ! null);// Get modified entriesIEnumerableObjectStateEntry modifiedEntryCollection Context.ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified).Where(m m ! null m.Entity ! null);// Set audit fields of added entriesforeach (ObjectStateEntry entry in addedEntryCollection){ var addedEntity entry.Entity as IAuditableEntity;if (addedEntity ! null){addedEntity.CreatedDate DateTime.Now;addedEntity.CreatedByKey m_User.UserKey;addedEntity.UpdatedDate DateTime.Now;addedEntity.UpdatedByKey m_User.UserKey;}}// Set audit fields of modified entriesforeach (ObjectStateEntry entry in modifiedEntryCollection){var modifiedEntity entry.Entity as IAuditableEntity;if (modifiedEntity ! null){modifiedEntity.UpdatedDate DateTime.Now;modifiedEntity.UpdatedByKey m_User.UserKey;}}return SaveChanges(); } 如果需要大量使用实体框架EF则有必要非常熟悉ObjectStateManager及其能力。因为有关进行中事务的大部分有用元数据都包含在ObjectStateManager中。 最后还需要修改数据上下文可能还有仓储的构造函数以使其接受用户对象。 虽然看似这要编写大量代码但每个EF数据上下文只需要编写一次。与上文的范例类似数据上下文和仓储的实际创建工作可由DI框架负责进行。 历史表 很多地方性的法规和制度要求对记录的改动进行追踪此外这样做也可以简化诊断工作。 对此的常规建议是直接让数据库自行处理。一些数据库内建包含了类似的功能这类功能通常叫做时间表Temporal table。其他数据库则可使用触发器模拟出类似的功能。无论哪种情况应用程序都不会发现额外的日志操作因此这种技术出错的概率也得以大幅降低。 如果出于某些原因无法使用时间表或触发器那么仓储需要能明确写入历史表。 无论将维持历史表的代码放在哪里都有两个基本惯例需要遵循。一致性在这里真的很重要如果一些表遵循一个惯例其他表遵循另一个管理最终只能造成混乱。 写入前复制在这个惯例中需要在实际执行更新或删除操作前将老的记录从活动Live表复制到历史表。这意味着历史表绝对不会包含当前记录。因此需要将活动表和历史表联接在一起才能看到完整的变更历史。 复制前写入或者可以首先更新活动表随后将该行复制到历史表。这种做法的优势在于历史表中包含完整的记录无需进行上文提到的联接。不足之处在于由于这种做法需要复制数据因此会耗费更多空间。 无论哪种惯例都可以使用软删除了解是谁实际删除了行。如果需要使用硬删除也只能在执行软删除之后再进行硬删除。 软删除 使用仓储可获得的另一个优势在于可以在应用程序无法察觉的情况下从硬删除切换为软删除。软删除可用能被应用程序察觉的方式删除记录但删除的记录可继续保留在数据库中以便用于审计等用途。此外在必要时应用程序还可以恢复被软删除的记录。 为避免数据丢失不应针对为软删除提供支持的表为应用程序分配DELETE特权。如果应用程序无意中试图执行硬删除权限检查功能会显示错误信息而不会直接删除行。 Chain Chain通过自己的审计规则基础架构提供了隐式的软删除支持。在配置软删除规则后按照习惯还需要配置匹配审计Matching audit列 var dataSource dataSource.WithRules(new SoftDeleteRule(DeletedFlag, true, OperationTypes.SelectOrDelete),new UserDataRule(DeletedByKey, EmployeeKey, OperationTypes.Delete),new DateTimeRule(DeletedDate, DateTimeKind.Local, OperationTypes.Delete)); 在发现表包含软删除列例如本例中的DeletedFlag后会自动发生两件事 所有查询的WHERE子句可暗中添加“AND DeletedFlag 0”。所有对DataSource.Delete的调用将变成更新Update语句以设置 deleted flag。 实体框架 在实体框架中可以在读取为软删除提供支持的表的每个查询中包含一个额外的Where子句。此外还需要将任何删除操作手工转换为更新操作使用对象图Object graph时这一点可能较难办到。 另一种方法的过程较繁琐但可能更不易出错。首先在DataContext.OnModelCreating覆盖中明确列出每个支持软删除的表。 protected override void OnModelCreating(DbModelBuilder modelBuilder) {modelBuilder.EntityEmployee(). Map(m m.Requires(IsDeleted).HasValue(false)); } 随后需要覆盖Save方法以确保删除操作可变成更新操作。Stackoverflow上的Colin提供了这种模式。 public override int SaveChanges() {foreach (var entry in ChangeTracker.Entries().Where(p p.State EntityState.Deleted p.Entity is ModelBase))SoftDelete(entry);return base.SaveChanges(); }private void SoftDelete(DbEntityEntry entry) {var e (ModelBase)entry.Entity;string tableName GetTableName(e.GetType());Database.ExecuteSqlCommand(String.Format(UPDATE {0} SET IsDeleted 1 WHERE ID id, tableName), new SqlParameter(id, e.ID));//Marking it Detached prevents the hard deleteentry.State EntityState.Detached; } 建议阅读Colin回答中的剩余内容这些回答解决了很多边界案例问题。 访问日志记录 虽然审计列、历史表以及软删除均适用于写入操作场景但有时候可能还要用日志记录读取操作。例如美国医疗健康行业中医护人员需要能够在紧急情况下访问病患的医疗记录。但在正常业务中他们只有在为病患提供治疗的过程中可以合法访问这些记录。 由于记录不能彻底锁定因此作为权宜之计只能追踪读取过每条记录的人的身份。在仓储层面上只需要对每个涉及敏感数据的查询进行日志记录即可轻松实现。最简单的方法是在相关仓储方法的基础上手工实现。 性能日志 用户体验已成为一项功能因此我们有必要了解每个查询到底要花费多长时间。单纯追踪每页面性能还不够因为一个页面可能涉及多个查询。对于实体框架这一点尤为重要因为延迟加载Lazy-loading可能会将数据库调用隐藏起来。 仓储中的显式日志记录 虽然很枯燥并且很容易漏掉某个查询但可将每个查询封装到“即抛型”计时器中。具体模式如下 public class OperationTimer : IDisposable {readonly object m_Context;readonly Stopwatch m_Timer;public OperationTimer(object context){m_Context context;m_Timer Stopwatch.StartNew();}public void Dispose(){//Write to log here using timer and context} } 具体用法为 using(new OperationTimer(Load employees)) {//execute query here } Chain Chain在数据源层面上暴露了一系列事件。本例需要的是DataSource.ExecutionFinished。范例如下 static void DefaultDispatcher_ExecutionFinished(object sender, ExecutionEventArgs e) {Debug.WriteLine($Execution finished: {e.ExecutionDetails.OperationName}. Duration: {e.Duration.Value.TotalSeconds.ToString(N3)} sec. Rows affected: {(e.RowsAffected ! null ? e.RowsAffected.Value.ToString(N0) : NULL)}.); } 此外还可将句柄附加到DataSource.GlobalExecutionFinished借此侦听来自所有数据源的事件。 实体框架 实体框架内建的日志能力无法衡量每个查询所需的时间。为了消除这种局限我们可以使用自定义的IDbCommandInterceptor。 public class EFLoggerForTesting : IDbCommandInterceptor {static readonly ConcurrentDictionaryDbCommand, DateTime m_StartTime new ConcurrentDictionaryDbCommand, DateTime();public void ReaderExecuted(DbCommand command, DbCommandInterceptionContextDbDataReader interceptionContext){Log(command, interceptionContext);}public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContextint interceptionContext){Log(command, interceptionContext);}public void ScalarExecuted(DbCommand command, DbCommandInterceptionContextobject interceptionContext){Log(command, interceptionContext);}private static void LogT(DbCommand command, DbCommandInterceptionContextT interceptionContext){DateTime startTime;TimeSpan duration;m_StartTime.TryRemove(command, out startTime);if (startTime ! default(DateTime)){duration DateTime.Now - startTime;} elseduration TimeSpan.Zero;string message;var parameters new StringBuilder();foreach (DbParameter param in command.Parameters){parameters.AppendLine(param.ParameterName param.DbType param.Value);}if (interceptionContext.Exception null){message string.Format(Database call took {0} sec. RequestId {1} \r\nCommand:\r\n{2}, duration.TotalSeconds.ToString(N3), requestId, parameters.ToString() command.CommandText);}else{message string.Format(EF Database call failed after {0} sec. RequestId {1} \r\nCommand:\r\n{2}\r\nError:{3} , duration.TotalSeconds.ToString(N3), requestId, parameters.ToString() command.CommandText, interceptionContext.Exception);}Debug.WriteLine(message);}public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContextint interceptionContext){OnStart(command);}public void ReaderExecuting(DbCommand command, DbCommandInterceptionContextDbDataReader interceptionContext){OnStart(command);}public void ScalarExecuting(DbCommand command, DbCommandInterceptionContextobject interceptionContext){OnStart(command);}private static void OnStart(DbCommand command){m_StartTime.TryAdd(command, DateTime.Now);} } 虽然这种方式无法获取上下文数据但可酌情将上下文推出Shove至ThreadLocal或AsyncLocal以绕过这一局限。 权限检查 – 表级 虽然可执行应用程序级别的权限检查但同时强制进行仓储级的检查也能提供一定的好处。这种做法可以避免忘了对新创建的Screen/页面进行权限检查。 仓储强制执行 实现这一切最简单的方法是在每个相关函数开始时执行角色检查。例如 public int Insert(Employee employee){if (!m_User.IsAdmin)throw new SecurityException(Only admins may add employees); 数据库强制执行 更成熟的做法是创建多个连接字符串。在创建仓储时可根据用户角色选择连接字符串。在本例中非管理员用户的连接字符串针对employee表不具备INSERT特权。 由于复杂度和繁琐的维护除非需要多层防御机制对安全性要求极高的环境否则不建议使用这种方法。就算在这种情况下也需要通过大量的自动化测试确保每个连接字符串只包含自己需要的全部权限。 权限检查 – 列级 有时候可能需要进行列级的权限检查。例如我们可能需要防止用户为自己分配管理员特权或可能希望阻止经理之外的其他用户查看员工的薪资数据。 Chain Chain可以利用自带的审计规则功能实现列级权限检查。此时会将匿名函数与列名称以及受限制操作列表一起传递至RestrictColumn构造函数。并可选指定表名称。 var IsAdminCheck user ((User)user).IsAdmin;dataSource dataSource.WithRules(new RestrictColumn(Users, IsAdmin, OperationTypes.Insert|OperationTypes.Update, IsAdminCheck)); 为防止读取受限制的列可将其传递至OperationTypes.Select flag。 Dapper 在Dapper中实现这一目标的最简单方法是使用多个SQL语句。如果用户缺乏某一特权只需要选择忽略对应列的SQL语句即可。 实体框架 查询可使用下列几个选项 根据用户角色手工创建不同的投影例如Select子句。正常执行查询随后如果权限检查失败对结果集进行循环并将受限制的属性设置为null/0。 对于插入按照上述方法将受限制属性留空即可。 更新操作较为复杂。当写入特定列的操作受限时将无法附加实体。此时需要重新获取原始记录对允许的值进行复制并保存该对象而不要保存应用程序代码传递来的对象。基本上这就是上一篇文章提到的“新手”模式。 将一个模型映射至多个表 数据架构方面有一个很重要的概念即无需在表和类之间创建一对一映射。为了让数据库的运转更高效或满足特定业务规则的需求通常可能需要将一个类映射至多个表。 假设需要记录有关棒球队的数据可能会用到这些表 表主键TeamTeamTeamSeasonMapTeamKeySeasonKey 如果应用程序只能在有关赛季Season的上下文中理解团队Team的概念那么可以用一个Team对象涵盖所有表。 Chain Chain中的类和表之间不具备强关系这意味着对于更新操作应该这样写代码 dataSource.Update(Team, myTeam).Execute(); dataSource.Update(TeamSeasonMap, myTeam).Execute(); 代码运行时会判断哪些表适用哪些属性并酌情生成SQL语句。 通过这种方式即可从所有表的联接视图中获取Team对象。Chain不支持直接联接假设始终通过视图实现。 实体框架 实体框架会认为映射至同一实体的多个表严格共享相同的主键。这意味着将无法支持该场景。 对于读取操作可以使用EF的常规LINQ语法执行联接和投影。对于更新操作需要将每个表的模型复制到单独的实体中。 缓存 一般来说仓储都需要考虑缓存问题。由于仓储知道数据的修改时间因此可充当处理缓存失效问题的最佳方法。 Chain Chain支持缓存但必须通过Appender分别应用给每个查询。Appender可附加至实际执行之前的操作中在本例中我们需要关注四个Appender .Cache(...).CacheAllItems(...).InvalidateCache(...).ReadOrCache(...) 也许通过仓储范例可以更好地说明这些Appender的作用。在下面的例子中可以看到对特定记录创建缓存以及使用CacheAllItems对集合创建缓存这两种做法之间的相互作用。 public class EmployeeCachingRepository {private const string TableName HR.Employee;private const string AllCacheKey HR.Employee ALL;public IClass1DataSource Source { get; private set; }public CachePolicy Policy { get; private set; }public EmployeeCachingRepository(IClass1DataSource source , CachePolicy policy null){Source source;Policy policy;}protected string CacheKey(int id){return $HR.Employee EmployeeKey{id};}protected string CacheKey(Employee entity){return CacheKey(entity.EmployeeKey.Value);}public Employee Get(int id){return Source.GetByKey(TableName, id) .ToObjectEmployee() .ReadOrCache(CacheKey(id), policy: Policy).Execute();}public IListEmployee GetAll(){return Source.From(TableName) .ToCollectionEmployee().CacheAllItems((Employee x) CacheKey(x), policy: Policy).ReadOrCache(AllCacheKey, policy: Policy).Execute();}public Employee Insert(Employee entity){return Source.Insert(TableName, entity) .ToObjectEmployee() .InvalidateCache(AllCacheKey) .Cache((Employee x) CacheKey(x), policy: Policy) .Execute();}public Employee Update(Employee entity){return Source.Update(TableName, entity) .ToObjectEmployee() .Cache(CacheKey(entity)) .InvalidateCache(AllCacheKey).Execute();}public void Delete(int id){Source.DeleteByKey(TableName, id). InvalidateCache(CacheKey(id)) .InvalidateCache(AllCacheKey).Execute();} } 从这个例子中可以发现Chain为失效逻辑提供了丰富的控制能力但作为代价我们必须慎重地指定各种选项。 实体框架 实体框架提供了两种级别的缓存。第一级仅限数据上下文主要可用于确保对象图不包含代表同一条物理数据库记录的重复实体。由于该缓存会与数据上下文一起销毁因此大部分缓存场景并不使用这种缓存。 在EF的术语中我们需要的是名为“二级缓存”的缓存。虽然该功能已包含在EF 5中但第6版实体框架并未提供任何拆箱即用的缓存功能。因此我们需要使用第三方库例如EntityFramework.Cache或EFSecondLevelCache。从列举的这些库可以知道为EF增加二级缓存并没有什么标准的模式。 关于本文作者 Jonathan Allen的第一份工作是在九十年代末期为一家诊所开发MIS项目借此帮助这家诊所逐渐由Access和Excel转向真正的企业级解决方案。在用五年时间为财政部开发自动化交易系统后他开始担任各种项目的顾问并从事了仓储机器人UI、癌症研究软件中间层以及大型房地产保险企业大数据需求等各类项目。闲暇时他喜欢研究并撰文介绍16世纪的格斗术。 原文地址http://www.infoq.com/cn/articles/repository-advanced.NET社区新闻深度好文微信中搜索dotNET跨平台或扫描二维码关注
http://www.sadfv.cn/news/37768/

相关文章:

  • 网站有备案号网站淘宝客一般怎么做
  • 浦东新区做网站公司五大免费资源网站
  • 爱情表白网站制作哪个网站专业做安防
  • 统一门户网站做网站需要多大的空间
  • 厦门自助网站建设报价河北网站建设免费推荐
  • 创造与魔法官方网站做自己网站的经费预算
  • 企业信息查询系统官网山东seo wordpress 主题
  • 有什么网站交互做的很好 知乎网站建设前期团队建设
  • 网站域名怎么填写公司网站怎么做推广
  • 做汽车拆解视频网站扬中潘杰简历
  • 企业网站建设的三个核心问题企业网站首页开发
  • 河北省住建和城乡建设厅网站黄骅港信息贴吧
  • 杭州网站推广技巧大型门户网站建设工作总结
  • 肥乡网站建设python基础教程电子书在线阅读
  • 内蒙网站建设seo优化上海网论坛网址
  • 网站新闻模板可以做100张照片的软件
  • 商务网站建设与维护试卷做水处理药剂的公司网站
  • 可以自己做漫画的网站建网站排名
  • 网站开发资格证书成都设计院
  • 深远互动 网站建设西宁做网站_君博示范
  • 无锡哪里建设网站网站的建设过程
  • 网站建设 定制赣州九一人才网最新招聘
  • 备案价公示网站太原制作手机网站
  • 万户网站制作温州做网站厉害的公司有哪些
  • 卖米网站源码建筑人才网市场
  • 职业教育网站平台建设营销型网站设计论文
  • 网页网站培训班广告公司怎么取名
  • 怎么写网站网站介绍模板
  • 速橙科技有限公司网站建设做空的网站有哪些
  • 如何利用国外网站开发客户jq动画效果网站