Querying facts gets a bit trickier. You start by writing a specification in LINQ.
Every specification has to start with one or two known facts. For example, suppose you wanted to get all sites created by a specific user. You could write that using query syntax.
var sitesByUser = Given<User>.Match((user, facts) =>
from site in facts.OfType<Site>()
where site.creator == user
select site
);
You could also write that using method syntax.
var sitesByUser = Given<User>.Match((user, facts) =>
facts.OfType<Site>().Where(site => site.creator == user)
);
Or even use shorthand.
var sitesByUser = Given<User>.Match((user, facts) =>
facts.OfType<Site>(site => site.creator == user)
);
No matter how you write it, this specification matches all facts of type Site
related to the given user.
Now suppose that you wanted to filter out deleted sites.
If someone had created a SiteDeleted
fact, then we want to exclude it from the results.
We can do this by checking that there are not any related facts:
var sitesByUser = Given<User>.Match((user, facts) =>
from site in facts.OfType<Site>()
where site.creator == user &&
!facts.Any<SiteDeleted>(deleted => deleted.site == site)
select site
);
You can't actually delete a fact. This is how you simulate deletion.
Sometimes users delete things accidentally. It happens. To let them restore a deleted fact, nest another "not any" clause.
var sitesByUser = Given<User>.Match((user, facts) =>
from site in facts.OfType<Site>()
where site.creator == user &&
!facts.Any<SiteDeleted>(deleted => deleted.site == site &&
!facts.Any<SiteRestored>(restored => restored.deleted == deleted)
)
select site
);
What if you want to delete it again? Do you need to nest it one level deeper?
No.
Just create a new SiteDeleted
.
Here's the model that makes this work:
[FactType("Blog.Site")]
public record Site(User creator, DateTime createdAt) { }
[FactType("Blog.Site.Deleted")]
public record SiteDeleted(Site site, DateTime deletedAt) { }
[FactType("Blog.Site.Restored")]
public record SiteRestored(SiteDeleted deleted) { }
You can extend a query to select more facts.
The way you do that depends on which syntax you are using.
If you favor the query syntax, then add another from
clause.
var postsInAllSites = Given<User>.Match((user, facts) =>
from site in facts.OfType<Site>()
where site.creator == user &&
!facts.Any<SiteDeleted>(deleted => deleted.site == site &&
!facts.Any<SiteRestored>(restored => restored.deleted == deleted)
)
from post in facts.OfType<Post>()
where post.site == site &&
!facts.Any<PostDeleted>(deleted => deleted.post == post &&
!facts.Any<PostRestored>(restored => restored.deleted == deleted)
)
select post
If instead you prefer the method syntax, then use SelectMany
:
var postsInAllSites = Given<User>.Match((user, facts) =>
facts.OfType<Site>()
.Where(site => site.creator == user &&
!facts.Any<SiteDeleted>(deleted => deleted.site == site &&
!facts.Any<SiteRestored>(restored => restored.deleted == deleted)))
.SelectMany(site => facts.OfType<Post>()
.Where(post => post.site == site &&
!facts.Any<PostDeleted>(deleted => deleted.post == post &&
!facts.Any<PostRestored>(restored => restored.deleted == deleted)))));
You can select more detailed information than the facts themselves.
Do this with the select
keyword or Select
method.
You will typically select an anonymous object containing fields of the fact.
You can use the hash of the fact as an ID in that object.
var sitesByUser = Given<User>.Match((user, facts) =>
from site in facts.OfType<Site>()
where site.creator == user &&
!facts.Any<SiteDeleted>(deleted => deleted.site == site &&
!facts.Any<SiteRestored>(restored => restored.deleted == deleted)
)
select new
{
id = jinagaClient.Hash(site),
createdAt = site.createdAt
}
);
If you are projecting results into a view model, you will want sub-selects to be observable.
That will let you set up a call back when a child projection changes.
Call facts.Observable
with a sub query to get back an IObservableCollection
.
var sitesByUser = Given<User>.Match((user, facts) =>
from site in facts.OfType<Site>()
where site.creator == user &&
!facts.Any<SiteDeleted>(deleted => deleted.site == site &&
!facts.Any<SiteRestored>(restored => restored.deleted == deleted)
)
select new
{
site,
names = facts.Observable(
from name in facts.OfType<SiteName>()
where name.site == site &&
!facts.Any<SiteName>(next => next.prior.Contains(name))
select name.value
),
domains = facts.Observable(
from domain in facts.OfType<SiteDomain>()
where domain.site == site &&
!facts.Any<SiteDomain>(next => next.prior.Contains(domain))
select domain.value
)
}
);