Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"rollForward": false
},
"fsdocs-tool": {
"version": "20.0.0",
"version": "22.2.0",
"commands": [
"fsdocs"
],
Expand Down
3 changes: 3 additions & 0 deletions docs/RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
### 1.5.29 - 02.11.2026
- Fix: parameters of a sub-query nested in another sub-query are bound (IN, EXISTS, UNION and the like)
- Fix: groupBy keys with option or value-option columns, and with x.IsSome / x.IsNone in a tuple key
- Fix: a grouped sub-query of IN / NOT IN that selects an aggregate selects it without the group keys, on both sides of a union too, #746
- IN / NOT IN sub-queries with a projection computed in .NET (e.g. `g.Max(...) + 1L`, a narrowing conversion, a user function) fail with an error instead of comparing against the uncomputed column
- Design time: a provider instance that goes away closes its connection instead of disposing it, as the cached types of other instances still use it ("Cannot access a disposed object")
- PostgreSQL: identity columns count as generated, so the typed ``Create(...)`` no longer takes them. Breaking for code that passed a value to a BY DEFAULT identity column: use Create() and set it, or Create(data). A saved ContextSchemaPath cache keeps the old signature until regenerated.

### 1.5.28 - 17.10.2026
Expand Down
1 change: 1 addition & 0 deletions docs/content/_template.html
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ <H1 style="font-size: 24px;">{{fsdocs-collection-name}}</H1>
</div>
</dialog>
<script type="module" src="{{root}}content/fsdocs-tips.js"></script>
<script type="module" src="{{root}}content/fsdocs-xmldoc-tips.js"></script>
<script type="module" src="{{root}}content/fsdocs-theme-toggle.js"></script>
<script type="module" src="{{root}}content/fsdocs-theme.js"></script>
<script type="module" src="{{root}}content/fsdocs-search.js"></script>
Expand Down
30 changes: 27 additions & 3 deletions docs/content/content/fsdocs-custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ blockquote {

#content pre.fssnip code {
font: 0.86rem 'Fira Code Fixed', monospace;
font-weight: 600;
font-weight: 500;
}

#content table.pre {
Expand Down Expand Up @@ -553,7 +553,7 @@ blockquote {
}
/* keywords */
#content span.k {
color: #1f4ee9;
color: #4878f0;
}
/* comment */
#content span.c {
Expand Down Expand Up @@ -597,7 +597,6 @@ div.fsdocs-tip {
border-radius: 4px;
font: 0.85rem 'Fira Code Fixed', monospace;
padding: 6px 8px 6px 8px;
display: none;
color: #d1d1d1;
pointer-events: none;
}
Expand All @@ -607,6 +606,31 @@ div.fsdocs-tip {
font: 0.85rem 'Fira Code Fixed', monospace;
}

/* XML doc in a tool tip, rendered by fsdocs-xmldoc-tips.js */
div.fsdocs-tip-doc {
margin-top: 6px;
padding-top: 6px;
border-top: 1px solid #5f7478;
white-space: normal;
}

div.fsdocs-tip-doc-summary {
color: #f0d98c;
}

div.fsdocs-tip-doc-remarks,
div.fsdocs-tip-doc-param {
margin-top: 4px;
}

span.fsdocs-tip-doc-name {
color: #9cdcfe;
}

div.fsdocs-tip-doc code {
color: #ffffff;
}

#content code {
color: #8c938c
}
69 changes: 69 additions & 0 deletions docs/content/content/fsdocs-xmldoc-tips.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// fsdocs puts a member's XML doc into its tooltip as raw text, tags included.
// This renders the tags: the summary in its own colour, one line per parameter, the return value, and
// <see cref="..."/> / <paramref name="..."/> as the name they refer to.

const docTag = /<\/?(summary|param|returns|remarks|exception|typeparam|see|paramref|typeparamref|para|c|code)\b/;

function refName(el) {
const name = el.getAttribute("cref") || el.getAttribute("name") || el.getAttribute("langword") || el.textContent;
return name.replace(/^[A-Z]:/, "");
}

function inline(node, target) {
for (const child of node.childNodes) {
if (child.nodeType === Node.TEXT_NODE) {
target.append(child.textContent.replace(/\s+/g, " "));
} else if (child.nodeType === Node.ELEMENT_NODE) {
const tag = child.localName;
if (tag === "see" || tag === "seealso" || tag === "paramref" || tag === "typeparamref" || tag === "c" || tag === "code") {
const code = document.createElement("code");
code.textContent = child.textContent.trim() || refName(child);
target.append(code);
} else {
inline(child, target);
}
}
}
}

function line(cls, label, el) {
const body = document.createDocumentFragment();
inline(el, body);
body.normalize();
if (body.firstChild?.nodeType === Node.TEXT_NODE) body.firstChild.textContent = body.firstChild.textContent.trimStart();
if (body.lastChild?.nodeType === Node.TEXT_NODE) body.lastChild.textContent = body.lastChild.textContent.trimEnd();
const div = document.createElement("div");
div.className = cls;
if (label) {
const name = document.createElement("span");
name.className = "fsdocs-tip-doc-name";
name.textContent = label;
div.append(name, " ");
}
div.append(body);
return div;
}

