ABP MPA

环境说明

  • VS2017
  • SqlServer2014
  • ABP MPA

基础配置

官方模板:

  1. 下载模板
  2. 还原Nuget包
  3. 设置启动项
  4. 修改数据库连接
  5. 数据迁移
  6. 登录系统
    默认账号:admin
    默认密码:123qwe

项目概要

场景:创建人物并分配给用户

实体:

  • 任务Missions
    属性:描述Description、创建时间CreationTime、任务状态State、导航属性AssignedUser
  • 任务用户MissionUsers
    属性:名称Name

领域层创建实体并迁移

Abp.Core

创建实体(Entity)

  1. 创建 Missions 目录并创建 Mission 实体类
$ cd Abp.Core
$ mkdir Missions
$ cd Missions
$ touch Mission.cs
  1. 实体类
  • 实体类继承自EntityEntity类实现了IEntity泛型接口,IEntity通过泛型指定int类型ID主键。
  • 定义创建时间CreationTime通过审计模块中IHasCreationTime实现
  • 定义状态枚举MissionState
  • 定义导航属性AssignedUser用来保存分配任务到某个用户
using Abp.Domain.Entities;
using Abp.Domain.Entities.Auditing;
using Abp.Timing;
using JCP.Authorization.Users;
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace JCP.Missions
{
    /**
     * 定义状态枚举
     */
    public enum MissionState:byte
    {
        Disable = 0,
        Enable = 1
    }
    /**
     * 实体类
     * 继承Entity类,Entity类实现IEntity泛型接口。
     * 通过泛型指定主键ID类型,默认Entity主键类型为int类型。
     * 创建时间CreationTime通过审计模块中IHasCreationTime实现
     */
    public class Mission:Entity,IHasCreationTime
    {
        public const int TitleMaxLength = 256;
        public const int DescriptionMaxLength = 1024 * 64;

        public long? UserID { get; set; }
        //导航属性 用来保存分配任务到某个用户
        [ForeignKey("UserID")]
        public User AssignedUser { get; set; }

        [Required]
        [MaxLength(TitleMaxLength)]
        public string Title { get; set; }

        [Required]
        [MaxLength(DescriptionMaxLength)]
        public string Description { get; set; }

        public MissionState State { get; set; }

        public DateTime CreationTime { get; set; }
        //构造器
        public Mission()
        {
            CreationTime = Clock.Now;
            State = MissionState.Enable;
        }
        public Mission(string title, string description=null):this()
        {
            Title = title;
            Description = description;
        }
    }
}

创建数据集(DbSet)

使用EntityFramework需先定义DbContext类,ABP模板已经创建了DbContext文件。

在基础服务层EntityFramework中定义实体对应的DbSet以应用 Code First 数据迁移

$ vim Abp.EntityFramework/EntityFramework/DbContext.cs
//TODO: Define an IDbSet for your Entities...
public IDbSet<JCP.Missions.Mission> Missions { get; set; }

错误

public IDbSet<Mission> Missions { get; set; }
错误  CS0246  未能找到类型或命名空间名“Mission”(是否缺少 using 指令或程序集引用?)

注意:此处要将Core作为EntityFramework的引用
public IDbSet<JCP.Missions.Mission> Missions { get; set; }

执行数据迁移

使用EntityFramework的Code First模式创建数据库架构

ABP模板生成的项目开启数据迁移功能

$ cat EntityFramework/Migrations/Configuration.cs
AutomaticMigrationsEnabled = true;
  • 创建迁移
MP> Add-Migration Add_Mission_Entity

未能加载文件或程序集“Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc”或它的某一个依赖项。
找到的程序集清单定义与程序集引用不匹配。 (异常来自 HRESULT:0x80131040)

解决方法:在文件中查找(Ctrl+Shift+F)关键字“407dd0808d44fbdc”,保证项目文件配置一致。
<dependentAssembly>
  <assemblyIdentity name="Castle.Core" publicKeyToken="407dd0808d44fbdc" culture="neutral" />
  <bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
</dependentAssembly>
The Designer Code for this migration file includes a snapshot of your current Code First model. 
This snapshot is used to calculate the changes to your model when you scaffold the next migration. 
If you make additional changes to your model that you want to include in this migration, 
then you can re-scaffold it by running 'Add-Migration Add_Mission_Entity' again.
namespace JCP.Migrations
{
    using System;
    using System.Data.Entity.Migrations;
    
    public partial class Add_Mission_Entity : DbMigration
    {
        public override void Up()
        {
            CreateTable(
                "dbo.Missions",
                c => new
                    {
                        Id = c.Int(nullable: false, identity: true),
                        UserID = c.Long(),
                        Title = c.String(nullable: false, maxLength: 256),
                        Description = c.String(nullable: false),
                        State = c.Byte(nullable: false),
                        CreationTime = c.DateTime(nullable: false),
                    })
                .PrimaryKey(t => t.Id)
                .ForeignKey("dbo.AbpUsers", t => t.UserID)
                .Index(t => t.UserID);
            
        }
        
