I am using Marten as an Event Store, in particular to fetch a stream of events.
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
being IReadOnlyList<IEvent>
and IEvent
defined as:
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();
}
I would like to convert each IEvent
to AccountEvents
, if the underlying type of the Data
property is AccountEvents
(if not the item is not yielded in the resulting sequence).
In C# I would simply use the keyword as
to achieve that, but in F# I am not sure what is the fastest F#-ish way(in terms of performance) to get that.
I ended up on the following code:
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)
But this seems quite "expensive", and I an wondering whether the step of converting .Data
to the Option<AccountEvents>
+ filter the ones that IsSome
can be done all at once.
The Seq.choose
function mentioned in rmunn's answer is very useful to know for this kind of situation, but for this exact situation I would recommend using the built in .NET method Enumerable.OfType<'T>
, which does exactly what you want and is probably quite optimised:
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>