
简介对于正在学习DevExpress XAF框架的.NET开发者这份源码集合提供了覆盖CRM、ERP、OA、SCM等场景的完整项目实例可直接用于参考业务流程设计、业务对象建模与后台界面搭建。压缩包共2000个文件大小仅7.47MB以1134个skin皮肤文件、765个cs源码文件、143个resx资源文件、95个ascx用户控件、68个xafml模型差异文件、68个aspx页面及13个sln解决方案等为主覆盖了皮肤定制、业务逻辑、模型配置和页面呈现等关键层面多个Global.asax入口也印证了这是一个多Web项目混合的源码集。目前已有522人学习下载。项目集中包含多个独立但同构的项目便于比较不同模块的XAF实现方式如登录权限、数据字典、报表集成等常见功能并附带了大量与界面样式相关的资源这些xafml模型差异文件与cs源码共同展示了模型驱动的演化过程配合css和js前端资源可以清晰梳理出从业务模型到界面交互的完整链路。对于想深入掌握XAF框架、降低企业级系统开发门槛的开发者是一份颇值得研读的源码资料尤其适合有一定.NET基础、希望快速上手XAF的开发者。1. 这四套系统为什么能塞进同一个框架里1.1 管理软件的三大共性我前几年一直在折腾一整套基于DevExpress XAF框架的企业项目集合里面同时包含CRM、ERP、OA、SCM四个子系统。当时把它们装进同一个基座不是因为图省事而是栽过跟头之后才明白这类管理软件的技术形态高度一致分开做四套独立项目纯粹是重复造轮子。CRM管的是客户资料、销售机会、跟进记录和合同ERP管的是商品、采购、库存、销售单据和应收应付OA管的是审批流、公告、会议室和文档SCM管的是供应商、比价、询价、交货计划。听着领域差异很大但把界面和交互刨掉剩下的全是同一件事谁在什么权限下对什么数据做什么操作按什么流程走下去。从技术视角拆开看就是一个通用规律——列表、详情表单、主从表、弹窗选择、审批流转、权限过滤、导出报表。这七样东西是每套系统的地基区别只是业务名词和流程长度。所以当时我给自己定了一个原则与其维护四个风格各异的项目不如做一套底层平台把通用能力全部沉淀进去四套系统只写各自真正不同的部分。1.2 Model-Driven开发模式是怎么运转的XAF的全称是eXtended Application Framework是DevExpress出品的.NET家族应用框架。它最核心的设计思想是模型驱动这句话很多人听过但不理解我换个说法传统开发是程序员手动拖控件、写事件、绑定数据页面长什么样完全靠手写代码决定而XAF反过来开发者只需要定义业务对象框架在运行时自动帮你生成一整套界面包括导航菜单、列表页、详情页、弹窗选择、增删改查按钮全都不用自己画。这些自动生成的界面不是写死在代码里的而是由一棵称为Application Model的元数据模型树在驱动。每个业务对象、每个View、每个按钮、每个菜单项都能在模型里找到对应节点。改模型就等于改界面很多时候连代码都不用动。我做这个项目时最喜欢拿装修来打比方业务对象是设计图Application Model是装修参数最终界面是装好的房子。图纸变了房子结构变参数变了软装风格变两者互不干扰。四套系统共用同一个图纸库但每套系统可以有不同的装修参数——这正是这套项目集合能高效维护的根本原因。2. 领域模型设计订单、客户、审批、供应商怎么串起来2.1 ORM选型XPO还是EF CoreXAF在数据访问层同时支持XPO和EF Core。XPO是DevExpress自家的ORM跟XAF的底层机制贴合得最紧密EF Core是通用生态里的主流社区资料和复杂查询能力更强支持也广。如果是从零开始规划一套包含四个业务域的项目集合我强烈建议直接用XPO不要纠结。原因不是EF Core差而是XAF里大量内部功能对XPO的支持是最完整的。比如自动生成ListView字段、成员级安全过滤、数据库结构自动升级这些在XPO下都是开箱即用的换到EF Core往往要补一堆适配代码项目一长成本就上去了。只有当你团队里所有人对EF Core已经非常熟练而且有特殊查询需求时才值得考虑EF Core路线。实体基类我习惯统一做一个带审计字段的抽象类所有模块的业务对象都从它继承。public abstract class AuditableBaseObject : DevExpress.Xpo.BaseObject { private DateTime _createdOn; private string _createdBy; private DateTime _modifiedOn; private string _modifiedBy; public DateTime CreatedOn { get { return _createdOn; } set { SetPropertyValue(nameof(CreatedOn), ref _createdOn, value); } } public string CreatedBy { get { return _createdBy; } set { SetPropertyValue(nameof(CreatedBy), ref _createdBy, value); } } public DateTime ModifiedOn { get { return _modifiedOn; } set { SetPropertyValue(nameof(ModifiedOn), ref _modifiedOn, value); } } public string ModifiedBy { get { return _modifiedBy; } set { SetPropertyValue(nameof(ModifiedBy), ref _modifiedBy, value); } } }主键统一用Guid。数据库层面Guid做主键会带来索引碎片问题但在企业内部系统这种千万行以内的数据量下这点副作用根本不用放心上。它换来的好处很实在四套系统之间经常要做数据导入、历史合并、离线创建Guid能保证不撞主键省去各种自增ID的冲突处理。2.2 共享主数据与跨模块关联项目集合里最大的设计难点不是单套系统怎么建而是四套系统怎么共享主数据又不互相依赖。一开始我们犯过一个错误让CRM模块直接引用ERP模块的客户实体运行没多久就被模块循环依赖卡死编译都过不去。后来定下的规矩很简单也一直沿用到现在所有跨模块共享的核心实体全部下沉到Common模块业务模块之间禁止直接引用对方实体。Common模块里放的是这些往来单位、联系人、员工、部门、组织架构、币种、计量单位。CRM里的客户、SCM里的供应商本质上都是同一个往来单位只是在不同业务域里的角色不同。这样客户转供应商就有了唯一的业务标识不需要靠名称匹配去猜。跨模块的业务单据关联我不用外键直接指来指去而是用“来源单据类型来源单据Oid”这种松耦合字段。比如ERP采购单上记录来源是“SCM比价单”存下比价单的Oid相关界面要用时再通过服务接口去解析。代价是查询多一步关联换来的却是模块之间彻底解耦这个账非常划算。2.3 字段审计与软删除这四套系统都涉及审批和财务审计是硬需求。上面的CreatedOn、CreatedBy只是基础我还会给关键的单据类额外挂一张操作日志表记录到字段级谁在什么时间把哪个字段从什么值改成了什么值。这个日志和业务数据分表存列表查询不参与join平时不影响性能。软删除也是企业业务的刚需。已审批单据不能物理删除否则审批痕迹、关联单据、统计报表全乱套。我在基类里加IsDeleted字段所有列表和数据访问入口统一过滤删除操作全部走软删除。这件事做起来不复杂但必须在数据访问层统一收口不能靠每个Controller自觉否则漏一处后面查数据问题查到你怀疑人生。3. Application Model控制界面生成的核心工具3.1 元数据模型到底是什么XAF里最让新手头大的就是Application Model。它是一棵巨大的元数据树在运行时由各个模块的配置合并而成。树上主要包含几大类节点BOModel对应业务对象、Views对应界面、Actions对应按钮、NavigationItems对应菜单、Options对应全局开关。每个业务对象默认都有ListView和DetailView两个View。ListView下面挂Columns控制列表显示哪些列、排列顺序、列宽度、是否排序DetailView下面挂LayoutGroups控制字段在编辑页面的布局、分组和Tab页。框架自动生成的默认视图通常能看但离能用还有距离必须通过Model Editor调整。3.2 Model Editor里最高频的几个操作Model Editor是XAF自带的配置可视化工具我日常做得最多的就是这几件事在ListView节点里调列顺序和宽度把“客户名称”“负责人”“最近跟进时间”“状态”这种关键列放前面把描述、备注等大文本字段从列表列里全部移除。列表页是给人扫一眼就能做判断的地方不是文档阅读器。在DetailView节点里调整字段分组把联系信息、扩展属性、操作记录拆成不同Tab避免一个详情页从上滚到下业务人员根本找不到重点。在Actions节点下直接禁用某些默认按钮。比如列表页的删除、批量编辑工具栏的导出功能在配置树里划掉就行完全不用动代码。在NavigationItems节点里调整菜单顺序和图标把四个系统的菜单分别放进CRM、ERP、OA、SCM四个一级分组用户一登录就能按系统进入不会全糊在一堆菜单里。Model Editor的修改会保存到模块的配置文件里跟着模块一起走。团队里每组人负责不同模块改各自的配置文件不会互相覆盖这也是XAF项目可以多人并行开发的重要基础。3.3 控制器与视图逻辑的分离界面自动生成不等于什么都不写。遇到真正的业务规则还是要靠Controllers。控制器是XAF里处理逻辑的主要手段它挂在某个业务对象或某个View上在特定时机触发执行。比如ERP订单详情页上放一个“审核”按钮点击后先校验金额和库存校验通过再生成库存流水、更新订单状态SCM供应商详情页上放一个“发起比价”按钮点击后跳转到比价流程并把供应商ID自动带进去。这些动作全部写成Controller里的自定义Action挂到对应View的Actions节点下。这套机制的好处是职责分离界面布局由Model控制业务逻辑由Controller控制两边各管各的。改字段布局不用碰逻辑改逻辑不用碰布局在四套系统并行迭代时效率高得不是一点半点。4. 模块化拆解让四套系统共享底层又互不干扰4.1 解决方案结构怎么摆整个项目集合如果按照标准XAF结构来组织大概是这样一个布局Xaf.Business.Common ├── Common.Module 公共模块基类、往来单位、员工、部门 ├── CRM.Module CRM业务模块 ├── ERP.Module ERP业务模块 ├── OA.Module OA业务模块 └── SCM.Module SCM业务模块 Xaf.Host.Win WinForms宿主程序 Xaf.Host.Web Web宿主程序每个业务模块是一个独立类库项目里面放自己的业务对象、控制器以及该模块的Application Model配置。宿主项目只负责加载模块不直接放业务代码。WinForms和Web可以共用一个业务层只是宿主壳不同这是XAF一个很成熟的做法。4.2 模块依赖与跨模块流程打通模块之间允许依赖但依赖方向必须克制。Common.Module在最底层谁都可以依赖它CRM、ERP、OA、SCM彼此之间尽量零依赖跨模块的需求统一丢到集成层解决。XAF启动时会把所有被引用的模块扫描一遍把各模块的业务对象和模型配置合并到一起。只要宿主项目引用了四个业务模块启动后菜单里自然就有四个系统的导航不需要额外组装。我遇到过最典型的跨模块流程是审批ERP的采购申请单要在OA里走审批流OA审批通过后再回写ERP单据状态。这个需求如果按老思路会让ERP模块直接引用OA模块的服务很快就循环依赖。我的做法是拆出一个集成层监听OA的事件调用ERP的服务接口做状态回写两个业务模块之间依然零引用。4.3 一套数据库还是四套数据库数据层方案我最终选的是单库多Schema一个数据库实例按模块前缀建表CRM_、ERP_、OA_、SCM_各管各的表再加公共的Common_和系统表。单库的最大好处是跨模块查询方便。ERP的销售订单要关联CRM的客户跟进记录OA的审批单要关联SCM的采购比价信息报表统计时直接跨表关联就能出数不用做服务调用拼接。备份恢复也是一份数据搞定比维护四个库简单得多。如果业务量真的涨到单库扛不住我建议先把报表和归档数据拆到独立只读库业务核心库继续保持单库。真要按模块拆成四个数据库XAF的跨库查询会非常别扭除非业务隔离需求极其强烈否则我不建议冒这个险。5. 权限与多租户这套集合最容易被低估的部分5.1 认证与授权模型XAF安全系统分两块认证处理“你是谁”授权处理“你能干什么”。默认的AuthenticationStandard通过用户名密码登录也可以扩展LDAP对接域账号授权用的是基于角色的AuthorizationRoleBased。角色下面挂权限权限再细分几层对象权限控制能否查看/创建/修改/删除某类对象成员权限控制能否访问对象的某个属性导航权限控制能否看到某个菜单。最实用的场景是给“销售经理”角色开CRM客户的读写权限给“销售助理”角色只开查看和创建禁止删除和导出。5.2 对象权限和行级过滤的实现XAF权限还有一个非常强的地方是可以按条件过滤数据行。这不是简单的菜单隐藏或按钮禁用而是直接在数据查询层面起作用。我当时给“销售员”角色的客户列表加过这样一个条件客户归属人等于当前登录用户或者所属部门等于当前用户所在部门。设置完以后同一套代码、同一个列表界面不同人登录看到的客户集合是完全不同的。这种行级过滤不需要在每次查询里手写判断安全系统统一搞定。对应到四套系统非常顺手OA的审批单只有发起人、当前审批人和归档员可见ERP的销售订单按销售员隔离SCM的供应商信息只对负责采购的成员开放。这个机制是XAF最有价值的功能之一前期把权限模型梳理清楚后面能省掉大量重复的过滤代码。5.3 多租户隔离怎么补XAF本身不做多租户但企业项目集合几乎躲不开这个问题同一个部署环境要给集团下的多个分公司或外部客户使用。我的做法是做一个租户上下文在业务基类上统一挂TenantId字段然后在数据访问层注入当前租户的过滤条件。这里有一个必须警惕的坑租户过滤和角色权限过滤是两回事不能混在一起。角色权限解决的是“这个用户能看哪些行”租户过滤解决的是“这批数据属于哪个租户”。两个条件都要生效而且必须在一个地方统一执行。我们当时在数据访问层做了收口所有查询都会自动叠加租户条件绝不依赖前端代码自觉这样才不会出现租户A的销售员在权限范围内瞥见租户B的数据。6. 列表性能、并发与升级实战里真正会卡住人的地方6.1 列表页从秒开变成秒崩的教训XAF项目里翻车最多的通常是列表性能。默认情况下如果业务对象字段多、关联多生成的ListView会把所有可显示列都加载出来。数据量在两三百行时完全没感觉一旦某个大表涨到几十万行不做处理的话页面基本就废了。我自己踩过这个坑后来固定了三层处理逻辑。第一ListView里只保留必要列把大文本、长描述全部从列表列中移除列表就是给人快速扫数据的不是看全文第二对重点列表启用Server Mode或Instant Feedback数据按需加载前端滚动时再去取数据绝不一次性把全表查进内存第三给频繁用于过滤和排序的字段建索引尤其是权限Criteria里用到的几个字段比如归属人、部门、状态这些字段没索引再好的查询条件也白搭。报表也是同样的道理。不要拿列表数据源去做聚合统计正确做法是单独写报表查询或者用XAF的DashboardView否则一个跨客户、跨时间段的业绩统计能把业务库拖垮。6.2 并发冲突和会话管理企业内部系统并发通常不高但并发冲突却天天见。XPO默认带乐观锁机制两个人同时打开同一张单据编辑后提交的人会收到冲突提示。这个机制千万别关冲突提示总比静默覆盖数据强得多。我们在生产企业现场就碰到过采购员和仓管员同时改同一张入库单的情况要不是乐观锁兜住数据会被无声覆盖后面盘点对账绝对是一团乱麻。会话管理同样不能偷懒。一个长期不释放的用户会话在XAF里会堆积大量缓存对象跑一段时间内存占用高得吓人。Web宿主我建议用按请求创建的会话生命周期请求结束就释放掉WinForms桌面端虽然没这么敏感但也别在窗体里长期持有同一个工作单元。6.3 版本升级和三方库兼容XAF的版本升级是大坑尤其是大版本跨越。Application Model的配置格式、模板源码结构、安全模块的API都可能变化。我们的步骤是先把所有模块统一升到同一版本逐个解决编译错误再检查数据库升级脚本在测试库里跑三遍确认无误后最后才上生产。数据库自动升级机制虽然方便但遇到改字段类型、合并表这种结构性调整一定要先手动备份数据库再执行升级。宁可在升级脚本里多写几行备份逻辑也不要赌自动升级任何时候都完美。网上流传的各种源码集合拿来学习参考架构完全没有问题真要放进生产环境务必先确认许可授权。DevExpress不便宜但比被法务找上门补票便宜得多这笔账要早点算清楚。以上来自我这个项目集合的主要经验从模型设计到权限再到性能和升级挑的全是最影响成败的部分。这套架构支撑了四个子系统在同一个基座上的长期迭代如果你正打算给企业的内部组织搭建类似的系统集合希望这份复盘能帮你把地基打结实一点。本文还有配套的精品资源点击获取