{"id":33191299,"url":"https://github.com/cmeeren/Fling","last_synced_at":"2025-11-21T00:02:01.973Z","repository":{"id":65422077,"uuid":"347914928","full_name":"cmeeren/Fling","owner":"cmeeren","description":"Fling significantly reduces boilerplate needed to efficiently save/load complex domain entities to/from multiple tables.","archived":false,"fork":false,"pushed_at":"2025-05-20T12:41:50.000Z","size":295,"stargazers_count":46,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-09-26T15:10:29.074Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"F#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cmeeren.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-03-15T09:51:50.000Z","updated_at":"2025-08-17T16:58:31.000Z","dependencies_parsed_at":"2024-06-20T05:48:14.718Z","dependency_job_id":"4e64908d-d25b-4c81-a6b4-5623835162c0","html_url":"https://github.com/cmeeren/Fling","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/cmeeren/Fling","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmeeren%2FFling","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmeeren%2FFling/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmeeren%2FFling/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmeeren%2FFling/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cmeeren","download_url":"https://codeload.github.com/cmeeren/Fling/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmeeren%2FFling/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":285532343,"owners_count":27187706,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-20T02:00:05.334Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-11-16T06:00:41.329Z","updated_at":"2025-11-21T00:02:01.967Z","avatar_url":"https://github.com/cmeeren.png","language":"F#","funding_links":[],"categories":["General Purpose Libraries"],"sub_categories":["Performance Analysis"],"readme":"Fling\n================\n\n**Fling significantly reduces boilerplate needed to efficiently save/load complex domain entities to/from multiple\ntables.**\n\nFling works with your existing (simple, per-table) get/insert/update/delete data access code, and enhances it with\nminimal boilerplate:\n\n* When loading, Fling fetches child entities and supports batch loading child entities for multiple parent entities.\n* When saving, Fling only inserts/updates/deletes changed rows.\n\nFling is completely database agnostic.\n\nIf you use SQL Server, Fling synergizes very well with [Facil](https://github.com/cmeeren/Facil), which can fully\ngenerate the data access code that Fling can use. However, Fling is just as useful without it.\n\nWhat does it look like?\n-----------------------\n\nGiven data access code, DTO types and functions to convert between domain and DTO types, Fling allows you to write these\nthree helpers for efficiently saving/loading complex domain entities as described above:\n\n```f#\nopen Fling.Fling\n\n// Saves an order. 'Order option' is the old order (None for initial insert)\nlet save: 'arg -\u003e Order option -\u003e Order -\u003e Async\u003c'saveResult option\u003e =\n    saveRoot orderToDbDto insertOrder updateOrder\n    |\u003e saveChildren\n        orderToLineDtos\n        _.OrderLineId\n        Db.insertOrderLine\n        Db.updateOrderLine\n        Db.deleteOrderLine\n    |\u003e saveOptChild orderToCouponDto _.OrderId Db.insertCoupon Db.updateCoupon Db.deleteCoupon\n    |\u003e saveChild Db.orderToPriceDataDto Db.insertPriceData Db.updatePriceData\n\n// Saves a sequence of orders in a single batch (at most one call to each insert/update/delete function).\n// 'Order option' is the old order (None for initial insert).\nlet saveBatch: 'arg -\u003e #seq\u003cOrder option * Order\u003e -\u003e Async\u003cunit option\u003e =\n    Batch.saveRoot orderToDbDto insertOrders updateOrders\n    |\u003e Batch.saveChildren\n        orderToLineDtos\n        _.OrderLineId\n        Db.insertOrderLines\n        Db.updateOrderLines\n        Db.deleteOrderLines\n    |\u003e Batch.saveOptChild orderToCouponDto _.OrderId Db.insertCoupons Db.updateCoupons Db.deleteCoupons\n    |\u003e Batch.saveChild Db.orderToPriceDataDto Db.insertPriceDatas Db.updatePriceDatas\n\n// Loads a single order.\nlet load: 'arg -\u003e ('arg -\u003e Async\u003cOrderDto option\u003e) -\u003e Async\u003cOrder option\u003e =\n    createLoader Dto.orderToDomain _.OrderId\n    |\u003e loadChild Db.getOrderLinesForOrder\n    |\u003e loadChild Db.getCouponForOrder\n    |\u003e loadChild Db.getPriceDataForOrder\n    |\u003e loadSerial\n\n// Loads a batch of orders (at most one fetch per Db function).\nlet loadBatch: 'arg -\u003e ('arg -\u003e Async\u003cOrderDto list\u003e) -\u003e Async\u003cOrder list\u003e =\n    createBatchLoader Dto.orderToDomain _.OrderId\n    |\u003e batchLoadChildren Db.getOrderLinesForOrders _.OrderId\n    |\u003e batchLoadOptChild Db.getCouponForOrders _.OrderId\n    |\u003e batchLoadChild Db.getPriceDataForOrders _.OrderId\n    |\u003e loadBatchSerial\n```\n\nQuick start\n-----------\n\n### 1. Install\n\nInstall Fling from [NuGet](https://www.nuget.org/packages/Fling).\n\n### 2. Write your domain logic as usual\n\nBelow, `Order` is a complex domain object (“aggregate root” in DDD terms) that contains child entities.\n\n```f#\ntype UserId = UserId of int\n\ntype OrderLineId = OrderLineId of int\n\ntype OrderId = OrderId of int\n\ntype OrderLine = { Id: OrderLineId; ProductName: string }\n\ntype Coupon = {\n    Code: string\n    Expiration: DateTimeOffset\n}\n\ntype PriceData = { NetPrice: decimal }\n\ntype Order = {\n    Id: OrderId\n    OrderNumber: string\n    Lines: OrderLine list\n    AssociatedUsers: Set\u003cUserId\u003e\n    Coupon: Coupon option\n    PriceData: PriceData\n}\n```\n\n### 3. Write DTO types corresponding to the database tables\n\n[Facil](https://github.com/cmeeren/Facil) can generate these for you if you use SQL Server.\n\nFor demonstration purposes, we store the Order aggregate in five tables: One for the top-level order data, one for the\norder line data (each order can have 0..N lines), one for the associated users (0..N), one for the coupon used on the\norder (0..1), and one for the price data (1-to-1).\n\n```f#\ntype OrderDto = { OrderId: int; OrderNumber: string }\n\ntype OrderLineDto = {\n    OrderId: int\n    OrderLineId: int\n    ProductName: string\n}\n\ntype OrderAssociatedUserDto = { OrderId: int; UserId: int }\n\ntype OrderCouponDto = {\n    OrderId: int\n    Code: string\n    Expiration: DateTimeOffset\n}\n\ntype OrderPriceDataDto = { OrderId: int; NetPrice: decimal }\n```\n\n### 4. Write the functions to convert between the domain entities and DTOs\n\nFor saving, you need one function for each of the DTO types that accepts the aggregate root (`Order`) and returns the\nDTO(s).\n\n```f#\nlet orderToDto (order: Order) : OrderDto =\n    failwith \"Your code here\"\n\nlet orderToLineDtos (order: Order) : OrderLineDto list =\n    failwith \"Your code here\"\n\nlet orderToAssociatedUserDtos (order: Order) : OrderAssociatedUserDto list =\n    failwith \"Your code here\"\n\nlet orderToCouponDto (order: Order) : OrderCouponDto option =\n    failwith \"Your code here\"\n\nlet orderToPriceDataDto (order: Order) : OrderPriceDataDto =\n    failwith \"Your code here\"\n\n```\n\nFor loading, then you need one function that accepts all relevant DTOs and produce your domain object.\n\n```f#\nlet orderFromDtos\n    (dto: OrderDto)\n    (lines: OrderLineDto list)\n    (users: OrderAssociatedUserDto list)\n    (coupon: OrderCouponDto option)\n    (price: OrderPriceDataDto)\n    : Order =\n    failwith \"Your code here\"\n```\n\n### 5. Write the individual get/insert/update/delete DB functions for each table\n\n[Facil](https://github.com/cmeeren/Facil) can generate these for you if you use SQL Server. If you use Facil, it is\nhighly recommended you also install Fling.Interop.Facil and\nsee [the instructions later in the readme](#flinginteropfacil).\n\nNote that all of these functions accept `'arg` as their first argument. This can be anything, but will typically be a\nconnection string, a connection object, or tuple containing a connection and a transaction. (Just use `()` if you don’t\nneed it.)\n\nFor non-batch loading, you need functions that accept the root ID (the order ID in our case) and return the DTO(s) that\nbelong to the root:\n\n```f#\nlet getOrderLinesForOrder (connStr: string) (orderId: int) : Async\u003cDtos.OrderLineDto list\u003e =\n    failwith \"Your code here\"\n\nlet getAssociatedUsersForOrder (connStr: string) (orderId: int) : Async\u003cDtos.OrderAssociatedUserDto list\u003e =\n    failwith \"Your code here\"\n\nlet getCouponForOrder (connStr: string) (orderId: int) : Async\u003cDtos.OrderCouponDto option\u003e =\n    failwith \"Your code here\"\n\nlet getPriceDataForOrder (connStr: string) (orderId: int) : Async\u003cDtos.OrderPriceDataDto\u003e =\n    failwith \"Your code here\"\n\n```\n\nFor batch loading, you need functions that accept a list of root IDs and returns all DTOs that belong to those roots:\n\n```f#\nlet getOrderLinesForOrders (connStr: string) (orderIds: int list) : Async\u003cDtos.OrderLineDto list\u003e =\n    failwith \"\"\n\nlet getAssociatedUsersForOrders (connStr: string) (orderIds: int list) : Async\u003cDtos.OrderAssociatedUserDto list\u003e =\n    failwith \"\"\n\nlet getCouponForOrders (connStr: string) (orderIds: int list) : Async\u003cDtos.OrderCouponDto list\u003e =\n    failwith \"\"\n\nlet getPriceDataForOrders (connStr: string) (orderIds: int list) : Async\u003cDtos.OrderPriceDataDto list\u003e =\n    failwith \"\"\n```\n\nFor saving, you need functions to insert/update the root DTO and all (non-optional) to-one child DTOs, and you need\nfunctions to insert/update/delete all to-many or optional to-one child DTOs. You typically want to run all of these in a\ntransaction, so `'arg` will typically contain a connection/transaction.\n\nYou can, if you want, use an “upsert” function instead of insert/update. If you do, just pass this function as both the\ninsert and update function in the next step.\n\nThe “insert root” and “update root” functions may return `Async\u003c'a\u003e` (e.g. for returning a generated ID), and must both\nreturn the same type. All child entity insert/update/delete functions must return `Async\u003cunit\u003e`.\n\n```f#\nlet insertOrder conn (dto: OrderDto) : Async\u003cunit\u003e =\n    failwith \"Your code here\"\n\nlet updateOrder conn (dto: OrderDto) : Async\u003cunit\u003e =\n    failwith \"Your code here\"\n\nlet insertOrderLine conn (dto: OrderLineDto) : Async\u003cunit\u003e =\n    failwith \"Your code here\"\n\nlet updateOrderLine conn (dto: OrderLineDto) : Async\u003cunit\u003e =\n    failwith \"Your code here\"\n\nlet deleteOrderLine conn (orderLineId: int) : Async\u003cunit\u003e =\n    failwith \"Your code here\"\n\nlet insertOrderAssociatedUser conn (dto: OrderAssociatedUserDto) : Async\u003cunit\u003e =\n    failwith \"Your code here\"\n\nlet updateOrderAssociatedUser conn (dto: OrderAssociatedUserDto) : Async\u003cunit\u003e =\n    failwith \"Your code here\"\n\nlet deleteOrderAssociatedUser conn (orderId: int, userId: int) : Async\u003cunit\u003e =\n    failwith \"Your code here\"\n\nlet insertCoupon conn (dto: OrderCouponDto) : Async\u003cunit\u003e =\n    failwith \"Your code here\"\n\nlet updateCoupon conn (dto: OrderCouponDto) : Async\u003cunit\u003e =\n    failwith \"Your code here\"\n\nlet deleteCoupon conn (orderId: int) : Async\u003cunit\u003e =\n    failwith \"Your code here\"\n\nlet insertPriceData conn (dto: OrderPriceDataDto) : Async\u003cunit\u003e =\n    failwith \"Your code here\"\n\nlet updatePriceData conn (dto: OrderPriceDataDto) : Async\u003cunit\u003e =\n    failwith \"Your code here\"\n```\n\n### 6. Wire everything together with Fling\n\nFling now allows you to wire everything together using a declarative syntax.\n\n#### Helper to load a single root entity with all child entities\n\nGiven a computation to get a single root DTO, the function below loads the root and all child entities and calls your\nDTO-to-domain function to return the root entity.\n\n```f#\nopen Fling.Fling\n\nlet load: 'arg -\u003e ('arg -\u003e Async\u003cOrderDto option\u003e) -\u003e Async\u003cOrder option\u003e =\n    createLoader orderFromDtos _.OrderId\n    |\u003e loadChild getOrderLinesForOrder\n    |\u003e loadChild getAssociatedUsersForOrder\n    |\u003e loadChild getCouponForOrder\n    |\u003e loadChild getPriceDataForOrder\n    |\u003e loadSerial\n```\n\n#### Helper to load multiple root entities with all child entities\n\nGiven a computation to get multiple root DTOs, the function below loads all root and child entities and calls your\nDTO-to-domain function to return the root entities.\n\nIn all the calls below, you specify a function to get the root ID given the child ID. Fling uses this to know which\nchild entities belong to which roots.\n\n```f#\nopen Fling.Fling\n\nlet loadBatch: 'arg -\u003e ('arg -\u003e Async\u003cOrderDto list\u003e) -\u003e Async\u003cOrder list\u003e =\n    createBatchLoader orderFromDtos _.OrderId\n    |\u003e batchLoadChildren getOrderLinesForOrders _.OrderId\n    |\u003e batchLoadChildren getAssociatedUsersForOrders _.OrderId\n    |\u003e batchLoadOptChild getCouponForOrders _.OrderId\n    |\u003e batchLoadChild getPriceDataForOrders _.OrderId\n    |\u003e loadBatchSerial\n```\n\n#### Helper to save a root entity and all child entities\n\nGiven an old root entity (`None` for initial creation, must be `Some` for updates) and an updated root entity, this\nhelper performs the necessary inserts/updates/deletes. It skips updating identical records (compared using `=`).\n\nEverything is done in the order you specify here. For to-many child entities, all deletes are performed first, then all\nupdates, then all inserts.\n\nFor to-many and optional to-one children, you specify a function to get the ID (typically the table’s primary key) of\nthe DTO. This will be passed to the `delete` function if the entity needs to be deleted, and is used for to-many\nchildren to know which child entities to compare, delete, and insert. Though these are trivial, bugs can sneak in\nhere – [Facil](https://github.com/cmeeren/Facil) can generate these for you if you use SQL Server.\n\n```f#\nopen Fling.Fling\n\nlet save: 'arg -\u003e Order option -\u003e Order -\u003e Async\u003cunit\u003e =\n    saveRoot orderToDbDto insertOrder updateOrder\n    |\u003e batchSaveChildren orderToLineDtos _OrderLineId insertOrderLines updateOrderLines deleteOrderLines\n    |\u003e saveChildren\n        orderToAssociatedUserDtos\n        (fun dto -\u003e dto.OrderId, dto.UserId)\n        insertOrderAssociatedUser\n        updateOrderAssociatedUser\n        deleteOrderAssociatedUser\n    |\u003e saveOptChild orderToCouponDto _.OrderId insertCoupon updateCoupon deleteCoupon\n    |\u003e saveChild orderToPriceDataDto insertPriceData updatePriceData\n```\n\nNote: You **MUST** pass `Some oldEntity`  if you are updating. Pass `None` only for initial insert of the domain\naggregate. If you supply `None` while updating, all entities will be inserted, which at best will fail with a primary\nkey violation if the entity exists, or at worst will leave old child entities that should have been deleted still\npresent in your database, causing the next load to return invalid data.\n\nIf you need to return a result, use `saveRootWithOutput` instead of `saveRoot`. You then get `Async\u003c'a option\u003e` instead\nof `Async\u003cunit\u003e`, where `'a` is the return type of your insert and update functions. If the root entity was\ninserted/updated, the function returns `Some` with the result of the insert/update; otherwise it returns `None`.\n\n#### Helper to save a batch of root entities and all child entities\n\nThis works the same as the non-batched version above, but inserts/updates/deletes are run in a batch against each table.\nIn other words, at most one call is made per insert/update/delete per child type.\n\nThe batched `saveRootWithOutput` returns `Async\u003c'insertResult option * 'updateResult option\u003e`. If root entities were\ninserted and/or updated, the respective value will be `Some`; otherwise it will be `None`.\n\n```f#\nopen Fling.Fling\n\nlet save: 'arg -\u003e #seq\u003cOrder option * Order\u003e -\u003e Async\u003cunit\u003e =\n    Batch.saveRoot orderToDbDto insertOrder updateOrder\n    |\u003e Batch.saveChildren orderToLineDtos _OrderLineId insertOrderLines updateOrderLines deleteOrderLines\n    |\u003e Batch.saveChildren\n        orderToAssociatedUserDtos\n        (fun dto -\u003e dto.OrderId, dto.UserId)\n        insertOrderAssociatedUsers\n        updateOrderAssociatedUsers\n        deleteOrderAssociatedUsers\n    |\u003e Batch.saveOptChild orderToCouponDto _.OrderId insertCoupons updateCoupons deleteCoupons\n    |\u003e Batch.saveChild orderToPriceDataDto insertPriceDatas updatePriceDatas\n```\n\n### 7. Call the helpers and profit\n\nFor example:\n\n```f#\nlet saveChangesToOrder connStr (oldOrder: Order option) (newOrder: Order) = async {\n    use conn = new SqlConnection(connStr)\n    do! conn.OpenAsync() |\u003e Async.AwaitTask\n    use tran = conn.BeginTransaction()\n    do! save (conn, tran) oldOrder newOrder\n    do! tran.CommitAsync() |\u003e Async.AwaitTask\n}\n\nlet getOrderById connStr (OrderId orderId) = async {\n    use conn = new SqlConnection(connStr)\n    do! conn.OpenAsync() |\u003e Async.AwaitTask\n    use tran = conn.BeginTransaction()\n    return! dbGetOrderById oid |\u003e load (conn, tran)\n}\n\nlet getAllOrders connStr = async {\n    use conn = new SqlConnection(connStr)\n    do! conn.OpenAsync() |\u003e Async.AwaitTask\n    use tran = conn.BeginTransaction()\n    return! dbGetAllOrders |\u003e loadBatch (conn, tran)\n}\n```\n\nProduction readiness\n--------------------\n\nFling is production ready.\n\nFling is fairly well tested and is used in several mission-critical production services at our company. I’m not claiming\nit’s perfect, or even bug-free, but I have a vested interest in keeping it working properly.\n\nIt’s still at 0.x because it's still new and I may still be discovering improvements that require breaking changes every\nnow and then. However, do not take 0.x to mean that it’s a buggy mess, or that the API will radically change every other\nweek. Breaking changes will cause a lot of churn for me, too.\n\n\nFling.Interop.Facil\n-------------------\n\nFling.Interop.Facil\nuses [ugly SRTP code](https://github.com/cmeeren/Fling/blob/master/src/Fling.Interop.Facil/Library.fs) to entirely\nremove the boilerplate needed to use Fling with the data access code generated by Facil.\n\nFling.Interop.Facil works with code generated by Facil \u003e= 1.1.0.\n\nTo use it, install Fling.Interop.Facil from [NuGet](https://www.nuget.org/packages/Fling.Interop.Facil)\nand `open Fling.Interop.Facil.Fling` after `open Fling.Fling`, then use the Facil script/procedure types instead of DB\nfunctions in all Fling functions.\n\nFor (non-batch) loading:\n\n* `'arg` is locked to `SqlConnection * SqlTransaction`\n* Instead of `loadSerial`, consider `loadWithTransactionFromConnStr` or `loadWithSnapshotTransactionFromConnStr`. These\n  helpers open a connection, start a transaction, and run the loader using that connection/transaction.\n* Unlike Fling, you have to use `loadChild`, `loadOptChild`, or `loadChildren` depending on the cardinality of the\n  relationship (in Fling, `loadChild` serves all three).\n\n```f#\nopen Fling.Fling\nopen Fling.Interop.Facil.Fling\n\nlet load: (SqlConnection * SqlTransaction -\u003e Async\u003cOrderDto option\u003e) -\u003e Async\u003cOrder option\u003e =\n    createLoader orderFromDtos _.OrderId\n    |\u003e loadChildren OrderLine_ByOrderId\n    |\u003e loadChildren OrderAssociatedUser_ByOrderId\n    |\u003e loadOptChild OrderCoupon_ByOrderId\n    |\u003e loadChild OrderPriceData_ByOrderId\n    |\u003e loadWithTransactionFromConnStr \"myConnStr\"\n```\n\nUse the `load` function with `loadOne` or `loadOneNoParam` like this:\n\n```f#\nlet orderById (OrderId orderId) =\n    loadOne load Order_ById orderId\n\nlet latestOrder (OrderId orderId) =\n    loadOneNoParam load Order_GetLatest\n```\n\nFor batch loading:\n\n* `'arg` is locked to `SqlConnection * SqlTransaction`\n* Instead of `loadBatchSerial`, consider `loadBatchWithTransactionFromConnStr` or\n  `loadBatchWithSnapshotTransactionFromConnStr`. These helpers open a connection, start a transaction, and run the\n  loader using that connection/transaction.\n\n```f#\nlet loadBatch: string -\u003e (SqlConnection * SqlTransaction -\u003e Async\u003cOrderDto list\u003e) -\u003e Async\u003cOrder list\u003e =\n    createBatchLoader orderFromDtos _.OrderId\n    |\u003e batchLoadChildren OrderLine_ByOrderIds _.OrderId\n    |\u003e batchLoadChildren OrderAssociatedUser_ByOrderIds _.OrderId\n    |\u003e batchLoadOptChild OrderCoupon_ByOrderIds _.OrderId\n    |\u003e batchLoadChild OrderPriceData_ByOrderIds _.OrderId\n    |\u003e loadBatchWithTransactionFromConnStr \"myConnStr\"\n```\n\nUse the `loadBatch` function with `loadMany` or `loadManyNoParam` like this:\n\n```f#\nlet searchOrders searchArgs =\n    loadMany loadBatch Order_Search (toSearchArgsDto searchArgs)\n\nlet allOrders (OrderId orderId) =\n    loadManyNoParam loadBatch Order_GetAll\n```\n\nFor non-batch saving:\n\n* `'arg` is locked to `SqlConnection * SqlTransaction`\n\n```f#\nlet save: (SqlConnection * SqlTransaction) -\u003e Order option -\u003e Order -\u003e Async\u003cunit\u003e =\n    saveRoot orderToDbDto Order_Insert Order_Update\n    |\u003e batchSaveChildren\n        orderToLineDtos\n        DbGen.TableDtos.OrderLine.getPrimaryKey\n        OrderLine_InsertBatch\n        OrderLine_UpdateBatch\n        OrderLine_DeleteBatch\n    |\u003e saveChildren\n        orderToAssociatedUserDtos\n        DbGen.TableDtos.OrderAssociatedUser.getPrimaryKey\n        OrderAssociatedUser_Insert\n        OrderAssociatedUser_Update\n        OrderAssociatedUser_Delete\n    |\u003e saveOptChild\n        orderToCouponDto\n        DbGen.TableDtos.OrderCoupon.getPrimaryKey\n        OrderCoupon_Insert\n        OrderCoupon_Update\n        OrderCoupon_Delete\n    |\u003e saveChild orderToPriceDataDto OrderPriceData_Insert OrderPriceData_Update\n```\n\nFor batch saving:\n\n* `'arg` is locked to `SqlConnection * SqlTransaction`\n\n```f#\nlet save: (SqlConnection * SqlTransaction) -\u003e #seq\u003cOrder option * Order\u003e -\u003e Async\u003cunit\u003e =\n    saveRoot orderToDbDto Order_InsertBatch Order_UpdateBatch\n    |\u003e Batch.saveChildren\n        orderToLineDtos\n        DbGen.TableDtos.OrderLine.getPrimaryKey\n        OrderLine_InsertBatch\n        OrderLine_UpdateBatch\n        OrderLine_DeleteBatch\n    |\u003e Batch.saveChildren\n        orderToAssociatedUserDtos\n        DbGen.TableDtos.OrderAssociatedUser.getPrimaryKey\n        OrderAssociatedUser_InsertBatch\n        OrderAssociatedUser_UpdateBatch\n        OrderAssociatedUser_DeleteBatch\n    |\u003e Batch.saveOptChild\n        orderToCouponDto\n        DbGen.TableDtos.OrderCoupon.getPrimaryKey\n        OrderCoupon_InsertBatch\n        OrderCoupon_UpdateBatch\n        OrderCoupon_DeleteBatch\n    |\u003e Batch.saveChild orderToPriceDataDto OrderPriceData_InsertBatch OrderPriceData_UpdateBatch\n```\n\nUse `saveWithTransactionFromConnStr` or `Batch.saveWithTransactionFromConnStr` to “convert” the\n`SqlConnection * SqlTransaction` to a `string` (connection string) and run the whole save in a transaction. This is\nuseful if you don’t need to run the save in a transaction with anything else:\n\n```f#\nlet save : string -\u003e Order option -\u003e Order -\u003e Async\u003cunit\u003e =\n    (* same code as above *)\n    |\u003e saveWithTransactionFromConnStr\n```\n\n```f#\nlet save : string -\u003e #seq\u003cOrder option * Order\u003e -\u003e Async\u003cunit\u003e =\n    (* same code as above *)\n    |\u003e Batch.saveWithTransactionFromConnStr\n```\n\nAs with Fling, use `saveRootWithOutput` instead of `saveRoot` if you need to return anything from the root’s\ninsert/update script\n\nLimitations\n-----------\n\n### Cannot interleave inserts/updates/deletes for different tables\n\nIt’s not possible to interleave inserts/updates/deletes for different tables. For example, you can’t specify that Fling\nshould *insert* first into table A and then into table B while at the same time  *delete* from table A and then from\ntable B. The ordering of operations can only be specified at the table (or “child”) level; all inserts/updates/deletes\nfor a table is performed before the next table. This may have implications for foreign key constraints in complex\naggregates.\n\n## Deployment checklist\n\nFor maintainers.\n\n* Make necessary changes to the code\n* Update the changelog\n* Update the version in `Fling.fsproj` and/or `Fling.Interop.Facil.fsproj`\n* Commit and push to `main`. If the GitHub build succeeds, the packages are automatically published to NuGet.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcmeeren%2FFling","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcmeeren%2FFling","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcmeeren%2FFling/lists"}