使用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);
核心思路
- 拆分流程分支:把Closed类型需要执行的
GetOtherData+ApproveRefund封装成一个独立的LINQ查询流,Open类型则直接返回一个成功的Either<States, Unit>流,无需执行额外步骤。 - 统一流程入口:通过
switch表达式根据交易类型选择对应的流,将分支逻辑集中在一处,避免分散到各个方法中。 - 保持链式调用:所有分支都返回相同类型的
IAsyncEnumerable<Either<States, Unit>>,因此可以无缝接入原有的LINQ查询链中,符合语法要求。
这种方式既保持了代码的可读性和维护性,又严格遵循了LINQ查询表达式的语法规则,同时满足了不同交易类型的流程差异需求。
内容的提问来源于stack exchange,提问作者Avrohom Yisroel
相关产品推荐
相关产品推荐

