2

我将 Marten 用作事件存储,特别是用于获取事件流。

type AccountCreation = {
    Owner: string
    AccountId: Guid
    CreatedAt: DateTimeOffset
    StartingBalance: decimal
}

type AccountEvents =
    | AccountCreated of AccountCreation
    | AccountCredited of Transaction
    | AccountDebited of Transaction

let settings = {
    Host = "localhost"
    DatabaseName = "postgres"
    UserName = "root"
    Password = "root"
    EventTypes = eventTypes
}
use store = createDocumentStore settings
use session = store.LightweightSession()

let khalidId = Guid.NewGuid()
let billId = Guid.NewGuid()

let khalid = AccountEvents.AccountCreated({
    Owner = "Khalid Abuhakmeh"
    AccountId = khalidId
    StartingBalance = 1000m
    CreatedAt = DateTimeOffset.UtcNow
})

let bill = {
    Owner = "Bill Boga"
    AccountId = billId
    StartingBalance = 0m
    CreatedAt = DateTimeOffset.UtcNow
}

session.Events.Append(khalidId, khalid) |> ignore
session.Events.Append(billId, bill) |> ignore

session.SaveChanges()

let stream = session.Events.FetchStream()

stream被定义为IReadOnlyList<IEvent>IEvent

public interface IEvent
{
    Guid Id { get; set; }
    int Version { get; set; }
    long Sequence { get; set; }
    object Data { get; }
    Guid StreamId { get; set; }
    string StreamKey { get; set; }
    DateTimeOffset Timestamp { get; set; }
    string TenantId { get; set; }
    void Apply<TAggregate>(TAggregate state, IAggregator<TAggregate> aggregator) where TAggregate : class, new();
}

如果属性的基础类型是(如果不是,则项目不会在结果序列中产生) ,我想将每个转换IEvent为。AccountEventsDataAccountEvents

在 C# 中,我会简单地使用关键字as来实现这一点,但在 F# 中,我不确定最快的 F#-ish 方式(就性能而言)是什么。

我最终得到了以下代码:

let seqCastOption<'T> sequence =
    sequence
    |> Seq.map(fun x ->
        match box x with
        | :? 'T as value -> Some value
        | _ -> None)

let fetchStream<'T> (session: IDocumentSession) (id: Guid) =
    let stream = session.Events.FetchStream(id)
    stream
    |> Seq.map(fun x -> x.Data)
    |> seqCastOption<'T>
    |> Seq.filter (fun x -> x.IsSome)
    |> Seq.map(fun x -> x.Value)

但这似乎相当“昂贵”,我想知道转换.DataOption<AccountEvents>+ 过滤器的步骤是否IsSome可以一次完成。

4

2 回答 2

6

rmunn 的答案中提到的Seq.choose函数对于了解这种情况非常有用,但是对于这种确切的情况,我建议使用内置的 .NET 方法Enumerable.OfType<'T>,它完全符合您的要求,并且可能非常优化:

open System.Linq

let fetchStream<'T> (session: IDocumentSession) (id: Guid) =
    let stream = session.Events.FetchStream(id)
    stream
    |> Seq.map(fun x -> x.Data)
    |> Enumerable.OfType<'T>
于 2019-06-06T10:48:24.047 回答
3

Seq.choose是您一直在寻找的功能。你给它一个接受 a'A并返回 a的函数,'B option它产生的'B值是Some。对于您的使用场景,它看起来像这样:

let castOption<'T> x =
    match box x with
    | :? 'T as value -> Some value
    | _ -> None

let fetchStream<'T> (session: IDocumentSession) (id: Guid) =
    let stream = session.Events.FetchStream(id)
    stream
    |> Seq.map(fun x -> x.Data)
    |> Seq.choose castOption<'T>
于 2019-06-06T10:23:05.353 回答