You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

请推荐基于.NET Framework(非.NET Core)的ASP.NET WebApi现代应用架构及配套技术方案

针对你的需求,我整理了一套基于.NET Framework的ASP.NET WebAPI现代架构方案,涵盖架构设计、ORM选型、数据库迁移、AD认证以及代码示例,同时解决你提到的EF Migrations数据丢失问题:

推荐方案:基于.NET Framework的ASP.NET WebAPI现代架构(Oracle + AD认证)

一、核心架构设计(分层+关键模式)

这套架构兼顾了业务解耦、可测试性和可维护性,完全适配你的技术选型需求:

  • 分层架构:
    • Controller:仅处理HTTP请求参数校验、响应封装,依赖Manager层完成业务逻辑,不直接操作数据库
    • Manager:业务逻辑核心层,协调多个Repository操作,通过Unit of Work管控事务
    • Repository:数据访问层,封装Oracle数据库CRUD操作,对外暴露业务友好的数据接口
  • Unit of Work模式:统一管理EF上下文的事务,确保多个Repository的操作要么全部提交,要么全部回滚,避免数据不一致
  • 辅助工具集成:
    • Automapper:简化DTO与实体模型之间的映射,消除重复的对象转换代码
    • Serilog:实现结构化日志输出,支持写入文件、Oracle数据库等,便于线上问题排查
    • xUnit:搭配Moq框架对Manager、Repository层做单元/集成测试,保障代码质量

二、ORM选型与数据丢失问题解决

1. 优先选型:Entity Framework 6(EF6)+ Oracle Provider

针对.NET Framework,EF6对Oracle的支持最为成熟,推荐使用Oracle Data Provider for .NET (ODP.NET) 或第三方的Devart dotConnect for Oracle(稳定性更强)。

2. 优化EF Migrations流程,避免数据丢失

你之前的流程确实存在数据丢失风险——EF自动生成的迁移脚本可能会误删字段、清空表数据。我建议调整为手动控制迁移脚本的安全流程:

优化后流程:

  1. Code First模型设计:用Fluent API定义实体与Oracle表的映射(比DataAnnotation更灵活)
  2. 生成空白迁移类:执行Add-Migration MigrationName -Empty生成空迁移,手动编写SQL变更脚本(比如新增字段、修改约束时,先备份数据再执行变更)
  3. 本地验证:在开发库执行手动编写的迁移脚本,确认数据安全、结构符合预期
  4. 集成Liquibase:将验证后的脚本导入Liquibase做版本管理,用于CI/CD部署
  5. 回滚准备:为每个迁移脚本编写对应的回滚脚本,Liquibase可自动管理版本回滚

3. 备选ORM:Dapper

如果对性能要求极高,或需要灵活控制复杂Oracle查询,Dapper是轻量型首选。它可以和Unit of Work模式结合,实现高效的数据访问。

三、Oracle数据库迁移体系(支持异步)

1. 主方案:EF6手动迁移脚本 + Liquibase

  • Liquibase优势:原生支持Oracle,版本化管理迁移脚本,支持异步执行(通过CLI异步命令或.NET API调用),自带回滚机制
  • 集成流程:
    • 每个EF手动迁移对应一个Liquibase变更集,将SQL脚本写入changelog.xml
    • CI/CD管道中,用Liquibase CLI异步执行变更集,避免阻塞部署流程
    • 用Liquibase的标签功能标记数据库版本,快速定位回滚节点

2. 备选方案:Oracle SQL Developer + 数据库项目

如果你更习惯传统脚本管理,可使用Oracle SQL Developer的版本控制功能,配合Visual Studio的Oracle数据库项目插件(第三方)对比生成变更脚本,再通过SQL*Plus异步命令完成部署。

四、AD/Windows认证实现

1. Web.config配置

<system.web>
  <authentication mode="Windows" />
  <authorization>
    <deny users="?" /> <!-- 拒绝匿名访问 -->
  </authorization>
</system.web>
<system.webServer>
  <security>
    <authentication>
      <windowsAuthentication enabled="true" />
      <anonymousAuthentication enabled="false" />
    </authentication>
  </security>
</system.webServer>

2. Controller中获取AD用户信息

[Authorize]
public class UserController : ApiController
{
    public IHttpActionResult GetCurrentUser()
    {
        var adUser = User.Identity.Name; // 格式:DOMAIN\Username
        return Ok(new { AdUserName = adUser });
    }
}

五、核心代码示例

1. Unit of Work + Repository(EF6)