        public override void Down()
        {
            DropForeignKey("dbo.Missions", "UserID", "dbo.AbpUsers");
            DropIndex("dbo.Missions", new[] { "UserID" });
            DropTable("dbo.Missions");
        }
    }
}
  • 创建预置种子
$ cd EntityFramework/EntityFramework/Migrations/SeedData/
$ touch DefaultMissionsCreator
using JCP.EntityFramework;
using JCP.Missions;
using System.Collections.Generic;
using System.Linq;

namespace JCP.Migrations.SeedData
{
    class DefaultMissionsCreator
    {
        private readonly JCPDbContext _context;
        private static readonly List<Mission> _missions;

        public DefaultMissionsCreator(JCPDbContext context)
        {
            _context = context;
        }
        static DefaultMissionsCreator()
        {
            _missions = new List<Mission>()
            {
              new Mission("title1", "description1"),
              new Mission("title2", "description2"),
            };
        }

        public void Create()
        {
            foreach(var item in _missions)
            {
                if(_context.Missions.FirstOrDefault(x=>x.Title == item.Title) == null)
                {
                    _context.Missions.Add(item);
                }
                _context.SaveChanges();
            }
        }
    }
}

  • 配置预置种子
$ cd abp.EntityFramework/Migrations/
$ vim Configuration.cs
//Define
new DefaultMissionsCreator(context).Create();
  • 创建数据表
PM> Update-Database
Unable to update database to match the current model because there are pending changes and automatic migration is disabled. 
Either write the pending model changes to a code-based migration or enable automatic migration. 
Set DbMigrationsConfiguration.AutomaticMigrationsEnabled to true to enable automatic migration.
You can use the Add-Migration command to write the pending model changes to a code-based migration.

领域层定义仓储并实现

  • 仓储 Repository 是用来操作数据库进行数据存取
  • 仓储接口在领域层 Core 定义
  • 仓库实现在基础设施层 EntityFramework

ABP中仓储类需实现 IRepository 接口,IRepository 接口定义了常用CURD、聚合操作、同步异步。

ABP针对不同ORM框架对该接口给与了默认实现

  • 针对EF提供了EfRepositoryBase<TDbContext, TEntity, TPrimaryKey>的泛型版本的实现方式
  • 针对NHibernate提供了NhRepositoryBase<TEntity, TPrimaryKey>的泛型版本的实现方式

泛型版本的实现意味着大多数情况下,这些方法已足够应付常规实体的需求。如果方法对于实体来说不够,便需要创建实体所需的接口或类。

仓储的注意事项

  • 仓储的方法中ABP自动进行数据库连接的开启和关闭
  • 仓储方法被调用时,数据库连接自动开启且启动事务。
  • 当仓储方法调用另一个仓储方法时,实际上共享的是同一个数据库连接和事务。
  • 仓储对象是暂时性的,因为IRepository接口默认继承自ITransienetDependency接口。
  • 仓储对象只有在需要时注入,才会由IoC容量自动创建新实例。
  • 默认泛型仓储能满足大部分需求,当无法满足需求时才创建定制化的仓储。

应用服务层使用仓储

通过仓储模式可更好地将业务代码与数据库操作代码更好的分离,可针对不同的数据库有不同的实现类,而业务代码无需修改。

直接通过在应用服务层定义仓储引用,通过构造函数注入即可,ABP提供了一种注入通用仓储的方式。

$ cd Application && mkdir Missions && cd Missions

$ vim IMissionAppService.cs
namespace JCP.Missions
{
    interface IMissionAppService
    {
    }
}

$ vim MissionAppService.cs
using Abp.Domain.Repositories;

namespace JCP.Missions
{
    class MissionAppService:IMissionAppService
    {
        private readonly IRepository<Mission> _missionRepository;
        public MissionAppService(IRepository<Mission> missionRepository)
        {
            _missionRepository = missionRepository;
        }
    }
}

领域层定义仓储接口

需求:

  • 查找用户分配那些任务
  1. 在领域层,创建IRepositories目录,然后定义IBackendMissionRepository
$ cd Core && mkdir IRepositories && cd IRepositories
$ vim IBackendMissionRepository.cs

using Abp.Domain.Repositories;
using JCP.Missions;
using System.Collections.Generic;

namespace JCP.IRepositories
{
    ///<summary>
    ///自定义仓储接口
    ///</summary>
    public interface IBackendMissionRepository:IRepository<Mission>
    {
        /// <summary>
        /// 根据用户编号获取所分配的任务
        /// </summary>
        /// <param name="userid">用户编号</param>
        /// <returns>任务列表</returns>
        List<Mission> GetMissionsByUserID(long userid);
    }
}
  1. 在基础架构层,实现仓储。
  • 仓库的实现继承自模板生成的JCPRepositoryBase泛型抽象类
  • 仓储的实现实现IBackendMissionRepository接口
  • 显式声明实现类的有参构造函数,使用泛型的IDbContextProvider将数据库上下文的子类ChargeStationContext传递给父类的构造方法。
