目录
一、常见问题
1. Swagger界面找不到接口
2.注释不显示
步骤一:新建xml输出
步骤二:代码引用xml文件
3.日期格式化带T
4.输出变量格式化(小驼峰->大驼峰)
二、报错解析
1.Swagger界面报错
2.底层代码调用失败
3.仓储引用失败
三、通用错误排查方法
1.查控制台程序输出
2.查日志
四、疑难解答
1. 两个仓储的区别
2.EFcore如何打印sql
3.ABP和 Volo.ABP区别
五、开发人员对接口测试流程
六、EFcore疑难专项
1.主键配置
2.映射失败的几大原因
3.任务中途取消
4.派生类型无法配置密钥
一、常见问题
1. Swagger界面找不到接口
- 代码问题:WebApi控制器(服务)没有继承ApplicationService
- 缓存问题:给浏览器清理下缓存,重启VS2022
2.注释不显示
步骤一:新建xml输出
方法一:哪个类库不显示,改对应类库的工程文件
<GenerateDocumentationFile>true</GenerateDocumentationFile><DocumentationFile>bin\Debug\net6.0\Acme.BookStore.HttpApi.Host.xml</DocumentationFile>
方法二: 对应类库的右键属性设置
输入框填写的内容应该和工程文件种的一致:
bin\Debug\net6.0\Acme.BookStore.HttpApi.Host.xml
步骤二:代码引用xml文件
API模块种补上代码,当然也可以直接字符串写死 xmlFile = " Acme.BookStore.HttpApi.Host.xml " ,怎么命名都好,总之,xmlFile这个文件名和上面步骤一设置的文件名一致。
private static void ConfigureSwaggerServices(ServiceConfigurationContext context, IConfiguration configuration)
{var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";//"Acme.BookStore.HttpApi.Host.xml"var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);context.Services.AddAbpSwaggerGenWithOAuth(configuration["AuthServer:Authority"],new Dictionary<string, string>{{"BookStore", "BookStore API"}},options =>{options.SwaggerDoc("v1", new OpenApiInfo { Title = "BookStore API", Version = "v1" });options.DocInclusionPredicate((docName, description) => true);options.CustomSchemaIds(type => type.FullName);options.IncludeXmlComments(xmlPath); // 添加这一行});
}
修改后的效果:
注意:工程文件只需要配置对应的应用层即可!不要再配置Host,否则会覆盖你应用层生成的xml。
3.日期格式化带T
需求如下,输出的日期带T,我们不需要显示T
代码编写位置
加上这个:
Configure<AbpJsonOptions>(options =>
{options.DefaultDateTimeFormat = "yyyy-MM-dd HH:mm:ss";
});
如果你想全局配置,那也可以将上述代码加在Program.cs这里,所有模块都会实现这样的效果:
(注意:我觉得日期格式化,虽然上面的方法可以解决问题,但是始终是不够灵活,因为有时候返回出去的是yyyy-MM-dd而不是完整的时间,因此我觉得用tostring方法手动调整,返回字符串出去 或者让前端处理,或许会更好)
4.输出变量格式化(小驼峰->大驼峰)
方法一:全局配置
builder.Services.AddMvc().AddJsonOptions(options =>
{options.JsonSerializerOptions.PropertyNamingPolicy = null;
});
效果:
方法二:每个字段都用特性配置
例如:
效果:
二、报错解析
1.Swagger界面报错
错误:Fetch errorInternal Server Error /swagger/v1/swagger.json
方法:查看控制台程序报错情况
可能情况:
- WebApi控制器(服务)某个方法使用了public修饰符,但是没有[HTTPGet]或其他请求标识特性,ABP 框架默认将其视为接口,因此会报错。(解决方法:①加上请求特性,用作接口;②或者用private修饰符声明私有方法,供其他接口使用,不作为对外开放接口)
- WebApi控制器(服务)出现路由同名现象,引发冲突。
2.底层代码调用失败
错误:Castle.Proxies.BookAppServiceProxy
原因:底层代码接口未注入到依赖容器,服务找不到这个底层代码接口
解决方案:底层代码继承ApplicationService或实现ITransientDependency
3.仓储引用失败
报错信息:“Acme.BookStore.Books.Book”不能用作泛型类型或方法“IRepository<TEntity, TKey>”中的类型参数“TEntity”。没有从“Acme.BookStore.Books.Book”到“Volo.Abp.Domain.Entities.IEntity<System.Guid>”的隐式引用转换。
原因:实体类没有继承AuditedAggregateRoot<Guid>或者AggregateRoot<Guid>或者DOEntity,因为IRepository<TEntity, TKey>需要继承自 IEntity<TKey> 接口。
三、通用错误排查方法
1.查控制台程序输出
2.查日志
- 位置参考:\src\xxxx.HttpApi.Host\Logs
四、疑难解答
1. 两个仓储的区别
一个指定了主键类型,调用GetAsync(根据id查表)、DeleteAsync(根据id删数据)等方法时,直接传入对应类型的id即可,否则需要传入LINQ的表达式树
private readonly IRepository<Book, Guid> _bookRepository;//await _bookRepository.DeleteAsync(id);
private readonly IRepository<Book> _bookRepository;//await _bookRepository.DeleteAsync(x=>x.Id==Guid.NewGuid());
2.前端传入枚举,让传参更清晰
//前端传入方式:post请求或者get请求直接传一个枚举值(string) 或者 索引(int) 都可(例如querytype="Grade1",或者querytype=1)//接收前端的实体:
public class SearchInput
{//可以写其他属性public StudentEnum querytype { set; get; }
}//枚举示例:
public enum StudentEnum
{Grade1 = 1,Grade2 = 2,Grade3 = 3
}//后端可以这样判断:
bool isgrade1 = input.querytype == StudentEnum.Grade1;// 上述两种入参,都可以输出True
3.ABP和 Volo.ABP区别
ABP和 Volo.ABP 是两个不同的框架,后者版本更加新,有基于最新的 ASP.NET Core 技术栈且ORM-EFcore采用高版本的。
五、开发人员对接口测试流程
- 检索条件是否生效
- 分页效果(测试第一到第三页)
- 比对数据
- 查看控制台程序和日志是否存在报错现象
六、EFcore疑难专项
1.主键配置
protected override void OnModelCreating(ModelBuilder modelBuilder)
{modelBuilder.Entity<xxxModel>(b => {b.ConfigureByConvention();// b.HasKey(x => new { x.Id }); // 其实不需要显式指定主键,只要你的实体中含有属性名称为Id的属性,EF Core 会自动识别 Id 为主键(注意不可识别id)});
}
不需要显式指定主键,只要你的实体中含有属性名称为Id的属性,EF Core 会自动识别 Id 为主键(注意不可识别id,是区分大小写的)
2.映射失败的几大原因
- 配置问题:Dbcontext写错字段类型,例如NUMBER(8,2)写成了NUMBER(8),如果高级版本采用ConfigureByConvention配置,不存在这个问题。
- 入参问题:前端传入和后端接收字段类型不一致,例如Guid和string
- 映射关系问题:没有创建CreateMap,导致映射失败(Mapping failed)
3.任务中途取消
报错:A Task was canceled
Update操作:根据传递实体的主键,查不到数据(检查主键是否正确)
4.派生类型无法配置密钥
A key cannot be configured on 'xxxx' because it is a derived type
实体字段配置问题:数据库实体不能继承另一个实体
反例:
public class A
{}
public class B : A
{}
正例:
//抽取公共字段出来,写在Common类中
public class A : Common
{}
public class B : Common
{}
5.实体继承 Entity<int>和继承AuditedAggregateRoot<id>的区别
讲直白点:
①仓储查表
必须继承一个,才能使用下述方式(注入仓储)来通过EFcore查表
private readonly IRepository<Book, Guid> _bookRepository;
②少写字段
继承Entity<int> ,实体可以少写一个 Id 字段
继承AuditedAggregateRoot<int>,实体可以少写很多字段例如创建时间、修改时间、操作用户等
③查表拼接
查表会拼接你这个实体所有的字段,包括继承的,这说明强制要求我们的表主键字段含有Id,否则就会出错。
不过也可以强制修改,例如我有一个表,继承了Entity<int>:
查表生成的sql是这样的:
SELECT Id,[pid],[pname] FROM [TestABP].[dbo].[Parent]
我没有Id字段,就会导致查这张表失败,解决方案二选一如下:
①改名,pid改为Id(不区分大小写,改成id Id ID iD都可以)
②忽略Id字段,让EFcore拼接查询sql的时候忽略它,以下两个步骤都必不可少,没有主键标记EFcore也会报错:
builder.Entity<Parent>(b =>{//b.ToTable(BookStoreConsts.DbTablePrefix + "Books", BookStoreConsts.DbSchema);b.HasKey(x => x.pid);// 声明本表主键【步骤一】b.Ignore(x => x.Id); //忽略继承的主键【步骤二】b.ConfigureByConvention();// 自动配置基本属性});
说了那么多,建议还是按它们那套标准来:建表必定含有Id字段作为主键,然后实体继承了Entity<int>后就不要重复写id字段了(重复写id字段可以查表,但是EFcore不支持这样映射,还是会报如下的错)。
6.EFcore如何打印sql
在上下文类加上以下代码即可:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{optionsBuilder// 输出到 VS 输出窗口.LogTo(msg => System.Diagnostics.Trace.WriteLine(msg), new[] { Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.CommandExecuting });}
(吐槽一下:EFcore又难用,拼出来的SQL又复杂,还不如用主流的ORM框架!)