使用.NET 6开发TodoList应用(5)——领域实体创建
需求
上一篇文章中我们完成了数据存储服务的接入,从这一篇开始将正式进入业务逻辑部分的开发。
首先要定义和解决的问题是,根据TodoList
项目的需求,我们应该设计怎样的数据实体,如何去进行操作?
长文预警!包含大量代码
目标
在本文中,我们希望达到以下几个目标:
- 定义领域实体;
- 通过数据库操作领域实体;
原理和思路
虽然TodoList
是一个很简单的应用,业务逻辑并不复杂,至少在这个系列文章中我并不想使其过度复杂。但是我还是打算借此简单地涉及领域驱动开发(DDD)的基础概念。
首先比较明确的是,我们的实体对象应该有两个:TodoList
和TodoItem
,并且一个TodoList
是由多个TodoItem
的列表构成,除此以外在实际的开发中,我们可能还需要追踪实体的变更情况,比如需要知道创建时间/修改时间/创建者/修改者,这种需求一般作为审计要求出现,而对实体的审计又是一个比较通用的需求。所以我们会将实体分成两部分:和业务需求直接相关的属性,以及和实体审计需求相关的属性。
其次,对于实体的数据库配置,有两种方式:通过Attribute
或者通过IEntityTypeConfiguration<T>
以代码的方式进行。我推荐使用第二种方式,将所有的具体配置集中到Infrastructure
层去管理,避免后续修改字段属性而去频繁修改位于Domain
层的实体对象定义,我们希望实体定义本身是稳定的。
最后,对于DDD来说有一些核心概念诸如领域事件,值对象,聚合根等等,我们都会在定义领域实体的时候有所涉及,但是目前还不会过多地使用。关于这些基本概念的含义,请参考这篇文章:DDD领域驱动设计基本理论知识总结。在我们的开发过程中,会进行一些精简,有部分内容也会随着后续的文章逐步完善。
实现
基础的领域概念框架搭建
所有和领域相关的概念都会进入到Domain
这个项目中,我们首先在Domain
项目里新建文件夹Base
用于存放所有的基础定义,下面将一个一个去实现。(另一种方式是把这些最基础的定义单独提出去新建一个SharedDefinition
类库并让Domain
引用这个项目。)
基础实体定义以及可审计实体定义
我这两个类都应该是抽象基类,他们的存在是为了让我们的业务实体继承使用的,并且为了允许不同的实体可以定义自己主键的类型,我们将基类定义成泛型的。
AuditableEntity.cs
namespace TodoList.Domain.Base;
public abstract class AuditableEntity
{
public DateTime Created { get; set; }
public string? CreatedBy { get; set; }
public DateTime? LastModified { get; set; }
public string? LastModifiedBy { get; set; }
}
在Base
里增加Interface
文件夹来保存接口定义。
IEntity.cs
namespace TodoList.Domain.Base.Interfaces;
public interface IEntity<T>
{
public T Id { get; set; }
}
除了这两个对象之外,我们还需要增加关于领域事件框架的定义。
DomainEvent.cs
namespace TodoList.Domain.Base;
public abstract class DomainEvent
{
protected DomainEvent()
{
DateOccurred = DateTimeOffset.UtcNow;
}
public bool IsPublished { get; set; }
public DateTimeOffset DateOccurred { get; protected set; } = DateTime.UtcNow;
}
IHasDomainEvent.cs
namespace TodoList.Domain.Base.Interfaces;
public interface IHasDomainEvent
{
public List<DomainEvent> DomainEvents { get; set; }
}
我们还剩下Aggregate Root
, ValueObject
和Domain Service
以及Domain Exception
,其他的相关概念暂时就不涉及了。
IAggregateRoot.cs
namespace TodoList.Domain.Base.Interfaces;
// 聚合根对象仅仅作为标记来使用
public interface IAggregateRoot { }
ValueObject
的实现有几乎固定的写法,请参考:https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/microservice-ddd-cqrs-patterns/implement-value-objects
ValueObject.cs
namespace TodoList.Domain.Base;
public abstract class ValueObject
{
protected static bool EqualOperator(ValueObject left, ValueObject right)
{
if (left is null ^ right is null)
{
return false;
}
return left?.Equals(right!) != false;
}
protected static bool NotEqualOperator(ValueObject left, ValueObject right)
{
return !(EqualOperator(left, right));
}
protected abstract IEnumerable<object> GetEqualityComponents();
public override bool Equals(object? obj)
{
if (obj == null || obj.GetType() != GetType())
{
return false;
}
var other = (ValueObject)obj;
return GetEqualityComponents().SequenceEqual(other.GetEqualityComponents());
}
public override int GetHashCode()
{
return GetEqualityComponents()
.Select(x => x != null ? x.GetHashCode() : 0)
.Aggregate((x, y) => x ^ y);
}
}
关于Domain Exception
的定义,是根据业务内容来确定的,暂时不在Base
里实现,而是放到各个聚合根的层级里。
定义TodoLIst/TodoItem实体
TodoList
对象从领域建模上来说属于聚合根,并且下一节将要实现的TodoItem
是构成该聚合根的一部分,业务意思上不能单独存在。有一种实现方式是按照聚合根的关联性进行代码组织:即在Domain
项目里新建文件夹AggregateRoots/TodoList
来保存和这个聚合根相关的所有业务定义:即:Events
/Exceptions
和Enums
三个文件夹用于存放对应的内容。但是这样可能会导致项目的目录层级太多,实际上在这里,我更倾向于在Domain
项目的根目录下创建Entities
/Events
/Enums
/Exceptions
/ValueObjects
文件夹来扁平化领域模型,对于世纪的开发查找起来也并不麻烦。所以才去后一种方式,然后在Entities
中创建TodoItem
和TodoList
实体:
TodoItem.cs
using TodoList.Domain.Base;
using TodoList.Domain.Base.Interfaces;
using TodoList.Domain.Enums;
using TodoList.Domain.Events;
namespace TodoList.Domain.Entities;
public class TodoItem : AuditableEntity, IEntity<Guid>, IHasDomainEvent
{
public Guid Id { get; set; }
public string? Title { get; set; }
public PriorityLevel Priority { get; set; }
private bool _done;
public bool Done
{
get => _done;
set
{
if (value && _done == false)
{
DomainEvents.Add(new TodoItemCompletedEvent(this));
}
_done = value;
}
}
public TodoList List { get; set; } = null!;
public List<DomainEvent> DomainEvents { get; set; } = new List<DomainEvent>();
}
PriorityLevel.cs
namespace TodoList.Domain.Enums;
public enum PriorityLevel
{
None = 0,
Low = 1,
Medium = 2,
High = 3
}
TodoItemCompletedEvent.cs
using TodoList.Domain.Base;
using TodoList.Domain.Entities;
namespace TodoList.Domain.Events;
public class TodoItemCompletedEvent : DomainEvent
{
public TodoItemCompletedEvent(TodoItem item) => Item = item;
public TodoItem Item { get; }
}
TodoList.cs
using TodoList.Domain.Base;
using TodoList.Domain.Base.Interfaces;
using TodoList.Domain.ValueObjects;
namespace TodoList.Domain.Entities;
public class TodoList : AuditableEntity, IEntity<Guid>, IHasDomainEvent, IAggregateRoot
{
public Guid Id { get; set; }
public string? Title { get; set; }
public Colour Colour { get; set; } = Colour.White;
public IList<TodoItem> Items { get; private set; } = new List<TodoItem>();
public List<DomainEvent> DomainEvents { get; set; } = new List<DomainEvent>();
}
为了演示ValueObject
,添加了一个Colour
对象,同时添加了一个领域异常对象UnsupportedColourException
Colour.cs
using TodoList.Domain.Base;
namespace TodoList.Domain.ValueObjects;
public class Colour : ValueObject
{
static Colour() { }
private Colour() { }
private Colour(string code) => Code = code;
public static Colour From(string code)
{
var colour = new Colour { Code = code };
if (!SupportedColours.Contains(colour))
{
throw new UnsupportedColourException(code);
}
return colour;
}
public static Colour White => new("#FFFFFF");
public static Colour Red => new("#FF5733");
public static Colour Orange => new("#FFC300");
public static Colour Yellow => new("#FFFF66");
public static Colour Green => new("#CCFF99 ");
public static Colour Blue => new("#6666FF");
public static Colour Purple => new("#9966CC");
public static Colour Grey => new("#999999");
public string Code { get; private set; } = "#000000";
public static implicit operator string(Colour colour) => colour.ToString();
public static explicit operator Colour(string code) => From(code);
public override string ToString() => Code;
protected static IEnumerable<Colour> SupportedColours
{
get
{
yield return White;
yield return Red;
yield return Orange;
yield return Yellow;
yield return Green;
yield return Blue;
yield return Purple;
yield return Grey;
}
}
protected override IEnumerable<object> GetEqualityComponents()
{
yield return Code;
}
}
UnsupportedColourException.cs
namespace TodoList.Domain.Exceptions;
public class UnsupportedColourException : Exception
{
public UnsupportedColourException(string code)
: base($"Colour \"{code}\" is unsupported.")
{
}
}
关于领域服务的内容我们暂时不去管,继续看看如何向数据库配置实体对象。
领域实体的数据库配置
这部分内容相对会熟悉一些,我们在Infrastructure/Persistence
中新建文件夹Configurations
用于存放实体配置:
TodoItemConfiguration.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using TodoList.Domain.Entities;
namespace TodoList.Infrastructure.Persistence.Configurations;
public class TodoItemConfiguration : IEntityTypeConfiguration<TodoItem>
{
public void Configure(EntityTypeBuilder<TodoItem> builder)
{
builder.Ignore(e => e.DomainEvents);
builder.Property(t => t.Title)
.HasMaxLength(200)
.IsRequired();
}
}
TodoListConfiguration.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace TodoList.Infrastructure.Persistence.Configurations;
public class TodoListConfiguration : IEntityTypeConfiguration<Domain.Entities.TodoList>
{
public void Configure(EntityTypeBuilder<Domain.Entities.TodoList> builder)
{
builder.Ignore(e => e.DomainEvents);
builder.Property(t => t.Title)
.HasMaxLength(200)
.IsRequired();
builder.OwnsOne(b => b.Colour);
}
}
修改DbContext
因为下一篇里我们将要使用Repository
模式,所以我们可以不需要让TodoListDbContext
继续继承IApplicationDbContext
了。关于直接在Application
里使用Context比较简单,就不继续演示了。
在这一步里面,我们需要完成以下几件事:
- 添加数据表;
- 重写SaveChangesAsync方法,自动补充审计相关字段值,并且在此发送领域事件;
对于第一件事,很简单。向TodoListDbContext.cs
类定义中加入:
// TodoLIst实体与命名空间名称有冲突,所以需要显示引用其他命名空间里的对象
public DbSet<Domain.Entities.TodoList> TodoLists => Set<Domain.Entities.TodoList>();
public DbSet<TodoItem> TodoItems => Set<TodoItem>();
对于第二件事,我们需要先向Application/Common/Interfaces
中添加一个接口用于管理领域事件的分发,但是在讲到CQRS
之前,我们暂时以Dummy
的方式实现这个接口,因为将要使用第三方框架实现具体逻辑,所以我们把实现类放到Infrastrcucture/Services
目录下,并在TodoListDbContext
中注入使用。
IDomainEventService.cs
using TodoList.Domain.Base;
namespace TodoList.Application.Common.Interfaces;
public interface IDomainEventService
{
Task Publish(DomainEvent domainEvent);
}
DomainEventService.cs
using Microsoft.Extensions.Logging;
using TodoList.Application.Common.Interfaces;
using TodoList.Domain.Base;
namespace TodoList.Infrastructure.Services;
public class DomainEventService : IDomainEventService
{
private readonly ILogger<DomainEventService> _logger;
public DomainEventService(ILogger<DomainEventService> logger)
{
_logger = logger;
}
public async Task Publish(DomainEvent domainEvent)
{
// 在这里暂时什么都不做,到CQRS那一篇的时候再回来补充这里的逻辑
_logger.LogInformation("Publishing domain event. Event - {event}", domainEvent.GetType().Name);
}
}
在DependencyInjection
中注入:
// 省略以上...并且这一句可以不需要了
// services.AddScoped<IApplicationDbContext>(provider => provider.GetRequiredService<TodoListDbContext>());
// 增加依赖注入
services.AddScoped<IDomainEventService, DomainEventService>();
return services;
最终的TodoListDbContext
实现如下:
TodoListDbContext.cs
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using TodoList.Application.Common.Interfaces;
using TodoList.Domain.Base;
using TodoList.Domain.Base.Interfaces;
using TodoList.Domain.Entities;
namespace TodoList.Infrastructure.Persistence;
public class TodoListDbContext : DbContext
{
private readonly IDomainEventService _domainEventService;
public TodoListDbContext(
DbContextOptions<TodoListDbContext> options,
IDomainEventService domainEventService) : base(options)
{
_domainEventService = domainEventService;
}
public DbSet<Domain.Entities.TodoList> TodoLists => Set<Domain.Entities.TodoList>();
public DbSet<TodoItem> TodoItems => Set<TodoItem>();
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new())
{
// 在我们重写的SaveChangesAsync方法中,去设置审计相关的字段,目前对于修改人这个字段暂时先给个定值,等到后面讲到认证鉴权的时候再回过头来看这里
foreach (var entry in ChangeTracker.Entries<AuditableEntity>())
{
switch (entry.State)
{
case EntityState.Added:
entry.Entity.CreatedBy = "Anonymous";
entry.Entity.Created = DateTime.UtcNow;
break;
case EntityState.Modified:
entry.Entity.LastModifiedBy = "Anonymous";
entry.Entity.LastModified = DateTime.UtcNow;
break;
}
}
// 在写数据库的时候同时发送领域事件,这里要注意一定要保证写入数据库成功后再发送领域事件,否则会导致领域对象状态的不一致问题。
var events = ChangeTracker.Entries<IHasDomainEvent>()
.Select(x => x.Entity.DomainEvents)
.SelectMany(x => x)
.Where(domainEvent => !domainEvent.IsPublished)
.ToArray();
var result = await base.SaveChangesAsync(cancellationToken);
await DispatchEvents(events);
return result;
}
protected override void OnModelCreating(ModelBuilder builder)
{
// 应用当前Assembly中定义的所有的Configurations,就不需要一个一个去写了。
builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
base.OnModelCreating(builder);
}
private async Task DispatchEvents(DomainEvent[] events)
{
foreach (var @event in events)
{
@event.IsPublished = true;
await _domainEventService.Publish(@event);
}
}
}
验证
生成Migrations
老办法,先生成Migrations。
$ dotnet ef migrations add AddEntities -p src/TodoList.Infrastructure/TodoList.Infrastructure.csproj -s src/TodoList.Api/TodoList.Api.csproj
Build started...
Build succeeded.
[14:06:15 INF] Entity Framework Core 6.0.1 initialized 'TodoListDbContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer:6.0.1' with options: MigrationsAssembly=TodoList.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Done. To undo this action, use 'ef migrations remove'
使用种子数据更新数据库
为了演示效果,在Infrastructure/Persistence/
下创建TodoListDbContextSeed.cs
文件并初始化种子数据:
TodoListDbContextSeed.cs
using Microsoft.EntityFrameworkCore;
using TodoList.Domain.Entities;
using TodoList.Domain.Enums;
using TodoList.Domain.ValueObjects;
namespace TodoList.Infrastructure.Persistence;
public static class TodoListDbContextSeed
{
public static async Task SeedSampleDataAsync(TodoListDbContext context)
{
if (!context.TodoLists.Any())
{
var list = new Domain.Entities.TodoList
{
Title = "Shopping",
Colour = Colour.Blue
};
list.Items.Add(new TodoItem { Title = "Apples", Done = true, Priority = PriorityLevel.High});
list.Items.Add(new TodoItem { Title = "Milk", Done = true });
list.Items.Add(new TodoItem { Title = "Bread", Done = true });
list.Items.Add(new TodoItem { Title = "Toilet paper" });
list.Items.Add(new TodoItem { Title = "Pasta" });
list.Items.Add(new TodoItem { Title = "Tissues" });
list.Items.Add(new TodoItem { Title = "Tuna" });
list.Items.Add(new TodoItem { Title = "Water" });
context.TodoLists.Add(list);
await context.SaveChangesAsync();
}
}
public static async Task UpdateSampleDataAsync(TodoListDbContext context)
{
var sampleTodoList = await context.TodoLists.FirstOrDefaultAsync();
if (sampleTodoList == null)
{
return;
}
sampleTodoList.Title = "Shopping - modified";
// 演示更新时审计字段的变化
context.Update(sampleTodoList);
await context.SaveChangesAsync();
}
}
在应用程序初始化的扩展中进行初始化和更新:
ApplicationStartupExtensions.cs
// 省略以上...
try
{
var context = services.GetRequiredService<TodoListDbContext>();
context.Database.Migrate();
// 生成种子数据
TodoListDbContextSeed.SeedSampleDataAsync(context).Wait();
// 更新部分种子数据以便查看审计字段
TodoListDbContextSeed.UpdateSampleDataAsync(context).Wait();
}
catch (Exception ex)
// 省略以下...
运行Api
项目,得到下面的输出,中间我省略了一些SQL语句的输出:
$ dotnet run --project src/TodoList.Api
Building...
# ...省略
[14:06:24 INF] Applying migration '20211222060615_AddEntities'.
# ...省略
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20211222060615_AddEntities', N'6.0.1');
# ...省略,注意下面的三个domain event,因为我们在造种子数据的时候有设置三个TodoItem标记为已完成,将会触发event。
[14:06:25 INF] Publishing domain event. Event - TodoItemCompletedEvent
[14:06:25 INF] Publishing domain event. Event - TodoItemCompletedEvent
[14:06:25 INF] Publishing domain event. Event - TodoItemCompletedEvent
# ...省略
[14:06:25 INF] Now listening on: https://localhost:7039
[14:06:25 INF] Now listening on: http://localhost:5050
[14:06:25 INF] Application started. Press Ctrl+C to shut down.
[14:06:25 INF] Hosting environment: Development
# ...省略
我们再去看看数据库中的数据:
TodoLists
数据表:
TodoItems
数据表
__EFMigrationsHistory
迁移表:
总结
在本文中,我们着手搭建了基本的领域驱动设计对应的Domain
层实现,包括两个领域实体对象及其关联的其他知识。最后通过种子数据的方式进行数据库数据操作的验证,下一篇我们将继续实现一个通用的Repository
模式。
参考资料
系列导航
- 使用.NET 6开发TodoList应用(1)——系列背景
- 使用.NET 6开发TodoList应用(2)——项目结构搭建
- 使用.NET 6开发TodoList应用(3)——引入第三方日志
- 使用.NET 6开发TodoList应用(4)——引入数据存储
- 使用.NET 6开发TodoList应用(5)——领域实体创建
- 使用.NET 6开发TodoList应用(5.1)——实现Repository模式
- 使用.NET 6开发TodoList应用(6)——实现POST请求
- 使用.NET 6开发TodoList应用(6.1)——实现CQRS模式
- 使用.NET 6开发TodoList应用(6.2)——实现AutoMapper
- 使用.NET 6开发TodoList应用(7)——实现GET请求
- 使用.NET 6开发TodoList应用(8)——实现全局异常处理
- 使用.NET 6开发TodoList应用(9)——实现PUT请求
- 使用.NET 6开发TodoList应用(10)——实现PATCH请求
- 使用.NET 6开发TodoList应用(11)——HTTP请求幂等性的考虑
- 使用.NET 6开发TodoList应用(12)——实现接口请求验证
- 使用.NET 6开发TodoList应用(13)——实现ActionFilter
- 使用.NET 6开发TodoList应用(14)——实现查询分页
- 使用.NET 6开发TodoList应用(15)——实现查询过滤
- 使用.NET 6开发TodoList应用(16)——实现查询搜索
- 使用.NET 6开发TodoList应用(17)——实现查询排序
- 使用.NET 6开发TodoList应用(18)——实现数据塑形
- 使用.NET 6开发TodoList应用(19)——实现HATEAOS支持
- 使用.NET 6开发TodoList应用(20)——处理OPTION和HEAD请求
- 使用.NET 6开发TodoList应用(21)——实现Root Document
- 使用.NET 6开发TodoList应用(22)——实现API版本控制
- 使用.NET 6开发TodoList应用(23)——实现缓存
- 使用.NET 6开发TodoList应用(24)——实现请求限流和阈值控制
- 使用.NET 6开发TodoList应用(25)——实现基于JWT的Identity功能
- 使用.NET 6开发TodoList应用(26)——实现RefreshToken
- 使用.NET 6开发TodoList应用(27)——实现Configuration和Option的强类型绑定
- 使用.NET 6开发TodoList应用(28)——实现API的Swagger文档化
- 使用.NET 6开发TodoList应用(29)——实现应用程序健康检查
- 使用.NET 6开发TodoList应用(30)——实现本地化功能
- 使用.NET 6开发TodoList应用(31)——实现Docker打包和部署
- 使用.NET 6开发TodoList应用(32)——实现基于Github Actions和ACI的CI/CD
使用.NET 6开发TodoList应用(5)——领域实体创建的更多相关文章
- 使用.NET 6开发TodoList应用(5.1)——实现Repository模式
需求 经常写CRUD程序的小伙伴们可能都经历过定义很多Repository接口,分别做对应的实现,依赖注入并使用的场景.有的时候会发现,很多分散的XXXXRepository的逻辑都是基本一致的,于是 ...
- 使用.NET 6开发TodoList应用(6)——使用MediatR实现POST请求
需求 需求很简单:如何创建新的TodoList和TodoItem并持久化. 初学者按照教程去实现的话,应该分成以下几步:创建Controller并实现POST方法:实用传入的请求参数new一个数据库实 ...
- 使用.NET 6开发TodoList应用文章索引
系列导航 使用.NET 6开发TodoList应用(1)--系列背景 使用.NET 6开发TodoList应用(2)--项目结构搭建 使用.NET 6开发TodoList应用(3)--引入第三方日志 ...
- 使用.NET 6开发TodoList应用(填坑1)——实现当前登录用户获取
系列导航及源代码 使用.NET 6开发TodoList应用文章索引 需求 在前面的文章里使用.NET 6开发TodoList应用(5)--领域实体创建,我们留了一个坑还没有填上,就是在数据库保存的时候 ...
- 使用.NET 6开发TodoList应用(3)——引入第三方日志库
需求 在我们项目开发的过程中,使用.NET 6自带的日志系统有时是不能满足实际需求的,比如有的时候我们需要将日志输出到第三方平台上,最典型的应用就是在各种云平台上,为了集中管理日志和查询日志,通常会选 ...
- 使用.NET 6开发TodoList应用(1)——系列背景
前言 想到要写这样一个系列博客,初衷有两个:一是希望通过一个实践项目,将.NET 6 WebAPI开发的基础知识串联起来,帮助那些想要入门.NET 6服务端开发的朋友们快速上手,对使用.NET 6开发 ...
- 使用.NET 6开发TodoList应用(2)——项目结构搭建
为了不影响阅读的体验,我把系列导航放到文章最后了,有需要的小伙伴可以直接通过导航跳转到对应的文章 : P TodoList需求简介 首先明确一下我们即将开发的这个TodoList应用都需要完成什么功能 ...
- 使用.NET 6开发TodoList应用(4)——引入数据存储
需求 作为后端CRUD程序员(bushi,数据存储是开发后端服务一个非常重要的组件.对我们的TodoList项目来说,自然也需要配置数据存储.目前的需求很简单: 需要能持久化TodoList对象并对其 ...
- 福利到!Rafy(原OEA)领域实体框架 2.22.2067 发布!
距离“上次框架完整发布”已经过去了一年半了,应群中的朋友要求,决定在国庆放假之际,把最新的框架发布出来,并把帮助文档整理出来,这样可以方便大家快速上手. 发布内容 注意,本次发布,只包含 Rafy ...
随机推荐
- SpringCloud升级之路2020.0.x版-44.避免链路信息丢失做的设计(1)
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 我们在这一节首先分析下 Spring Cloud Gateway 一些其他可能丢失链路信息 ...
- ABC 210
A 按题意模拟. scanf("%lld%lld%lld%lld",&n,&a,&x,&y); std::cout<<n * x - ( ...
- 一类巧妙利用利用失配树的序列DP
I.导入 求长度为\(\text{len}\)的包含给定连续子串\(\text{T}\)的 0/1 串的个数.(\(|T|<=15\)) 通常来说这种题目应该立刻联想到状压 DP 与取反集--这 ...
- C语言 自定义函数按行读入文件2
再改进下上次的读入一行函数,利用zlib库的gzgtec函数读取文件,动态分配内存,最后没有多出空行. 1 #include <stdio.h> 2 #include <stdlib ...
- 大规模 K8s 集群管理经验分享 · 上篇
11 月 23 日,Erda 与 OSCHINA 社区联手发起了[高手问答第 271 期 -- 聊聊大规模 K8s 集群管理],目前问答活动已持续一周,由 Erda SRE 团队负责人骆冰利为大家解答 ...
- oracle 拆分字符串
WITH t AS (SELECT '1-2-3-4' a FROM dual)SELECT Regexp_Substr(a, '[^-]+', 1, LEVEL) i FROM tCONNECT B ...
- Android给页面添加横线和竖线
竖线 <View android:layout_width="1dip" android:layout_height="match_parent& ...
- spring注解-自动装配
Spring利用依赖注入(DI)完成对IOC容器中中各个组件的依赖关系赋值 一.@Autowired 默认优先按照类型去容器中找对应的组件(applicationContext.getBean(Boo ...
- spring boot druid数据源
pom.xml配置 <!-- druid --> <dependency> <groupId>com.alibaba</groupId> <art ...
- entfrm开源免费模块化无代码开发平台,开放生态为您创造更多的价值
entfrm开发平台6大特性,赋能快速开发,为您创造更多的价值: 1. 模块化 丰富的模块稳定的框架 后台极易上手 目前已包括系统管理.任务调度.运维监控.开发工具.消息系统.工作流引擎.内容管理等模 ...