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语句好奇怪。

 

  1. 我尝试去删掉所有的,在重新添加,报下面的错误:

 

可能也是因为导航属性的值丢失导致的,然而,删掉重新添加毕竟很low的方式,而且,还报错了。都打算用EF的context.Database.ExecuteSqlCommand

方式,先生成SQL语句,然后执行了。中午Kewei吃完饭,我请他过来帮看看为什么会生成insert语句,不行的话,就生成sql语句去做。

 

有2个bugs导致了这个问题:

  1. Kewei调试时候发现,SessionTargetDTTRecord的domain对象id属性为0000-0000,f12过去发现,基类中有id,这个类中又重复定义了,所以给子类的id赋值了,而base.id却没有赋值.
  2. 我debug发现,autoMap SessionTarget对象的时候,没有把SessionTargetDTTRecords和SessionTargetTARecords给ignore,导致进行不必要map了,把导航属性的值便成null了,比如下图的sessionTarget.

 

-           

 

posted on 2015-12-24 17:11  王老二  阅读(472)  评论(0编辑  收藏  举报