ABA项目技术总结:IOC框架Autofac- 以及碰到的Bugs, sync同步数据为例
Autofac 一个依赖注入框架
protected void Application_Start() { GlobalConfiguration.Configure(AutofacWebAPI.Initialize); }
public class AutofacWebAPI { public static void Initialize(HttpConfiguration config) { Initialize(config, RegisterServices(new ContainerBuilder())); } public static void Initialize(HttpConfiguration config, IContainer container) { config.DependencyResolver = new AutofacWebApiDependencyResolver(container); } private static IContainer RegisterServices(ContainerBuilder builder) { builder.RegisterApiControllers(Assembly.GetExecutingAssembly()); // EF DbContext builder.RegisterType<ABATrackerAgencyDataContext>() .As<DbContext>() .InstancePerRequest(); // Register repositories by using Autofac's OpenGenerics feature // More info: http://code.google.com/p/autofac/wiki/OpenGenerics builder.RegisterGeneric(typeof(EntityRepository<,>)) .As(typeof(IEntityRepository<,>)) .InstancePerRequest(); // Services builder.RegisterType<DesCryptService>() .As<ICryptoService>() .InstancePerRequest(); builder.RegisterType<MembershipService>() .As<IMembershipService>() .InstancePerRequest(); builder.RegisterType<ProgramService>() .As<IProgramService>() .InstancePerRequest(); builder.RegisterType<ClientService>() .As<IClientService>() .InstancePerRequest(); builder.RegisterType<BTPService>() .As<IBTPService>() .InstancePerRequest(); builder.RegisterType<TherapySessionService>() .As<ITherapySessionService>() .InstancePerRequest(); return builder.Build(); } }
public class ProgramService : IProgramService { private readonly IEntityRepository<Program, Guid> _programRepository; private readonly IEntityRepository<ProgramGoal, Guid> _programGoalRepository; private readonly IEntityRepository<ProgramTarget, Guid> _programTargetRepository; private readonly IEntityRepository<TATargetTask, Guid> _taTargetTaskRepository; public ProgramService( IEntityRepository<Program, Guid> programRepository, IEntityRepository<ProgramGoal, Guid> programGoalRepository, IEntityRepository<ProgramTarget, Guid> programTargetRepository, IEntityRepository<TATargetTask, Guid> taTargetTaskRepository) { this._programRepository = programRepository; this._programGoalRepository = programGoalRepository; this._programTargetRepository = programTargetRepository; this._taTargetTaskRepository = taTargetTaskRepository; } public Program GetProgram(Guid Id) { var program = _programRepository.AllIncluding( p => p.ProgramGoals, p => p.ProgramTargets, p => p.ProgramTargets.Select(t => t.TATargetTasks)) .Where(p => p.Id == Id && p.Deleted == false) .FirstOrDefault(); return program; } public bool CheckUniqueForProgramName(Guid id, string programName) { return !_programRepository.GetAll().Any(p => p.ProgramName == programName && p.Id != id); } }
[Authorize(Roles="Admin,BCBA")] public class ProgramsController : ApiController { private readonly IProgramService _programService; private readonly IMembershipService _membershipService; public ProgramsController(IProgramService programService, IMembershipService membershipService) { this._programService = programService; this._membershipService = membershipService; } [HttpGet] public bool CheckProgramName(Guid id, string programName) { return _programService.CheckUniqueForProgramName(id, programName); } public ProgramDto GetProgram(Guid id) { var program = _programService.GetProgram(id); if (program == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } program.ProgramGoals = program.ProgramGoals.Where(g => g.Deleted == false).ToList(); program.ProgramTargets = program.ProgramTargets.Where(t => t.Deleted == false).OrderBy(t => t.StatusIndicator).ThenBy(t => t.TargetTypeCode).ThenBy(t => t.TargetName).ThenByDescending(t=>t.MasteredDate).ToList(); foreach (var target in program.ProgramTargets) { target.TATargetTasks = target.TATargetTasks.Where(t => t.Deleted == false).ToList(); } return Mapper.Map<Program, ProgramDto>(program); } public IEnumerable<ProgramSelectionDto> GetProgramsByDomains(string domainIds) { var programs = _programService.GetProgramsByDomains(domainIds); return Mapper.Map<IEnumerable<Program>, IEnumerable<ProgramSelectionDto>>(programs); } [EmptyParameterFilter("requestModel")] public HttpResponseMessage PutProgram(Guid id, ProgramRequestModel requestModel) { var program = _programService.GetProgram(id); if (program == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } var httpStatusCode = HttpStatusCode.OK; var updatedProgramResult = _programService.UpdateProgram(requestModel.ToProgram(program)); if (!updatedProgramResult.IsSuccess) { httpStatusCode = HttpStatusCode.Conflict; } var response = Request.CreateResponse(httpStatusCode, Mapper.Map<Program, ProgramDto>(updatedProgramResult.Entity)); return response; } public HttpResponseMessage DeleteProgram(Guid id) { var program = _programService.GetProgram(id); if (program == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } var programRemoveResult = _programService.RemoveProgram(program); if (!programRemoveResult.IsSuccess) { return new HttpResponseMessage(HttpStatusCode.Conflict); } return new HttpResponseMessage(HttpStatusCode.NoContent); } }
数据库设计的坑:
1.因为把公用的属性Id,修改时间,修改人 等5个字段,放到base里面了,但是数据中每张表的主键叫sessionId,xxxId, 需要额外map,都叫id就省事了。
2.数据库到EF,数据中某些表名是复数形式.
而Domin映射的类中,复数代表一个集合。每次更新EF from DB之后,单个属性的映射会重命名为默认的表名字 也就是 ProgramTarget ProgramTargets, ProgramTarget 是domain对象,定义为单数形式了,ProgramTargets在我们C#代码中是用来表示集合的。
BUG1:
在保存不公开其关系的外键属性的实体时出错。EntityEntries 属性将返回 null,因为单个实体无法标识为异常的源。通过在实体类型中公开外键属性,可以更加轻松地在保存时处理异常。有关详细信息,请参阅 InnerException。
InnerException:{"“FK_BTPProgramTargets_BTPProgram”AssociationSet 中的关系处于“Deleted”状态。如果有多重性约束,则相应的“BTPProgramTargets”也必须处于“Deleted”状态。"}
foreach (BTP btp in client.BTPs) { foreach (BTPProgram program in btp.BTPPrograms) { //Reset each BTPProgramTarget from requestModel foreach (var btpSync in clientSync.Btps) { foreach (var programSync in btpSync.BTPPrograms) { if (program.Id == programSync.ProgramId) { //Mapper.Map<ICollection<BTPProgramTargetSyncRequestModel>, ICollection<BTPProgramTarget>>(programSync.BTPProgramTargets,program.BTPProgramTargets); //SelfMapTargets(programSync.BTPProgramTargets, program.BTPProgramTargets); foreach (var targetSrc in programSync.BTPProgramTargets) { var targetDest = program.BTPProgramTargets.FirstOrDefault(t => !Guid.Equals(Guid.Empty, targetSrc.TargetId) && t.Id == targetSrc.TargetId); if (targetDest != null) { Mapper.Map<BTPProgramTargetSyncRequestModel, BTPProgramTarget>(targetSrc, targetDest); EntityLogger.UpdateEntity(targetDest); } } } } } } }
解决步骤:
1.刚开始,看到Deleted状态,猜想应该是domain中导航属性,集合之前可能有10条记录,但是传过来的requestModel只有5条,这样EF会去删掉5条记录,会涉及到delete。对比数据库的记录和请求的记录发现并不是这样。
2.排除其他因素,注释掉Mapper.Map<ICollection<BTPProgramTargetSyncRequestModel>, ICollection<BTPProgramTarget>>(programSync.BTPProgramTargets,program.BTPProgramTargets);只一行把requestModel ICollection转换为Domain ICollection代码后,正常工作,问题定位到map上。
3.自己写一个方法,手动map,成功更新数据库。
private void SelfMapTargets(ICollection<BTPProgramTargetSyncRequestModel> syncBTPProgramTargets, ICollection<BTPProgramTarget> btpProgramTargets) { foreach (var btpProgramTarget in btpProgramTargets) { foreach (var syncBTPProgramTarget in syncBTPProgramTargets) { if(Guid.Equals(btpProgramTarget.Id,syncBTPProgramTarget.TargetId)) { btpProgramTarget.PercentofCorrectResponse = syncBTPProgramTarget.PercentofCorrectResponse; } } } }
4.Debug,watch的发现,直接Maping List之后的结果,BTPProgramTarget对象的父对象BTPProgram被重置了null了,很奇怪,requestModel中并没有BTPProgram的属性,按道理不应该覆盖才对。 client的BTP模块有类似功能,查看相关代码,发现那儿是foreach给每个对象map的,不是map list,也改为foreach每个对象,map,问题解决,成功更新到数据库。
foreach (var targetSrc in programSrc.ProgramTargets) { var targetDest = programDest.BTPProgramTargets.FirstOrDefault(t => !Guid.Equals(Guid.Empty, targetSrc.TargetId) && t.Id == targetSrc.TargetId); if (targetDest != null) { Mapper.Map<BTPProgramTargetRequestModel, BTPProgramTarget>(targetSrc, targetDest); EntityLogger.UpdateEntity(targetDest); } else { targetDest = Mapper.Map<BTPProgramTargetRequestModel, BTPProgramTarget>(targetSrc); EntityLogger.CreateEntity(targetDest); programDest.BTPProgramTargets.Add(targetDest); } foreach (var taskSrc in targetSrc.TATargetTasks) { var taskDest = targetDest.BTPTATargetTasks.FirstOrDefault(t => !Guid.Equals(Guid.Empty, taskSrc.TaskId) && t.Id == taskSrc.TaskId); if (taskDest != null) { Mapper.Map<BTPTATargetTaskRequestModel, BTPTATargetTask>(taskSrc, taskDest); EntityLogger.UpdateEntity(taskDest); } else { taskDest = Mapper.Map<BTPTATargetTaskRequestModel, BTPTATargetTask>(taskSrc); EntityLogger.CreateEntity(taskDest); targetDest.BTPTATargetTasks.Add(taskDest); } } }
PS:btp那里遍历是因为这个是后台模块,可能会添加新的targets,所以要foreach,如果数据库不存在,就添加到EF的domain集合,而我这里,前台传递过来的数据<=数据库数据 数据库中软删除,所以假如数据库有10条记录,其中可能有4条是delete状态,前台不会得到,但是还需要保留在数据库中,我们在保存的时候,最方便的做法应该是 update方法执行前,获取client下子对象的所有数据,包含删除的,map对象的时候,只map客户端传过来的id,也就是delete=false的,然后把map后的domain保存,这样就不涉及EF删掉被软删除的数据。
2016/1/4
sync post的时候,还是报错, {"Violation of PRIMARY KEY constraint 'PK__SessionT__3214EC07C3324B04'. Cannot insert duplicate key in object
'dbo.SessionTargetDTTRecord'. The duplicate key value is (347ae60e-efb3-43eb-ada9-1dd2efa59b3f).\r\nThe statement has been
terminated."}
事实上,是get的数据原封不动的post过来的,却生成了insert语句,应该是update语句才对,后台的_SessionTargetDTTRecordRepository.add(entity)方法调试的时候,也没有走到,saveChanges方法调用后,却生成了insert语句好奇怪。
- 我尝试去删掉所有的,在重新添加,报下面的错误:
可能也是因为导航属性的值丢失导致的,然而,删掉重新添加毕竟很low的方式,而且,还报错了。都打算用EF的context.Database.ExecuteSqlCommand
方式,先生成SQL语句,然后执行了。中午Kewei吃完饭,我请他过来帮看看为什么会生成insert语句,不行的话,就生成sql语句去做。
有2个bugs导致了这个问题:
- Kewei调试时候发现,SessionTargetDTTRecord的domain对象id属性为0000-0000,f12过去发现,基类中有id,这个类中又重复定义了,所以给子类的id赋值了,而base.id却没有赋值.
- 我debug发现,autoMap SessionTarget对象的时候,没有把SessionTargetDTTRecords和SessionTargetTARecords给ignore,导致进行不必要map了,把导航属性的值便成null了,比如下图的sessionTarget.
-