// IUnitOfWork接口
public interface IUnitOfWork : IDisposable
{
    IEmployeeRepository Employees { get; }
    Task<int> SaveChangesAsync();
}

// UnitOfWork实现
public class UnitOfWork : IUnitOfWork
{
    private readonly OracleDbContext _context;
    private IEmployeeRepository _employeeRepo;

    public UnitOfWork(OracleDbContext context) => _context = context;

    public IEmployeeRepository Employees => _employeeRepo ??= new EmployeeRepository(_context);

    public async Task<int> SaveChangesAsync() => await _context.SaveChangesAsync();

    public void Dispose() => _context.Dispose();
}

// Repository接口与实现
public interface IEmployeeRepository
{
    Task<Employee> GetByIdAsync(int id);
    Task AddAsync(Employee employee);
}

public class EmployeeRepository : IEmployeeRepository
{
    private readonly OracleDbContext _context;

    public EmployeeRepository(OracleDbContext context) => _context = context;

    public async Task<Employee> GetByIdAsync(int id) => await _context.Employees.FindAsync(id);

    public async Task AddAsync(Employee employee) => await _context.Employees.AddAsync(employee);
}

// Oracle DbContext配置
public class OracleDbContext : DbContext
{
    public OracleDbContext() : base("OracleConnectionString")
    {
        Configuration.LazyLoadingEnabled = false;
        Configuration.ProxyCreationEnabled = false;
    }

    public DbSet<Employee> Employees { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // Fluent API映射Oracle表
        modelBuilder.Entity<Employee>()
            .ToTable("EMPLOYEES")
            .HasKey(e => e.Id);
        modelBuilder.Entity<Employee>()
            .Property(e => e.Id).HasColumnName("EMP_ID");
        modelBuilder.Entity<Employee>()
            .Property(e => e.Name).HasColumnName("EMP_NAME").IsRequired().HasMaxLength(100);
    }
}

2. Manager层示例(含Automapper)

public interface IEmployeeManager
{
    Task<EmployeeDto> GetEmployeeByIdAsync(int id);
    Task<int> CreateEmployeeAsync(EmployeeDto dto);
}

public class EmployeeManager : IEmployeeManager
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly IMapper _mapper;

    public EmployeeManager(IUnitOfWork unitOfWork, IMapper mapper)
    {
        _unitOfWork = unitOfWork;
        _mapper = mapper;
    }

    public async Task<EmployeeDto> GetEmployeeByIdAsync(int id)
    {
        var employee = await _unitOfWork.Employees.GetByIdAsync(id);
        if (employee == null)
            throw new KeyNotFoundException($"Employee {id} not found");
        return _mapper.Map<EmployeeDto>(employee);
    }

    public async Task<int> CreateEmployeeAsync(EmployeeDto dto)
    {
        var employee = _mapper.Map<Employee>(dto);
        await _unitOfWork.Employees.AddAsync(employee);
        await _unitOfWork.SaveChangesAsync();
        return employee.Id;
    }
}

// Automapper配置
public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<Employee, EmployeeDto>();
        CreateMap<EmployeeDto, Employee>();
    }
}

3. Serilog初始化(Global.asax)

protected void Application_Start()
{
    // 配置Serilog输出到文件和Oracle数据库
    Log.Logger = new LoggerConfiguration()
        .WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day)
        .WriteTo.Oracle(cfg => cfg.ConnectionString("OracleConnectionString")
            .TableName("WEBAPI_LOGS")
            .ColumnOptions(o =>
            {
                o.AddStandardColumns(StandardColumn.LogEvent);
                o.AddColumn("UserName", col => col.PropertyValue(x => x.Properties["UserName"]));
            }))
        .CreateLogger();

    GlobalConfiguration.Configure(WebApiConfig.Register);
    // 初始化Automapper
    AutoMapper.Mapper.Initialize(cfg => cfg.AddProfile<MappingProfile>());
}

六、关键注意事项

  • Oracle连接字符串:使用ODP.NET格式,例如:Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=oracle-server)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=ORCL)));User Id=api_user;Password=xxx;
  • 异步优先:所有数据访问、业务逻辑方法都使用async/await,提升WebAPI并发能力
  • 数据备份:迁移前通过Liquibase预执行脚本备份涉及变更的表,避免数据丢失
  • 单元测试:用xUnit+Moq模拟Repository和Unit of Work,对Manager层做无依赖测试

内容的提问来源于stack exchange,提问作者Boris Gappov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 13:22:40