$ vim EntityFramework/EntityRepository/Repositories/BackendMissionRepository.cs

using Abp.EntityFramework;
using JCP.IRepositories;
using JCP.Missions;
using System.Collections.Generic;
using System.Linq;

namespace JCP.EntityFramework.Repositories
{
    class BackendMissionRepository:JCPRepositoryBase<Mission>, IBackendMissionRepository
    {
        public BackendMissionRepository(IDbContextProvider<JCPDbContext> dbContextProvider) : base(dbContextProvider)
        {

        }
        /// <summary>
        /// 根据用户编号获取所分配的任务
        /// </summary>
        /// <param name="userid">用户编号</param>
        /// <returns>任务列表</returns>
        public List<Mission> GetMissionsByUserID(long userid)
        {
            var query = GetAll();
            if(userid > 0)
            {
                query = query.Where(x => x.UserID == userid);
            }
            return query.ToList();
        }
    }
}

基础架构层实现仓储类

在EntityFramework项目中实现定义的IMissionRepository仓储接口

  • 定义基类
$ vim MissionBaseRepository.cs

错误警告

错误

错误  CS0246  未能找到类型或命名空间名“Mission”(是否缺少 using 指令或程序集引用?) 
JCP.EntityFramework 
D:\ASP.NET\VS2017\MPA\JCP.EntityFramework\EntityFramework\JCPDbContext.cs

错误  CS0006  未能找到元数据文件
“D:\ASP.NET\VS2017\MPA\JCP.EntityFramework\bin\Debug\JCP.EntityFramework.dll”

警告

发现“Castle.Core”的不同版本间存在无法解决的冲突。当日志详细信息设置为“详细”时,这些引用冲突将会在生成日志中列出。    

发现同一依赖程序集的不同版本间存在冲突。在 Visual Studio 中,请双击此警告(或选择此警告并按 Enter)以修复冲突;否则,请将以下绑定重定向添加到应用程序配置文件中的“runtime”节点: <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"><dependentAssembly><assemblyIdentity name="System.ValueTuple" culture="neutral" publicKeyToken="cc7b13ffcd2ddd51" /><bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" /></dependentAssembly></assemblyBinding>           

发现同一依赖程序集的不同版本间存在冲突。请将项目文件中的“AutoGenerateBindingRedirects”属性设置为 true。有关详细信息,请参阅 http://go.microsoft.com/fwlink/?LinkId=294190。      

未能找到引用的组件“Microsoft.Owin.Security.Google”   

未能找到引用的组件“Microsoft.Owin.Security.Twitter”。 

“System.ValueTuple, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51”上的显式绑定重定向与自动生成的绑定重定向冲突。请考虑将其从应用程序配置文件中删除或者禁用自动生成的绑定重定向。生成会将其替换为:“<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" xmlns="urn:schemas-microsoft-com:asm.v1" />”。

未能解析此引用。未能找到程序集“Microsoft.Owin.Security.Twitter, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL”。请检查磁盘上是否存在该程序集。 如果您的代码需要此引用,则可能出现编译错误。  JCP.Web         

未能解析此引用。未能找到程序集“Microsoft.Owin.Security.Google, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL”。请检查磁盘上是否存在该程序集。 如果您的代码需要此引用,则可能出现编译错误。   JCP.Web         

Your project specifies TypeScriptToolsVersion 2.2, but a matching compiler was not found. The latest available TypeScript compiler will be used (3.0). To remove this warning, install the TypeScript 2.2 SDK or update the value of TypeScriptToolsVersion.    JCP.Web         

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • 原帖 下载源码 用我的方法配置好你的环境 译者注:必须要下源码,作者文章中的代码比较飘逸,会跳过部分声明,你可能会...
    时代小召唤阅读 1,348评论 3 1
  • 一、简历准备 1、个人技能 (1)自定义控件、UI设计、常用动画特效 自定义控件 ①为什么要自定义控件? Andr...
    lucas777阅读 5,200评论 2 54
  • 《首页和今日看点收录文章》 文:丹菡 在茂密的森林里,有颗巨大的黄桷树,粗壮的枝丫不停地向天空挺进,蔓延,扩张着他...
    丹菡阅读 524评论 6 10
  • 字符串 r表示内部的字符串默认不转义
    智勇双全的小六阅读 128评论 0 0
  • 一位新同事留言提问: 如何和新客户沟通呢? 这个问题是实体门店中,销售人员经常会碰到的一个小难题。 大部分人可能都...
    楠飞阅读 316评论 0 1