function render(text) {
const xml = new DOMParser().parseFromString(`<doc>${text}</doc>`, "application/xml");
if (xml.getElementsByTagName("parsererror").length > 0) return null;
const doc = document.createElement("div");
doc.className = "fsdocs-tip-doc";
for (const el of xml.documentElement.children) {
switch (el.localName) {
case "summary": doc.append(line("fsdocs-tip-doc-summary", null, el)); break;
case "remarks": doc.append(line("fsdocs-tip-doc-remarks", null, el)); break;
case "param":
case "typeparam": doc.append(line("fsdocs-tip-doc-param", el.getAttribute("name") + ":", el)); break;
case "returns": doc.append(line("fsdocs-tip-doc-param", "Returns:", el)); break;
case "exception": doc.append(line("fsdocs-tip-doc-param", "Throws " + refName(el) + ":", el)); break;
}
}
return doc.childNodes.length > 0 ? doc : null;
}

for (const em of document.querySelectorAll("div.fsdocs-tip > em")) {
if (!docTag.test(em.textContent)) continue;
const doc = render(em.textContent);
if (doc) em.replaceWith(doc);
}
14 changes: 7 additions & 7 deletions docs/content/core/async.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ open System.Threading.Tasks
open FSharp.Data.Sql

type MyWebServer() =
member __.``Execute Business Logics`` (id : Guid) : Task<_> =
member __.``Execute Business Logics`` (orderId : int64) : Task<_> =
task {
use transaction =
new System.Transactions.TransactionScope(
Expand All @@ -67,14 +67,14 @@ type MyWebServer() =
let context = TypeProviderConnection.GetDataContext cstr
let! fetched =
query {
for t2 in context.MyDataBase.MyTable2 do
join t1 in context.MyDataBase.MyTable1 on (t2.ForeignId = t1.Id)
where (t2.Id = id)
select t1
for order in context.Main.Orders do
join customer in context.Main.Customers on (order.CustomerId.Value = customer.CustomerId)
where (order.OrderId = orderId)
select order
} |> Seq.executeQueryAsync

fetched |> Seq.iter (fun entity ->
entity.SetColumn("Updated", DateTime.UtcNow |> box)
fetched |> Seq.iter (fun order ->
order.ShippedDate <- Some DateTime.UtcNow
)
do! context.SubmitUpdatesAsync()

Expand Down
36 changes: 19 additions & 17 deletions docs/content/core/composable.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type sql = SqlDataProvider<
(*** hide ***)
let ctx = sql.GetDataContext()
(*** hide ***)
let xs = [].AsQueryable()
let xs = ([] : int list).AsQueryable()

(**
# Composable Queries
Expand Down Expand Up @@ -82,10 +82,10 @@ In other words, SQLProvider queries typically follow this pattern:

*)

let (qry:IQueryable<'T>) =
let pattern (xs:IQueryable<'T>) : IQueryable<'T> =
query {
//for is like C# foreach
for x in (xs:IQueryable<'T>) do
for x in xs do
select x
}

Expand Down Expand Up @@ -125,7 +125,7 @@ let companyNameFilter inUse =

Then, you can create the main query
*)
let query1 =
let mainQuery =
query {
for customers in ctx.Main.Customers do
where (customers.ContactTitle = "USA")
Expand All @@ -136,7 +136,7 @@ let query1 =
and now call you are able to call the second query like this
*)

let res = companyNameFilter true query1 |> Seq.toArray
let res = companyNameFilter true mainQuery |> Seq.toArray

(**
### Generate composable queries by using FSharp.Linq.ComposableQuery
Expand All @@ -150,18 +150,18 @@ Example for using FSharpComposableQuery
*)

let qry1 =
query { for u in dbContext.Users do
select (u.Id, u.Name, u.Email)
query { for c in ctx.Main.Customers do
select (c.CustomerId, c.CompanyName, c.City)
}

let qry2 =
query { for c in dbContext.Cars do
select (c.UserId, c.Brand, c.Year)
query { for o in ctx.Main.Orders do
select (o.CustomerId, o.OrderId, o.Freight)
}

query { for (i,n,e) in qry1 do
join (u,b,y) in qry2 on (i = u)
where (y > 2015)
where (y > 100m)
select (i,n,e,u,b,y)
} |> Seq.toArray

Expand All @@ -180,29 +180,31 @@ open System.Linq

let nestedQueryTest =
let qry1 = query {
for emp in ctx.Hr.Employees do
for emp in ctx.Main.Employees do
where (emp.FirstName.StartsWith("S"))
select emp.FirstName
}
query {
for emp in ctx.Hr.Employees do
for emp in ctx.Main.Employees do
where (qry1.Contains(emp.FirstName))
select (emp.FirstName, emp.LastName)
} |> Seq.toArray

(**

### Using non-strongly-typed __.GetColumn "name"
### Using non-strongly-typed GetColumn "name"

All the entities inherit from SqlEntity which has GetColumn-method.
So you can use non-strongly-typed columns like this:
All the entities are SqlEntity objects, which implement `IColumnHolder` with a GetColumn method.
So you can use non-strongly-typed columns, by the database's column name, like this:

*)
open FSharp.Data.Sql.Common

let qry =
query {
for x in query1 do
where ((x.GetColumn<string> "CustomerId") = "ALFKI")
select (x.GetColumn<string> "CustomerId")
where (((x :> IColumnHolder).GetColumn<string> "CustomerID") = "ALFKI")
select ((x :> IColumnHolder).GetColumn<string> "CustomerID")
} |> Seq.head

(**
Expand Down
28 changes: 13 additions & 15 deletions docs/content/core/crud.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,15 @@ ctx.SubmitUpdates()
*)

type Employee2 = {
Id:int
Id:int64
FirstName:string
LastName:string
}

let updateEmployee (employee: Employee2) =
let foundEmployeeMaybe = query {
for p in ctx.Public.Employee2 do
where (p.Id = employee.Id)
for p in ctx.Main.Employees do
where (p.EmployeeId = employee.Id)
select (Some p)
exactlyOneOrDefault
}
Expand All @@ -164,8 +164,8 @@ let updateEmployee (employee: Employee2) =

let updateEmployee' (employee: Employee2) =
query {
for p in ctx.Public.Employee2 do
where (p.Id = employee.Id)
for p in ctx.Main.Employees do
where (p.EmployeeId = employee.Id)
}
|> Seq.iter( fun e ->
e.FirstName <- employee.FirstName
Expand All @@ -174,7 +174,7 @@ let updateEmployee' (employee: Employee2) =
ctx.SubmitUpdates()

let john = {
Id = 1
Id = 1L
FirstName = "John"
LastName = "Doe" }

Expand All @@ -188,7 +188,7 @@ output of .ColumnValues:

employees
|> Seq.map (fun x ->
employee.Create(x.ColumnValues)) // create twins
ctx.Main.Employees.Create(x.ColumnValues)) // create twins
|> Seq.toList

/// Retrieve the FSharp.Data.Sql.Common.SqlEntity objects
Expand Down Expand Up @@ -284,14 +284,12 @@ You can leverage this feature by setting the `OnConflict` property on a row obje
Sql Server has a similar feature in the form of the MERGE statement. This is not yet supported.
*)

let ctx = sql.GetDataContext()

let emp = ctx.Main.Employees.Create()
emp.Id <- 1
emp.FirstName <- "Jane"
emp.LastName <- "Doe"
let jane = ctx.Main.Employees.Create()
jane.EmployeeId <- 1L
jane.FirstName <- "Jane"
jane.LastName <- "Doe"

emp.OnConflict <- FSharp.Data.Sql.Common.OnConflict.Update
jane.OnConflict <- FSharp.Data.Sql.Common.OnConflict.Update

ctx.SubmitUpdates()

Expand Down Expand Up @@ -321,7 +319,7 @@ There are 3 overrides of create.

*)

let emp = ctx.Main.Employees.``Create(FirstName, LastName)``("Don", "Syme")
let don = ctx.Main.Employees.``Create(FirstName, LastName)``("Don", "Syme")

(**

Expand Down
1 change: 0 additions & 1 deletion docs/content/core/general.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ let [<Literal>] resolutionPath = __SOURCE_DIRECTORY__ + @"/../../files/sqlite"
(*** hide ***)
let [<Literal>] connectionString = "Data Source=" + __SOURCE_DIRECTORY__ + @"\..\northwindEF.db;Version=3;Read Only=false;FailIfMissing=True;"

(*** hide ***)
(**

# SQL Provider Basics
Expand Down
3 changes: 2 additions & 1 deletion docs/content/core/mappers.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ If you want to use SQLProvider as code-generator and copy and paste the tables a

*)

sql.dataContext.DesignTimeCommands.TemplateAsRecord.``main.OrdersTemplate``
// The template is a nested type: type its path and a dot in the editor
// sql.dataContext.DesignTimeCommands.TemplateAsRecord.``main.OrdersTemplate``.

// intellisense will generate you code that you can copy and paste as template to create your own type:
// ``type MainOrders = { CustomerId : String voption; EmployeeId : Int64 voption; Freight : Decimal voption; OrderDate : DateTime voption; OrderId : Int64; RequiredDate : DateTime voption; ShipAddress : String voption; ShipCity : String voption; ShipCountry : String voption; ShipName : String voption; ShipPostalCode : String voption; ShipRegion : String voption; ShippedDate : DateTime voption }``
Expand Down
3 changes: 0 additions & 3 deletions docs/content/core/mssql.fsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
(*** hide ***)

(*** hide ***)
#I @"../../../bin/mssql/lib/net8.0/"
#I @"../../../bin/mssql/lib/net8.0/"
#r "FSharp.Data.SqlProvider.Common.dll"
#r "FSharp.Data.SqlProvider.MsSql.dll"
Expand Down
Loading
Loading