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.

-
浙公网安备 33010602011771号