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

使用LanguageExt查询语法实现退款流程的条件分支逻辑

问题描述

我有一个接收交易ID并发起退款的方法,该方法会先从数据库查询交易记录,再调用外部服务申请退款。若退款获批,则更新本地数据库中的交易记录并返回成功状态;若出现异常或退款被拒绝,则返回对应状态。

忽略Trans.Type属性时的可运行代码如下:

static async Task<States> Refund(int id) =>
  await (
    from transaction in GetTransaction(id).ToAsync()
    from _1 in GetOtherData().ToAsync()
    from result in ApproveRefund(transaction).ToAsync()
    from _2 in Save(transaction).ToAsync()
    select States.Success
  )
  .Match(s => s, s => s);

// Get the transaction from the database
static async Task<Either<States, Trans>> GetTransaction(int id) =>
  id switch {
    0 => new Trans(id, TransType.Open, 1.0M),
    1 => new Trans(id, TransType.Closed, 1.0M),
    2 => new Trans(id, TransType.Open, 1.5M),
    3 => new Trans(id, TransType.Closed, 1.5M),
    _ => States.TransactionNotFound
  };

// We need some extra data before calling the external service.
// There are actually about four methods that are called in turn,
// but for simplicity, I've only included one here
static async Task<Either<States, Unit>> GetOtherData() =>
  unit;

// For simplicity, approve any transaction less that £1.50
// In reality, this would seek approval from an external service
static async Task<Either<States, States>> ApproveRefund(Trans transaction) =>
  transaction.Amount < 1.5M
    ? Right<States, States>(States.Success)
    : Left<States, States>(States.RefundRefused);

// Save the changes. For this sample we assume this will always succeed
static async Task<Either<States, Unit>> Save(Trans transaction) {
  transaction.Refunded = DateTime.Now;
  return States.Success;
}

class Trans {
  public Trans(int id, TransType type, decimal amount, DateTime? refunded = null) {
    Id = id;
    Type = type;
    Amount = amount;
    Refunded = refunded;
  }

  public int Id { get; set; }
  public TransType Type { get; set; }
  public decimal Amount { get; set; }
  public DateTime? Refunded { get; set; }
}

enum TransType {
  Open,
  Closed
}

enum States {
  Success,
  TransactionNotFound,
  RefundRefused,
  // More specific error states would go here
}

现在需要处理两种交易类型:

  • Closed类型:退款流程保持不变,需调用GetOtherData、ApproveRefund后再保存
  • Open类型:无需调用外部服务,仅需设置Refunded属性并保存

尝试添加条件分支后代码无法编译:

static async Task<States> Refund(int id) =>
  await (
    from transaction in GetTransaction(id).ToAsync()
    if (transaction.Type == TransType.Closed) {
      from _1 in GetOtherData().ToAsync()
      from result in ApproveRefund(transaction).ToAsync()
    }
    from _2 in Save(transaction).ToAsync()
    select States.Success
  )
  .Match(s => s, s => s);

实际场景中GetOtherData等前置方法有多个,不想在每个方法中添加类型判断,请问如何实现需求?

解决方案

你可以通过将不同类型的流程封装为独立的Either流,再根据交易类型选择对应的流执行,最后统一执行保存步骤。这样既避免了在每个前置方法中加判断,也符合LINQ查询表达式的语法规则。

修改后的Refund方法如下:

static async Task<States> Refund(int id) =>
  await (
    from transaction in GetTransaction(id).ToAsync()
    // 根据交易类型选择对应的流程
    from _ in transaction.Type switch
    {
        TransType.Closed => 
            from _1 in GetOtherData().ToAsync()
            from result in ApproveRefund(transaction).ToAsync()
            select Unit.Default,
        TransType.Open => 
            // Open类型直接返回成功信号,跳过外部服务流程
            Task.FromResult(Right<States, Unit>(Unit.Default)).ToAsync(),
        _ => Task.FromResult(Left<States, Unit>(States.TransactionNotFound)).ToAsync()
    }
    from _2 in Save(transaction).ToAsync()
    select States.Success
  )
  .Match(s => s, s => s);

核心思路

  1. 拆分流程分支:把Closed类型需要执行的GetOtherData+ApproveRefund封装成一个独立的LINQ查询流,Open类型则直接返回一个成功的Either<States, Unit>流,无需执行额外步骤。
  2. 统一流程入口:通过switch表达式根据交易类型选择对应的流,将分支逻辑集中在一处,避免分散到各个方法中。
  3. 保持链式调用:所有分支都返回相同类型的IAsyncEnumerable<Either<States, Unit>>,因此可以无缝接入原有的LINQ查询链中,符合语法要求。

这种方式既保持了代码的可读性和维护性,又严格遵循了LINQ查询表达式的语法规则,同时满足了不同交易类型的流程差异需求。


内容的提问来源于stack exchange,提问作者Avrohom Yisroel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 08:57:22