diff --git a/.artifactignore b/.artifactignore
new file mode 100644
index 0000000000..a287e295ae
--- /dev/null
+++ b/.artifactignore
@@ -0,0 +1,3 @@
+**/*
+!**/bin/**
+!**/obj/**
diff --git a/.editorconfig b/.editorconfig
index eba04ad326..faf5c7766a 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -282,7 +282,7 @@ dotnet_naming_style.internal_error_style.required_suffix = ____INTERNAL_ERROR___
# All public/protected/protected_internal constant fields must be PascalCase
# https://docs.microsoft.com/dotnet/standard/design-guidelines/field
-dotnet_naming_symbols.public_protected_constant_fields_group.applicable_accessibilities = public, protected, protected_internal
+dotnet_naming_symbols.public_protected_constant_fields_group.applicable_accessibilities = public, protected, protected_internal, internal, private
dotnet_naming_symbols.public_protected_constant_fields_group.required_modifiers = const
dotnet_naming_symbols.public_protected_constant_fields_group.applicable_kinds = field
dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.symbols = public_protected_constant_fields_group
@@ -356,24 +356,13 @@ dotnet_naming_rule.parameters_rule.symbols = parameters_group
dotnet_naming_rule.parameters_rule.style = camel_case_style
dotnet_naming_rule.parameters_rule.severity = warning
-# Private static fields use camelCase and start with s_
-dotnet_naming_symbols.private_static_field_symbols.applicable_accessibilities = private
-dotnet_naming_symbols.private_static_field_symbols.required_modifiers = static, shared
-dotnet_naming_symbols.private_static_field_symbols.applicable_kinds = field
-dotnet_naming_rule.private_static_fields_must_be_camel_cased_and_prefixed_with_s_underscore.symbols = private_static_field_symbols
-dotnet_naming_rule.private_static_fields_must_be_camel_cased_and_prefixed_with_s_underscore.style = camel_case_and_prefix_with_s_underscore_style
-dotnet_naming_rule.private_static_fields_must_be_camel_cased_and_prefixed_with_s_underscore.severity = warning
-dotnet_naming_style.camel_case_and_prefix_with_s_underscore_style.required_prefix = s_
-dotnet_naming_style.camel_case_and_prefix_with_s_underscore_style.capitalization = camel_case
-
# Instance fields use camelCase and are prefixed with '_'
-dotnet_naming_symbols.private_field_symbols.applicable_accessibilities = private
-dotnet_naming_symbols.private_field_symbols.applicable_kinds = field
-dotnet_naming_rule.private_instance_fields_must_be_camel_cased_and_prefixed_with_underscore.symbols = private_field_symbols
-dotnet_naming_rule.private_instance_fields_must_be_camel_cased_and_prefixed_with_underscore.style = camel_case_and_prefix_with_underscore_style
-dotnet_naming_rule.private_instance_fields_must_be_camel_cased_and_prefixed_with_underscore.severity = warning
-dotnet_naming_style.camel_case_and_prefix_with_underscore_style.required_prefix = _
-dotnet_naming_style.camel_case_and_prefix_with_underscore_style.capitalization = camel_case
+dotnet_naming_rule.instance_fields_should_be_camel_case.severity = warning
+dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
+dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
+dotnet_naming_symbols.instance_fields.applicable_kinds = field
+dotnet_naming_style.instance_field_style.capitalization = camel_case
+dotnet_naming_style.instance_field_style.required_prefix = _
##########################################
# License
@@ -408,4 +397,4 @@ dotnet_naming_style.camel_case_and_prefix_with_underscore_style.capitalization
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
-##########################################
\ No newline at end of file
+##########################################
diff --git a/.github/BUILD.md b/.github/BUILD.md
index 5f962a8911..12b64b287f 100644
--- a/.github/BUILD.md
+++ b/.github/BUILD.md
@@ -1,33 +1,99 @@
-# Umbraco CMS Build
+# Umbraco CMS Build
## Are you sure?
In order to use Umbraco as a CMS and build your website with it, you should not build it yourself. If you're reading this then you're trying to contribute to Umbraco or you're debugging a complex issue.
-- Are you about to create a pull request for Umbraco?
+- Are you about to [create a pull request for Umbraco][contribution guidelines]?
- Are you trying to get to the bottom of a problem in your existing Umbraco installation?
If the answer is yes, please read on. Otherwise, make sure to head on over [to the download page](https://our.umbraco.com/download) and start using Umbraco CMS as intended.
-**Table of contents**
+## Table of contents
-[Building from source](#building-from-source)
- * [The quick build](#quick)
- * [Build infrastructure](#build-infrastructure)
- * [Properties](#properties)
- * [GetUmbracoVersion](#getumbracoversion)
- * [SetUmbracoVersion](#setumbracoversion)
- * [Build](#build)
- * [Build-UmbracoDocs](#build-umbracodocs)
- * [Verify-NuGet](#verify-nuget)
- * [Cleaning up](#cleaning-up)
+↖️ You can jump to any section by using the "table of contents" button (  ) above.
-[Azure DevOps](#azure-devops)
-[Quirks](#quirks)
- * [Powershell quirks](#powershell-quirks)
- * [Git quirks](#git-quirks)
+## Debugging source locally
+Did you read ["Are you sure"](#are-you-sure)?
+
+[More details about contributing to Umbraco and how to use the GitHub tooling can be found in our guide to contributing.][contribution guidelines]
+
+If you want to run a build without debugging, see [Building from source](#building-from-source) below. This runs the build in the same way it is run on our build servers.
+
+#### Debugging with VS Code
+
+In order to build the Umbraco source code locally with Visual Studio Code, first make sure you have the following installed.
+
+ * [Visual Studio Code](https://code.visualstudio.com/)
+ * [dotnet SDK v6.0.2+](https://dotnet.microsoft.com/en-us/download)
+ * [Node.js v14+](https://nodejs.org/en/download/)
+ * npm v7+ (installed with Node.js)
+ * [Git command line](https://git-scm.com/download/)
+
+Open the root folder of the repository in Visual Studio Code.
+
+To build the front end you'll need to open the command pallet (Ctrl + Shift + P) and run `>Tasks: Run Task` followed by `Client Watch` and then run the `Client Build` task in the same way.
+
+You can also run the tasks manually on the command line:
+
+```
+cd src\Umbraco.Web.UI.Client
+npm install
+npm run dev
+```
+
+or
+
+```
+cd src\Umbraco.Web.UI.Client
+npm install
+gulp dev
+```
+
+**The initial Gulp build might take a long time - don't worry, this will be faster on subsequent runs.**
+
+You might run into [Gulp quirks](#gulp-quirks).
+
+The caching for the back office has been described as 'aggressive' so we often find it's best when making back office changes to [disable caching in the browser (check "Disable cache" on the "Network" tab of developer tools)][disable browser caching] to help you to see the changes you're making.
+
+To run the C# portion of the project, either hit F5 to begin debugging, or manually using the command line:
+
+```
+dotnet watch --project .\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj
+```
+
+**The initial C# build might take a _really_ long time (seriously, go and make a cup of coffee!) - but don't worry, this will be faster on subsequent runs.**
+
+When the page eventually loads in your web browser, you can follow the installer to set up a database for debugging. You may also wish to install a [starter kit][starter kits] to ease your debugging.
+
+#### Debugging with Visual Studio
+
+In order to build the Umbraco source code locally with Visual Studio, first make sure you have the following installed.
+
+ * [Visual Studio 2019 v16.8+ with .NET 6.0.2+](https://visualstudio.microsoft.com/vs/) ([the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects)
+ * [Node.js v14+](https://nodejs.org/en/download/)
+ * npm v7+ (installed with Node.js)
+ * [Git command line](https://git-scm.com/download/)
+
+The easiest way to get started is to open `umbraco.sln` in Visual Studio.
+
+To build the front end, you'll first need to run `cd src\Umbraco.Web.UI.Client && npm install` in the command line (or `cd src\Umbraco.Web.UI.Client; npm install` in PowerShell). Then find the Task Runner Explorer (View → Other Windows → Task Runner Explorer) and run the `build` task under `Gulpfile.js`. You may need to refresh the Task Runner Explorer before the tasks load.
+
+If you're working on the backoffice, you may wish to run the `dev` command instead while you're working with it, so changes are copied over to the appropriate directories and you can refresh your browser to view the results of your changes.
+
+**The initial Gulp build might take a long time - don't worry, this will be faster on subsequent runs.**
+
+You might run into [Gulp quirks](#gulp-quirks).
+
+The caching for the back office has been described as 'aggressive' so we often find it's best when making back office changes to [disable caching in the browser (check "Disable cache" on the "Network" tab of developer tools)][disable browser caching] to help you to see the changes you're making.
+
+"The rest" is a C# based codebase, which is mostly ASP.NET Core MVC based. You can make changes, build them in Visual Studio, and hit F5 to see the result.
+
+**The initial C# build might take a _really_ long time (seriously, go and make a cup of coffee!) - but don't worry, this will be faster on subsequent runs.**
+
+When the page eventually loads in your web browser, you can follow the installer to set up a database for debugging. You may also wish to install a [starter kit][starter kits] to ease your debugging.
## Building from source
@@ -38,13 +104,14 @@ Did you read ["Are you sure"](#are-you-sure)?
To build Umbraco, fire up PowerShell and move to Umbraco's repository root (the directory that contains `src`, `build`, `LICENSE.md`...). There, trigger the build with the following command:
build/build.ps1
-
+
If you only see a build.bat-file, you're probably on the wrong branch. If you switch to the correct branch (v8/contrib) the file will appear and you can build it.
You might run into [Powershell quirks](#powershell-quirks).
If it runs without errors; Hooray! Now you can continue with [the next step](CONTRIBUTING.md#how-do-i-begin) and open the solution and build it.
+
### Build Infrastructure
The Umbraco Build infrastructure relies on a PowerShell object. The object can be retrieved with:
@@ -145,7 +212,7 @@ To perform a more complete clear, you will want to also delete the content of th
The following command will force remove all untracked files and directories, whether they are ignored by Git or not. Combined with `git reset` it can recreate a pristine working directory.
git clean -xdf .
-
+
For git documentation see:
* git [clean]()
* git [reset]()
@@ -214,3 +281,19 @@ The best solution is to unblock the Zip file before un-zipping: right-click the
### Git Quirks
Git might have issues dealing with long file paths during build. You may want/need to enable `core.longpaths` support (see [this page](https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path) for details).
+
+### Gulp Quirks
+
+You may need to run the following commands to set up gulp properly:
+
+ ```
+npm cache clean --force
+npm ci
+npm run build
+ ```
+
+
+
+[ contribution guidelines]: CONTRIBUTING.md "Read the guide to contributing for more details on contributing to Umbraco"
+[ starter kits ]: https://our.umbraco.com/packages/?category=Starter%20Kits&version=9 "Browse starter kits available for v9 on Our "
+[ disable browser caching ]: https://techwiser.com/disable-cache-google-chrome-firefox "Instructions on how to disable browser caching in Chrome and Firefox"
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index e8b378fb15..28618fb548 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -2,192 +2,131 @@
👍🎉 First off, thanks for taking the time to contribute! 🎉👍
-The following is a set of guidelines, for contributing to Umbraco CMS.
+These contribution guidelines are mostly just that - guidelines, not rules. This is what we've found to work best over the years, but if you choose to ignore them, we still love you! 💖 Use your best judgement, and feel free to propose changes to this document in a pull request.
-These are mostly guidelines, not rules. Use your best judgement, and feel free to propose changes to this document in a pull request.
+## Coding not your thing? Or want more ways to contribute?
-Remember, we're a friendly bunch and are happy with whatever contribution you might provide. Below are guidelines for success that we've gathered over the years. If you choose to ignore them then we still love you 💖.
+This document covers contributing to the codebase of the CMS but [the community site has plenty of inspiration for other ways to get involved.][get involved]
-**Code of conduct**
+If you don't feel you'd like to make code changes here, you can visit our [documentation repository][docs repo] and use your experience to contribute to making the docs we have, even better.
-This project and everyone participating in it, is governed by the [our Code of Conduct](https://github.com/umbraco/.github/blob/main/.github/CODE_OF_CONDUCT.md).
+We also encourage community members to feel free to comment on others' pull requests and issues - the expertise we have is not limited to the Core Collaborators and HQ. So, if you see something on the issue tracker or pull requests you feel you can add to, please don't be shy.
-**Table of contents**
+## Table of contents
-[Contributing code changes](#contributing-code-changes)
- * [Guidelines for contributions we welcome](#guidelines-for-contributions-we-welcome)
- * [Ownership and copyright](#ownership-and-copyright)
- * [What can I start with?](#what-can-i-start-with)
- * [How do I begin?](#how-do-i-begin)
- * [Pull requests](#pull-requests)
+- [Before you start](#before-you-start)
+ * [Code of Conduct](#code-of-conduct)
+ * [What can I contribute?](#what-can-i-contribute)
+ + [Making larger changes](#making-larger-changes)
+ + [Pull request or package?](#pull-request-or-package)
+ + [Ownership and copyright](#ownership-and-copyright)
+- [Finding your first issue: Up for grabs](#finding-your-first-issue-up-for-grabs)
+- [Making your changes](#making-your-changes)
+ + [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository)
+ + [Style guide](#style-guide)
+ + [Questions?](#questions)
+- [Creating a pull request](#creating-a-pull-request)
+- [The review process](#the-review-process)
+ * [Dealing with requested changes](#dealing-with-requested-changes)
+ + [No longer available?](#no-longer-available)
+ * [The Core Collaborators team](#the-core-collaborators-team)
-[Reviews](#reviews)
- * [Styleguides](#styleguides)
- * [The Core Contributors](#the-core-contributors-team)
- * [Questions?](#questions)
+## Before you start
-[Working with the code](#working-with-the-code)
- * [Building Umbraco from source code](#building-umbraco-from-source-code)
- * [Working with the source code](#working-with-the-source-code)
- * [Making changes after the PR is open](#making-changes-after-the-pr-is-open)
- * [Which branch should I target for my contributions?](#which-branch-should-i-target-for-my-contributions)
- * [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository)
-## Contributing code changes
+### Code of Conduct
-This document gives you a quick overview on how to get started.
+This project and everyone participating in it, is governed by the [our Code of Conduct][code of conduct].
-### Guidelines for contributions we welcome
+### What can I contribute?
-Not all changes are wanted, so on occasion we might close a PR without merging it. We will give you feedback why we can't accept your changes and we'll be nice about it, thanking you for spending your valuable time.
+We categorise pull requests (PRs) into two categories:
-We have [documented what we consider small and large changes](CONTRIBUTION_GUIDELINES.md). Make sure to talk to us before making large changes, so we can ensure that you don't put all your hard work into something we would not be able to merge.
+| PR type | Definition |
+| --------- | ------------------------------------------------------------ |
+| Small PRs | Bug fixes and small improvements - can be recognized by seeing a small number of changes and possibly a small number of new files. |
+| Large PRs | New features and large refactorings - can be recognized by seeing a large number of changes, plenty of new files, updates to package manager files (NuGet’s packages.config, NPM’s packages.json, etc.). |
-Remember, it is always worth working on an issue from the `Up for grabs` list or even asking for some feedback before you send us a PR. This way, your PR will not be closed as unwanted.
+We’re usually able to handle small PRs pretty quickly. A community volunteer will do the initial review and flag it for Umbraco HQ as “community tested”. If everything looks good, it will be merged pretty quickly [as per the described process][review process].
+
+We would love to follow the same process for larger PRs but this is not always possible due to time limitations and priorities that need to be aligned. We don’t want to put up any barriers, but this document should set the correct expectations.
+
+Not all changes are wanted, so on occasion we might close a PR without merging it but if we do, we will give you feedback why we can't accept your changes. **So make sure to [talk to us before making large changes][making larger changes]**, so we can ensure that you don't put all your hard work into something we would not be able to merge.
+
+#### Making larger changes
+
+[making larger changes]: #making-larger-changes
+
+Please make sure to describe your larger ideas in an [issue (bugs)][issues] or [discussion (new features)][discussions], it helps to put in mock up screenshots or videos. If the change makes sense for HQ to include in Umbraco CMS we will leave you some feedback on how we’d like to see it being implemented.
+
+If a larger pull request is encouraged by Umbraco HQ, the process will be similar to what is described in the small PRs process above, we strive to feedback within 14 days. Finalizing and merging the PR might take longer though as it will likely need to be picked up by the development team to make sure everything is in order. We’ll keep you posted on the progress.
+
+#### Pull request or package?
+
+[pr or package]: #pull-request-or-package
+
+If you're unsure about whether your changes belong in the core Umbraco CMS or if you should turn your idea into a package instead, make sure to [talk to us][making larger changes].
+
+If it doesn’t fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and fix bugs. Eventually, a package could "graduate" to be included in the CMS.
#### Ownership and copyright
-It is your responsibility to make sure that you're allowed to share the code you're providing us.
-For example, you should have permission from your employer or customer to share code.
+It is your responsibility to make sure that you're allowed to share the code you're providing us. For example, you should have permission from your employer or customer to share code.
Similarly, if your contribution is copied or adapted from somewhere else, make sure that the license allows you to reuse that for a contribution to Umbraco-CMS.
If you're not sure, leave a note on your contribution and we will be happy to guide you.
-When your contribution has been accepted, it will be [MIT licensed](https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/LICENSE.md) from that time onwards.
+When your contribution has been accepted, it will be [MIT licensed][MIT license] from that time onwards.
-### What can I start with?
+## Finding your first issue: Up for grabs
-Unsure where to begin contributing to Umbraco? You can start by looking through [these `Up for grabs` issues](https://github.com/umbraco/Umbraco-CMS/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs+)
+Umbraco HQ will regularly mark newly created issues on the issue tracker with [the `community/up-for-grabs` tag][up for grabs issues]. This means that the proposed changes are wanted in Umbraco but the HQ does not have the time to make them at this time. We encourage anyone to pick them up and help out.
-### How do I begin?
+If you do start working on something, make sure to leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has.
+
+## Making your changes
Great question! The short version goes like this:
- * **Fork** - create a fork of [`Umbraco-CMS` on GitHub](https://github.com/umbraco/Umbraco-CMS)
+1. **Fork**
- 
+ Create a fork of [`Umbraco-CMS` on GitHub][Umbraco CMS repo]
+
+ 
+
+1. **Clone**
- * **Clone** - when GitHub has created your fork, you can clone it in your favorite Git tool
+ When GitHub has created your fork, you can clone it in your favorite Git tool
+
+ 
+
+1. **Switch to the correct branch**
- 
+ Switch to the `v10/contrib` branch
- * **Switch to the correct branch** - switch to the `v9/contrib` branch
- * **Build** - build your fork of Umbraco locally as described in [building Umbraco from source code](BUILD.md)
- * **Change** - make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback](#questions)
- * **Commit** - done? Yay! 🎉 **Important:** create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`. When you have a branch, commit your changes. Don't commit to `v9/contrib`, create a new branch first.
- * **Push** - great, now you can push the changes up to your fork on GitHub
- * **Create pull request** - exciting! You're ready to show us your changes (or not quite ready, you just need some feedback to progress - you can now make use of GitHub's draft pull request status, detailed [here](https://github.blog/2019-02-14-introducing-draft-pull-requests/)). GitHub has picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go.
+1. **Build**
- 
+ Build your fork of Umbraco locally as described in the build documentation: you can [debug with Visual Studio Code][build - debugging with code] or [with Visual Studio][build - debugging with vs].
-### Pull requests
-The most successful pull requests usually look a like this:
+1. **Branch**
- * Fill in the required template (shown when starting a PR on GitHub), and link your pull request to an issue on the [issue tracker,](https://github.com/umbraco/Umbraco-CMS/issues) if applicable.
- * Include screenshots and animated GIFs in your pull request whenever possible.
- * Unit tests, while optional, are awesome. Thank you!
- * New code is commented with documentation from which [the reference documentation](https://our.umbraco.com/documentation/Reference/) is generated.
+ Create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case issue number `12345`. Don't commit to `v10/contrib`, create a new branch first.
-Again, these are guidelines, not strict requirements. However, the more information that you give to us, the more we have to work with when considering your contributions. Good documentation of a pull request can really speed up the time it takes to review and merge your work!
+1. **Change**
-## Reviews
+ Make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback][questions].
-You've sent us your first contribution - congratulations! Now what?
+1. **Commit and push**
-The [pull request team](#the-pr-team) can now start reviewing your proposed changes and give you feedback on them. If it's not perfect, we'll either fix up what we need or we can request that you make some additional changes.
+ Done? Yay! 🎉
-We have [a process in place which you can read all about](REVIEW_PROCESS.md). The very abbreviated version is:
+ Remember to commit to your new `temp` branch, and don't commit to `v10/contrib`. Then you can push the changes up to your fork on GitHub.
-- Your PR will get a reply within 48 hours
-- An in-depth reply will be added within at most 2 weeks
-- The PR will be either merged or rejected within at most 4 weeks
-- Sometimes it is difficult to meet these timelines and we'll talk to you if this is the case.
+#### Keeping your Umbraco fork in sync with the main repository
+[sync fork]: #keeping-your-umbraco-fork-in-sync-with-the-main-repository
-### Styleguides
-
-To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up.
-
-That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc.
-
-### The Core Contributors team
-
-The Core Contributors team consists of one member of Umbraco HQ, [Sebastiaan](https://github.com/nul800sebastiaan), who gets assistance from the following community members who have comitted to volunteering their free time:
-
-- [Nathan Woulfe](https://github.com/nathanwoulfe)
-- [Joe Glombek](https://github.com/glombek)
-- [Laura Weatherhead](https://github.com/lssweatherhead)
-- [Michael Latouche](https://github.com/mikecp)
-- [Owain Williams](https://github.com/OwainWilliams)
-
-
-These wonderful people aim to provide you with a first reply to your PR, review and test out your changes and on occasions, they might ask more questions. If they are happy with your work, they'll let Umbraco HQ know by approving the PR. Hq will have final sign-off and will check the work again before it is merged.
-
-### Questions?
-
-You can get in touch with [the core contributors team](#the-core-contributors-team) in multiple ways; we love open conversations and we are a friendly bunch. No question you have is stupid. Any question you have usually helps out multiple people with the same question. Ask away:
-
-- If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward.
-- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"](https://our.umbraco.com/forum/contributing-to-umbraco-cms/) forum. The team monitors that one closely, so one of us will be on hand and ready to point you in the right direction.
-
-## Working with the code
-
-### Building Umbraco from source code
-
-In order to build the Umbraco source code locally, first make sure you have the following installed.
-
- * [Visual Studio 2019 v16.8+ (with .NET Core 3.0)](https://visualstudio.microsoft.com/vs/)
- * [Node.js v10+](https://nodejs.org/en/download/)
- * npm v6.4.1+ (installed with Node.js)
- * [Git command line](https://git-scm.com/download/)
-
-The easiest way to get started is to open `src\umbraco.sln` in Visual Studio 2019 (version 16.3 or higher, [the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects). In Visual Studio, find the Task Runner Explorer (in the View menu under Other Windows) and run the build task under the gulpfile.
-
-Alternatively, you can run `build.ps1` from the Powershell command line, which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `gulp dev` in `src\Umbraco.Web.UI.Client`. See [this page](BUILD.md) for more details.
-
-
-
-After this build completes, you should be able to hit `F5` in Visual Studio to build and run the project. A IISExpress webserver will start and the Umbraco installer will pop up in your browser. Follow the directions there to get a working Umbraco install up and running.
-
-### Working with the source code
-
-Some parts of our source code are over 10 years old now. And when we say "old", we mean "mature" of course!
-
-There are two big areas that you should know about:
-
- 1. The Umbraco backoffice is a extensible AngularJS app and requires you to run a `gulp dev` command while you're working with it, so changes are copied over to the appropriate directories and you can refresh your browser to view the results of your changes.
- You may need to run the following commands to set up gulp properly:
- ```
- npm cache clean --force
- npm ci
- npm run build
- ```
- The caching for the back office has been described as 'aggressive' so we often find it's best when making back office changes to disable caching in the browser to help you to see the changes you're making.
-
- 2. "The rest" is a C# based codebase, which is mostly ASP.NET MVC based. You can make changes, build them in Visual Studio, and hit `F5` to see the result.
-
-To find the general areas for something you're looking to fix or improve, have a look at the following two parts of the API documentation.
-
- * [The AngularJS based backoffice files](https://apidocs.umbraco.com/v9/ui#/api) (to be found in `src\Umbraco.Web.UI.Client\src`)
- * [The C# application](https://apidocs.umbraco.com/v9/csharp/)
-
-### Which branch should I target for my contributions?
-
-We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-flow/), but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `v9/contrib`. If you are working on v9, this is the branch you should be targetting. For v8 contributions, please target 'v8/contrib'
-
-Please note: we are no longer accepting features for v7 but will continue to merge bug fixes as and when they arise.
-
-
-
-### Making changes after the PR is open
-
-If you make the corrections we ask for in the same branch and push them to your fork again, the pull request automatically updates with the additional commit(s) so we can review it again. If all is well, we'll merge the code and your commits are forever part of Umbraco!
-
-### Keeping your Umbraco fork in sync with the main repository
-
-We recommend you to sync with our repository before you submit your pull request. That way, you can fix any potential merge conflicts and make our lives a little bit easier.
-
-Also, if you have submitted a pull request three weeks ago and want to work on something new, you'll want to get the latest code to build against of course.
+Once you've already got a fork and cloned your fork locally, you can skip steps 1 and 2 going forward. Just remember to keep your fork up to date before making further changes.
To sync your fork with this original one, you'll have to add the upstream url. You only have to do this once:
@@ -199,13 +138,110 @@ Then when you want to get the changes from the main repository:
```
git fetch upstream
-git rebase upstream/v9/contrib
+git rebase upstream/v10/contrib
```
-In this command we're syncing with the `v9/contrib` branch, but you can of course choose another one if needed.
+In this command we're syncing with the `v10/contrib` branch, but you can of course choose another one if needed.
-(More info on how this works: [http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated](http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated))
+[More information on how this works can be found on the thoughtbot blog.][sync fork ext]
-### And finally
+#### Style guide
-We welcome all kinds of contributions to this repository. If you don't feel you'd like to make code changes here, you can visit our [documentation repository](https://github.com/umbraco/UmbracoDocs) and use your experience to contribute to making the docs we have, even better. We also encourage community members to feel free to comment on others' pull requests and issues - the expertise we have is not limited to the Core Contributors and HQ. So, if you see something on the issue tracker or pull requests you feel you can add to, please don't be shy.
+To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up.
+
+That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc.
+
+#### Questions?
+[questions]: #questions
+
+You can get in touch with [the core contributors team][core collabs] in multiple ways; we love open conversations and we are a friendly bunch. No question you have is stupid. Any question you have usually helps out multiple people with the same question. Ask away:
+
+- If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward.
+- If you want to ask questions on some code you've already written you can create a draft pull request, [detailed in a GitHub blog post][draft prs].
+- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"][contrib forum] forum. The team monitors that one closely, so one of us will be on hand and ready to point you in the right direction.
+
+## Creating a pull request
+
+Exciting! You're ready to show us your changes.
+
+We recommend you to [sync with our repository][sync fork] before you submit your pull request. That way, you can fix any potential merge conflicts and make our lives a little bit easier.
+
+GitHub will have picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go.
+
+
+We like to use [git flow][git flow] as much as possible, but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `v10/contrib`. If you are working on v9, this is the branch you should be targeting.
+
+Please note: we are no longer accepting features for v8 and below but will continue to merge security fixes as and when they arise.
+
+## The review process
+[review process]: #the-review-process
+
+You've sent us your first contribution - congratulations! Now what?
+
+The [Core Collaborators team][Core collabs] can now start reviewing your proposed changes and give you feedback on them. If it's not perfect, we'll either fix up what we need or we can request that you make some additional changes.
+
+You will get an initial automated reply from our [Friendly Umbraco Robot, Umbrabot][Umbrabot], to acknowledge that we’ve seen your PR and we’ll pick it up as soon as we can. You can take this opportunity to double check everything is in order based off the handy checklist Umbrabot provides.
+
+You will get feedback as soon as the [Core Collaborators team][Core collabs] can after opening the PR. You’ll most likely get feedback within a couple of weeks. Then there are a few possible outcomes:
+
+- Your proposed change is awesome! We merge it in and it will be included in the next minor release of Umbraco
+- If the change is a high priority bug fix, we will cherry-pick it into the next patch release as well so that we can release it as soon as possible
+- Your proposed change is awesome but needs a bit more work, we’ll give you feedback on the changes we’d like to see
+- Your proposed change is awesome but... not something we’re looking to include at this point. We’ll close your PR and the related issue (we’ll be nice about it!). See [making larger changes][making larger changes] and [pull request or package?][pr or package]
+
+### Dealing with requested changes
+
+If you make the corrections we ask for in the same branch and push them to your fork again, the pull request automatically updates with the additional commit(s) so we can review it again. If all is well, we'll merge the code and your commits are forever part of Umbraco!
+
+#### No longer available?
+
+We understand you have other things to do and can't just drop everything to help us out.
+
+So if we’re asking for your help to improve the PR we’ll wait for two weeks to give you a fair chance to make changes. We’ll ask for an update if we don’t hear back from you after that time.
+
+If we don’t hear back from you for 4 weeks, we’ll close the PR so that it doesn’t just hang around forever. You’re very welcome to re-open it once you have some more time to spend on it.
+
+There will be times that we really like your proposed changes and we’ll finish the final improvements we’d like to see ourselves. You still get the credits and your commits will live on in the git repository.
+
+### The Core Collaborators team
+[Core collabs]: #the-core-collaborators-team
+
+The Core Contributors team consists of one member of Umbraco HQ, [Sebastiaan][Sebastiaan], who gets assistance from the following community members who have committed to volunteering their free time:
+
+- [Nathan Woulfe][Nathan Woulfe]
+- [Joe Glombek][Joe Glombek]
+- [Laura Weatherhead][Laura Weatherhead]
+- [Michael Latouche][Michael Latouche]
+- [Owain Williams][Owain Williams]
+
+
+These wonderful people aim to provide you with a reply to your PR, review and test out your changes and on occasions, they might ask more questions. If they are happy with your work, they'll let Umbraco HQ know by approving the PR. HQ will have final sign-off and will check the work again before it is merged.
+
+
+
+
+
+[MIT license]: ../LICENSE.md "Umbraco's license declaration"
+[build - debugging with vs]: BUILD.md#debugging-with-visual-studio "Details on building and debugging Umbraco with Visual Studio"
+[build - debugging with code]: BUILD.md#debugging-with-vs-code "Details on building and debugging Umbraco with Visual Studio Code"
+
+
+
+[Nathan Woulfe]: https://github.com/nathanwoulfe "Nathan's GitHub profile"
+[Joe Glombek]: https://github.com/glombek "Joe's GitHub profile"
+[Laura Weatherhead]: https://github.com/lssweatherhead "Laura's GitHub profile"
+[Michael Latouche]: https://github.com/mikecp "Michael's GitHub profile"
+[Owain Williams]: https://github.com/OwainWilliams "Owain's GitHub profile"
+[Sebastiaan]: https://github.com/nul800sebastiaan "Senastiaan's GitHub profile"
+[ Umbrabot ]: https://github.com/umbrabot
+[git flow]: https://jeffkreeftmeijer.com/git-flow/ "An explanation of git flow"
+[sync fork ext]: http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated "Details on keeping a git fork updated"
+[draft prs]: https://github.blog/2019-02-14-introducing-draft-pull-requests/ "Github's blog post providing details on draft pull requests"
+[contrib forum]: https://our.umbraco.com/forum/contributing-to-umbraco-cms/
+[get involved]: https://community.umbraco.com/get-involved/
+[docs repo]: https://github.com/umbraco/UmbracoDocs
+[code of conduct]: https://github.com/umbraco/.github/blob/main/.github/CODE_OF_CONDUCT.md
+[up for grabs issues]: https://github.com/umbraco/Umbraco-CMS/issues?q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs
+[Umbraco CMS repo]: https://github.com/umbraco/Umbraco-CMS
+[issues]: https://github.com/umbraco/Umbraco-CMS/issues
+[discussions]: https://github.com/umbraco/Umbraco-CMS/discussions
diff --git a/.github/CONTRIBUTION_GUIDELINES.md b/.github/CONTRIBUTION_GUIDELINES.md
deleted file mode 100644
index 0ac35e6897..0000000000
--- a/.github/CONTRIBUTION_GUIDELINES.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# Contributing to Umbraco CMS
-
-When you’re considering creating a pull request for Umbraco CMS, we will categorize them in two different sizes, small and large.
-
-The process for both sizes is very similar, as [explained in the contribution document](CONTRIBUTING.md#how-do-i-begin).
-
-## Small PRs
-Bug fixes and small improvements - can be recognized by seeing a small number of changes and possibly a small number of new files.
-
-We’re usually able to handle small PRs pretty quickly. A community volunteer will do the initial review and flag it for Umbraco HQ as “community tested”. If everything looks good, it will be merged pretty quickly [as per the described process](REVIEW_PROCESS.md).
-
-### Up for grabs
-
-Umbraco HQ will regularly mark newly created issues on the issue tracker with the `Up for grabs` tag. This means that the proposed changes are wanted in Umbraco but the HQ does not have the time to make them at this time. We encourage anyone to pick them up and help out.
-
-If you do start working on something, make sure to leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has.
-
-## Large PRs
-New features and large refactorings - can be recognized by seeing a large number of changes, plenty of new files, updates to package manager files (NuGet’s packages.config, NPM’s packages.json, etc.).
-
-We would love to follow the same process for larger PRs but this is not always possible due to time limitations and priorities that need to be aligned. We don’t want to put up any barriers, but this document should set the correct expectations.
-
-Please make sure to describe your idea in an issue, it helps to put in mockup screenshots or videos.
-
-If the change makes sense for HQ to include in Umbraco CMS we will leave you some feedback on how we’d like to see it being implemented.
-
-If a larger pull request is encouraged by Umbraco HQ, the process will be similar to what is described in the [small PRs process](#small-prs) above, we strive to feedback within 14 days. Finalizing and merging the PR might take longer though as it will likely need to be picked up by the development team to make sure everything is in order. We’ll keep you posted on the progress.
-
-It is highly recommended that you speak to the HQ before making large, complex changes.
-
-### Pull request or package?
-
-If it doesn’t fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and fix bugs.
-
-Eventually, a package could "graduate" to be included in the CMS.
diff --git a/.github/REVIEW_PROCESS.md b/.github/REVIEW_PROCESS.md
deleted file mode 100644
index 917d25b090..0000000000
--- a/.github/REVIEW_PROCESS.md
+++ /dev/null
@@ -1,25 +0,0 @@
-# Review process
-
-You're an awesome person and have sent us your contribution in the form of a pull request! It's now time to relax for a bit and wait for our response.
-
-In order to set some expectations, here's what happens next.
-
-## Review process
-
-You will get an initial reply within 48 hours (workdays) to acknowledge that we’ve seen your PR and we’ll pick it up as soon as we can.
-
-You will get feedback within at most 14 days after opening the PR. You’ll most likely get feedback sooner though. Then there are a few possible outcomes:
-
-- Your proposed change is awesome! We merge it in and it will be included in the next minor release of Umbraco
-- If the change is a high priority bug fix, we will cherry-pick it into the next patch release as well so that we can release it as soon as possible
-- Your proposed change is awesome but needs a bit more work, we’ll give you feedback on the changes we’d like to see
-- Your proposed change is awesome but.. not something we’re looking to include at this point. We’ll close your PR and the related issue (we’ll be nice about it!)
-
-## Are you still available?
-
-We understand you have other things to do and can't just drop everything to help us out.
-So if we’re asking for your help to improve the PR we’ll wait for two weeks to give you a fair chance to make changes. We’ll ask for an update if we don’t hear back from you after that time.
-
-If we don’t hear back from you for 4 weeks, we’ll close the PR so that it doesn’t just hang around forever. You’re very welcome to re-open it once you have some more time to spend on it.
-
-There will be times that we really like your proposed changes and we’ll finish the final improvements we’d like to see ourselves. You still get the credits and your commits will live on in the git repository.
\ No newline at end of file
diff --git a/.github/img/tableofcontentsicon.svg b/.github/img/tableofcontentsicon.svg
new file mode 100644
index 0000000000..a08c8742d3
--- /dev/null
+++ b/.github/img/tableofcontentsicon.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/.github/workflows/add-issues-to-review-project.yml b/.github/workflows/add-issues-to-review-project.yml
new file mode 100644
index 0000000000..4590c173fc
--- /dev/null
+++ b/.github/workflows/add-issues-to-review-project.yml
@@ -0,0 +1,53 @@
+name: Add issues to review project
+
+on:
+ issues:
+ types:
+ - opened
+
+jobs:
+ get-user-type:
+ runs-on: ubuntu-latest
+ outputs:
+ ignored: ${{ steps.set-output.outputs.ignored }}
+ steps:
+ - name: Install dependencies
+ run: |
+ npm install node-fetch@2
+ - uses: actions/github-script@v5
+ name: "Determing HQ user or not"
+ id: set-output
+ with:
+ script: |
+ const fetch = require('node-fetch');
+ const response = await fetch('https://collaboratorsv2.euwest01.umbraco.io/umbraco/api/users/IsIgnoredUser', {
+ method: 'post',
+ body: JSON.stringify('${{ github.event.issue.user.login }}'),
+ headers: {
+ 'Authorization': 'Bearer ${{ secrets.OUR_BOT_API_TOKEN }}',
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ var isIgnoredUser = true;
+ try {
+ if(response.status === 200) {
+ const data = await response.text();
+ isIgnoredUser = data === "true";
+ } else {
+ console.log("Returned data not indicate success:", response.status);
+ }
+ } catch(error) {
+ console.log(error);
+ };
+ core.setOutput("ignored", isIgnoredUser);
+ console.log("Ignored is", isIgnoredUser);
+ add-to-project:
+ if: needs.get-user-type.outputs.ignored == 'false'
+ runs-on: ubuntu-latest
+ needs: [get-user-type]
+ steps:
+ - uses: actions/add-to-project@main
+ with:
+ project-url: https://github.com/orgs/${{ github.repository_owner }}/projects/21
+ github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index add4e13c77..c686f373e1 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -14,7 +14,9 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
diff --git a/.github/workflows/issues-first-response.yml b/.github/workflows/issues-first-response.yml
new file mode 100644
index 0000000000..134a368785
--- /dev/null
+++ b/.github/workflows/issues-first-response.yml
@@ -0,0 +1,53 @@
+name: issue-first-response
+
+on:
+ issues:
+ types: [opened]
+
+jobs:
+ send-response:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+ steps:
+ - name: Install dependencies
+ run: |
+ npm install node-fetch@2
+ - name: Fetch random comment 🗣️ and add it to the issue
+ uses: actions/github-script@v6
+ with:
+ script: |
+ const fetch = require('node-fetch')
+
+ const response = await fetch('https://collaboratorsv2.euwest01.umbraco.io/umbraco/api/comments/PostComment', {
+ method: 'post',
+ body: JSON.stringify({
+ repo: '${{ github.repository }}',
+ number: '${{ github.event.number }}',
+ actor: '${{ github.actor }}',
+ commentType: 'opened-issue-first-comment'
+ }),
+ headers: {
+ 'Authorization': 'Bearer ${{ secrets.OUR_BOT_API_TOKEN }}',
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ try {
+ const data = await response.text();
+
+ if(response.status === 200 && data !== '') {
+ github.rest.issues.createComment({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ body: data
+ });
+ } else {
+ console.log("Status code did not indicate success:", response.status);
+ console.log("Returned data:", data);
+ }
+ } catch(error) {
+ console.log(error);
+ }
diff --git a/.github/workflows/pr-first-response.yml b/.github/workflows/pr-first-response.yml
index 0969a883c9..991a5d0808 100644
--- a/.github/workflows/pr-first-response.yml
+++ b/.github/workflows/pr-first-response.yml
@@ -4,12 +4,12 @@ on:
pull_request_target:
types: [opened]
-jobs:
+jobs:
send-response:
runs-on: ubuntu-latest
permissions:
issues: write
- pull-requests: write
+ pull-requests: write
steps:
- name: Install dependencies
run: |
@@ -45,8 +45,17 @@ jobs:
body: data
});
} else {
- console.log("Status code did not indicate success:", response.status);
+ console.log("Returned data not indicate success.");
+
+ if(response.status !== 200) {
+ console.log("Status code:", response.status)
+ }
+
console.log("Returned data:", data);
+
+ if(data === '') {
+ console.log("An empty response usually indicates that either no comment was found or the actor user was not eligible for getting an automated response (HQ users are not getting auto-responses).")
+ }
}
} catch(error) {
console.log(error);
diff --git a/.github/workflows/up-for-grabs-response.yml b/.github/workflows/up-for-grabs-response.yml
new file mode 100644
index 0000000000..eb16eb7779
--- /dev/null
+++ b/.github/workflows/up-for-grabs-response.yml
@@ -0,0 +1,63 @@
+name: labeled-up-for-grabs-first-comment
+
+on:
+ issues:
+ types:
+ - labeled
+
+jobs:
+ send-response:
+ if: github.event.label.name == 'community/up-for-grabs'
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+ steps:
+ - name: Install dependencies
+ run: |
+ npm install node-fetch@2
+ - name: Fetch comment 🗣️ and add it to the issue
+ uses: actions/github-script@v6
+ with:
+ script: |
+ const fetch = require('node-fetch');
+ const response = await fetch('https://collaboratorsv2.euwest01.umbraco.io/umbraco/api/comments/PostComment', {
+ method: 'post',
+ body: JSON.stringify({
+ repo: '${{ github.repository }}',
+ number: '${{ github.event.issue.number }}',
+ actor: '${{ github.event.issue.user.login }}',
+ commentType: 'labeled-up-for-grabs-first-comment'
+ }),
+ headers: {
+ 'Authorization': 'Bearer ${{ secrets.OUR_BOT_API_TOKEN }}',
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ try {
+ const data = await response.text();
+
+ if(response.status === 200 && data !== '') {
+ github.rest.issues.createComment({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ body: data
+ });
+ } else {
+ console.log("Returned data not indicate success.");
+
+ if(response.status !== 200) {
+ console.log("Status code:", response.status)
+ }
+
+ console.log("Returned data:", data);
+
+ if(data === '') {
+ console.log("An empty response usually indicates that either no comment was found or the actor user was not eligible for getting an automated response (HQ users are not getting auto-responses).")
+ }
+ }
+ } catch(error) {
+ console.log(error);
+ };
diff --git a/.gitignore b/.gitignore
index 348474c8cb..26f96f89b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,6 +54,10 @@ preserve.belle
/src/Umbraco.Web.UI.Docs/api/
/src/Umbraco.Web.UI.Docs/package-lock.json
+# csharp-docs
+/build/csharp-docs/api/
+/build/csharp-docs/_site/
+
# Build
/build.out/
/build.tmp/
@@ -64,8 +68,6 @@ preserve.belle
/build/docs.zip
/build/ui-docs.zip
/build/csharp-docs.zip
-/build/ApiDocs/
-/src/ApiDocs/api/
/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/
# Environment specific data
@@ -102,4 +104,5 @@ cypress.env.json
/tests/Umbraco.Tests.UnitTests/[Uu]mbraco/[Dd]ata/TEMP/
# Ignore auto-generated schema
-/src/Umbraco.Web.UI/[Uu]mbraco/config/appsettings-schema.json
+/src/Umbraco.Web.UI/appsettings-schema.json
+/src/Umbraco.Cms/appsettings-schema.json
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 1d4324a34d..99876bc77e 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -33,6 +33,18 @@
"$gulp-tsc"
]
},
+ {
+ "label": "Client Watch",
+ "detail": "runs npm run dev for Umbraco.Web.UI.Client",
+ "promptOnClose": true,
+ "group": "build",
+ "type": "npm",
+ "script": "dev",
+ "path": "src/Umbraco.Web.UI.Client/",
+ "problemMatcher": [
+ "$gulp-tsc"
+ ]
+ },
{
"label": "Dotnet build",
"detail": "Dotnet build of SLN",
diff --git a/Directory.Build.props b/Directory.Build.props
index fcf605f555..8ae2e0baec 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,7 +1,18 @@
-
-
-
-
+
+
+
+
+
+
+
+ all
+ 3.5.107
+
+
+
+ all
+
+
diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec
deleted file mode 100644
index f105f33cda..0000000000
--- a/build/NuSpecs/UmbracoCms.nuspec
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
- Umbraco.Cms
- 10.0.0
- Umbraco Cms
- Umbraco HQ
- Umbraco HQ
- MIT
- https://umbraco.com/
- https://umbraco.com/dist/nuget/logo-small.png
- false
- Installs Umbraco Cms in your Visual Studio ASP.NET Core project
- Installs Umbraco Cms in your Visual Studio ASP.NET Core project
- en-US
- umbraco
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml
index 9657898c0d..ae8ca25f52 100644
--- a/build/azure-pipelines.yml
+++ b/build/azure-pipelines.yml
@@ -1,721 +1,571 @@
name: $(TeamProject)_$(Build.DefinitionName)_$(SourceBranchName)_$(Date:yyyyMMdd)$(Rev:.r)
+
+parameters:
+ - name: sqlServerIntegrationTests
+ displayName: Run SQL Server Integration Tests
+ type: boolean
+ default: false
+ - name: myGetDeploy
+ displayName: Deploy to MyGet
+ type: boolean
+ default: false
+ - name: nuGetDeploy
+ displayName: Deploy to NuGet
+ type: boolean
+ default: false
+ - name: buildApiDocs
+ displayName: Build API docs
+ type: boolean
+ default: false
+ - name: uploadApiDocs
+ displayName: Upload API docs
+ type: boolean
+ default: false
+
variables:
- buildConfiguration: Release
- SA_PASSWORD: UmbracoIntegration123!
- UMBRACO__CMS_GLOBAL__ID: 00000000-0000-0000-0000-000000000042
- UmbracoBuild: AzurePipeline
- nodeVersion: 14.18.1
-resources:
- containers:
- - container: mssql
- image: 'mcr.microsoft.com/mssql/server:2017-latest'
- env:
- ACCEPT_EULA: 'Y'
- SA_PASSWORD: $(SA_PASSWORD)
- MSSQL_PID: Developer
- ports:
- - '1433:1433'
- options: '--name mssql'
+ buildConfiguration: Release
+ SA_PASSWORD: UmbracoIntegration123!
+ UMBRACO__CMS_GLOBAL__ID: 00000000-0000-0000-0000-000000000042
+ nodeVersion: 14.18.1
+ DOTNET_NOLOGO: 1
+ DOTNET_CLI_TELEMETRY_OPTOUT: 1
+
stages:
- - stage: Determine_build_type
- displayName: Determine build type
- dependsOn: [ ]
- jobs:
- - job: Set_build_variables
- displayName: Set build variables
- pool:
- vmImage: windows-latest
- steps:
- - task: PowerShell@1
- name: setReleaseVariable
- displayName: Set isRelease variable
- inputs:
- scriptType: inlineScript
- inlineScript: >
- $isRelease = [regex]::matches($env:BUILD_SOURCEBRANCH,"v\d+\/\d+.\d+.*")
+ ###############################################
+ ## Build
+ ###############################################
+ - stage: Build
+ variables:
+ npm_config_cache: $(Pipeline.Workspace)/.npm_client
+ jobs:
+ - job: A
+ displayName: Build Umbraco CMS
+ pool:
+ vmImage: 'ubuntu-latest'
+ steps:
+ - task: NodeTool@0
+ displayName: Use node $(nodeVersion)
+ inputs:
+ versionSpec: $(nodeVersion)
+ - task: Cache@2
+ displayName: Cache node_modules
+ inputs:
+ key: '"npm_client" | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Umbraco.Web.UI.Client/package-lock.json'
+ restoreKeys: |
+ "npm_client" | "$(Agent.OS)"
+ "npm_client"
+ path: $(npm_config_cache)
+ - script: npm ci --no-fund --no-audit --prefer-offline
+ workingDirectory: src/Umbraco.Web.UI.Client
+ displayName: Run npm ci
+ - task: gulp@0
+ displayName: Run gulp build
+ inputs:
+ gulpFile: src/Umbraco.Web.UI.Client/gulpfile.js
+ targets: coreBuild
+ workingDirectory: src/Umbraco.Web.UI.Client
+ - task: DotNetCoreCLI@2
+ displayName: Run dotnet build
+ inputs:
+ command: build
+ projects: umbraco.sln
+ arguments: '--configuration $(buildConfiguration)'
+ - script: |
+ version="$(Build.BuildNumber)"
+ echo "varsion: $version"
- if ($isRelease.Count -gt 0){
- Write-Host "##vso[build.addbuildtag]Release build"
- Write-Host "##vso[task.setvariable variable=isRelease;isOutput=true]true"
- }else{
- Write-Host "##vso[task.setvariable variable=isRelease;isOutput=true]false"
- }
- - stage: Unit_Tests
- displayName: Unit Tests
- dependsOn: []
- jobs:
- - job: Linux_Unit_Tests
- displayName: Linux
- pool:
- vmImage: ubuntu-latest
- steps:
- - task: UseDotNet@2
- displayName: Use .Net 6.x
- inputs:
- version: 6.x
- - task: DotNetCoreCLI@2
- displayName: dotnet build
- inputs:
- command: build
- projects: '**/umbraco.sln'
- - task: DotNetCoreCLI@2
- displayName: dotnet test
- inputs:
- command: test
- projects: '**/*.Tests.UnitTests.csproj'
- arguments: '--no-build'
- - job: MacOS_Unit_Tests
- displayName: Mac OS
- pool:
- vmImage: macOS-latest
- steps:
- - task: UseDotNet@2
- displayName: Use .Net 6.x
- inputs:
- version: 6.x
- - task: DotNetCoreCLI@2
- displayName: dotnet build
- inputs:
- command: build
- projects: '**/umbraco.sln'
- - task: DotNetCoreCLI@2
- displayName: dotnet test
- inputs:
- command: test
- projects: '**/*.Tests.UnitTests.csproj'
- arguments: '--no-build'
- - job: Windows_Unit_Tests
- displayName: Windows
- pool:
- vmImage: windows-latest
- steps:
- - task: UseDotNet@2
- displayName: Use .Net 6.x
- inputs:
- version: 6.x
- - task: DotNetCoreCLI@2
- displayName: dotnet build
- inputs:
- command: build
- projects: '**/umbraco.sln'
- - task: DotNetCoreCLI@2
- displayName: dotnet test
- inputs:
- command: test
- projects: '**/*.Tests.UnitTests.csproj'
- arguments: '--no-build'
+ major="$(echo $version | cut -d '.' -f 1)"
+ echo "major version: $major"
- - stage: Integration_Tests
- displayName: Integration Tests
- dependsOn: []
- jobs:
+ echo "##vso[task.setvariable variable=majorVersion;isOutput=true]$major"
+ displayName: Set major version
+ name: determineMajorVersion
+ - task: PowerShell@2
+ displayName: Prepare nupkg
+ inputs:
+ targetType: inline
+ script: |
+ $umbracoVersion = "$(Build.BuildNumber)" -replace "\+",".g"
+ $templatePaths = Get-ChildItem 'templates/**/.template.config/template.json'
- - job: Linux_Integration_Tests_SQLite
- timeoutInMinutes: 120
- displayName: Linux (SQLite)
- pool:
- vmImage: ubuntu-latest
- steps:
- - task: UseDotNet@2
- displayName: Use .Net 6.x
- inputs:
- version: 6.x
- - task: DotNetCoreCLI@2
- displayName: dotnet build
- inputs:
- command: build
- projects: '**/umbraco.sln'
- - task: DotNetCoreCLI@2
- displayName: dotnet test
- inputs:
- command: test
- projects: '**/Umbraco.Tests.Integration.csproj'
- arguments: '--no-build'
- env:
- Tests__Database__DatabaseType: 'Sqlite'
- Umbraco__Cms__global__MainDomLock: 'FileSystemMainDomLock'
+ foreach ($templatePath in $templatePaths) {
+ $a = Get-Content $templatePath -Raw | ConvertFrom-Json
+ if ($a.symbols -and $a.symbols.UmbracoVersion) {
+ $a.symbols.UmbracoVersion.defaultValue = $umbracoVersion
+ $a | ConvertTo-Json -Depth 32 | Set-Content $templatePath
+ }
+ }
- - job: Linux_Integration_Tests_SQLServer
- services:
- mssql: mssql
- timeoutInMinutes: 120
- displayName: Linux (SQL Server)
- pool:
- vmImage: ubuntu-latest
- steps:
- - task: UseDotNet@2
- displayName: Use .Net 6.x
- inputs:
- version: 6.x
- - task: DotNetCoreCLI@2
- displayName: dotnet build
- inputs:
- command: build
- projects: '**/umbraco.sln'
- - task: DotNetCoreCLI@2
- displayName: dotnet test
- inputs:
- command: test
- projects: '**/Umbraco.Tests.Integration.csproj'
- arguments: '--no-build'
- env:
- Tests__Database__DatabaseType: 'SqlServer'
- Tests__Database__SQLServerMasterConnectionString: 'Server=localhost,1433;User Id=sa;Password=$(SA_PASSWORD);'
- Umbraco__Cms__global__MainDomLock: 'SqlMainDomLock'
+ dotnet pack --configuration $(buildConfiguration) umbraco.sln -o $(Build.ArtifactStagingDirectory)/nupkg
+ - script: |
+ sha="$(Build.SourceVersion)"
+ sha=${sha:0:7}
+ buildnumber="$(Build.BuildNumber)_$(Build.BuildId)_$sha"
+ echo "##vso[build.updatebuildnumber]$buildnumber"
+ displayName: Update build number
+ - task: PublishPipelineArtifact@1
+ displayName: Publish nupkg
+ inputs:
+ targetPath: $(Build.ArtifactStagingDirectory)/nupkg
+ artifactName: nupkg
+ - task: PublishPipelineArtifact@1
+ displayName: Publish build artifacts
+ inputs:
+ targetPath: $(Build.SourcesDirectory)
+ artifactName: build_output
- - job: macOS_Integration_Tests_SQLite
- timeoutInMinutes: 120
- displayName: macOS (SQLite)
- pool:
- vmImage: macOS-latest
- steps:
- - task: UseDotNet@2
- displayName: Use .Net 6.x
- inputs:
- version: 6.x
- - task: DotNetCoreCLI@2
- displayName: dotnet build
- inputs:
- command: build
- projects: '**/umbraco.sln'
- - task: DotNetCoreCLI@2
- displayName: dotnet test
- inputs:
- command: test
- projects: '**/Umbraco.Tests.Integration.csproj'
- arguments: '--no-build'
- env:
- Tests__Database__DatabaseType: 'Sqlite'
- Umbraco__Cms__global__MainDomLock: 'FileSystemMainDomLock'
+ - stage: Build_Docs
+ condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.buildApiDocs}}))
+ displayName: Prepare API Documentation
+ dependsOn: Build
+ variables:
+ umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['determineMajorVersion.majorVersion'] ]
+ jobs:
+ # C# API Reference
+ - job:
+ displayName: Build C# API Reference
+ pool:
+ vmImage: 'windows-latest'
+ steps:
+ - task: PowerShell@2
+ displayName: Install DocFX
+ inputs:
+ targetType: inline
+ script: |
+ choco install docfx --version=2.59.2 -y
+ if ($lastexitcode -ne 0){
+ throw ("Error installing DocFX")
+ }
+ - task: PowerShell@2
+ displayName: Generate metadata
+ inputs:
+ targetType: inline
+ script: |
+ docfx metadata "$(Build.SourcesDirectory)/build/csharp-docs/docfx.json"
+ if ($lastexitcode -ne 0){
+ throw ("Error generating metadata.")
+ }
+ - task: PowerShell@2
+ displayName: Generate documentation
+ inputs:
+ targetType: inline
+ script: |
+ docfx build "$(Build.SourcesDirectory)/build/csharp-docs/docfx.json"
+ if ($lastexitcode -ne 0){
+ throw ("Error generating documentation.")
+ }
+ - task: ArchiveFiles@2
+ displayName: Archive C# Docs
+ inputs:
+ rootFolderOrFile: $(Build.SourcesDirectory)/build/csharp-docs/_site
+ includeRootFolder: false
+ archiveFile: $(Build.ArtifactStagingDirectory)/csharp-docs.zip
+ - task: PublishPipelineArtifact@1
+ displayName: Publish C# Docs
+ inputs:
+ targetPath: $(Build.ArtifactStagingDirectory)/csharp-docs.zip
+ artifact: csharp-docs
- - job: Windows_Integration_Tests_LocalDb
- timeoutInMinutes: 120
- displayName: Windows (LocalDb)
- pool:
- vmImage: windows-latest
- steps:
- - task: UseDotNet@2
- displayName: Use .Net 6.x
- inputs:
- version: 6.x
- - powershell: sqllocaldb start mssqllocaldb
- displayName: Start MSSQL LocalDb
- - task: DotNetCoreCLI@2
- displayName: dotnet build
- inputs:
- command: build
- projects: '**/umbraco.sln'
- - task: DotNetCoreCLI@2
- displayName: dotnet test
- inputs:
- command: test
- projects: '**/Umbraco.Tests.Integration.csproj'
- arguments: '--no-build'
- env:
- Tests__Database__DatabaseType: 'LocalDb'
- Umbraco__Cms__global__MainDomLock: 'MainDomSemaphoreLock'
+ # js API Reference
+ - job:
+ displayName: Build js API Reference
+ pool:
+ vmImage: 'ubuntu-latest'
+ steps:
+ - task: NodeTool@0
+ displayName: Use Node 10.15.0
+ inputs:
+ versionSpec: 10.15.0 # Won't work with 14.18.1
+ - script: |
+ npm ci --no-fund --no-audit --prefer-offline
+ npx gulp docs
- - stage: Acceptance_Tests
- displayName: Acceptance Tests
- dependsOn: []
- variables:
- - name: Umbraco__CMS__Unattended__InstallUnattended
+ major="$(umbracoMajorVersion)"
+ echo "major version: $major"
+
+ baseUrl="https://apidocs.umbraco.com/v$major/ui/"
+ echo "baseUrl: $baseUrl"
+
+ sed -i "s|baseUrl = .*|baseUrl = '$baseUrl',|" api/index.html
+ displayName: Generate js Docs
+ workingDirectory: $(Build.SourcesDirectory)/src/Umbraco.Web.UI.Docs
+ - task: ArchiveFiles@2
+ displayName: Archive js Docs
+ inputs:
+ rootFolderOrFile: $(Build.SourcesDirectory)/src/Umbraco.Web.UI.Docs/api
+ includeRootFolder: false
+ archiveFile: $(Build.ArtifactStagingDirectory)/ui-docs.zip
+ - task: PublishPipelineArtifact@1
+ displayName: Publish js Docs
+ inputs:
+ targetPath: $(Build.ArtifactStagingDirectory)/ui-docs.zip
+ artifact: ui-docs
+
+ ###############################################
+ ## Test
+ ###############################################
+ - stage: Unit
+ displayName: Unit Tests
+ dependsOn: Build
+ jobs:
+ # Unit Tests
+ - job:
+ displayName: Unit Tests
+ strategy:
+ matrix:
+ Windows:
+ vmImage: 'windows-latest'
+ Linux:
+ vmImage: 'ubuntu-latest'
+ macOS:
+ vmImage: 'macOS-latest'
+ pool:
+ vmImage: $(vmImage)
+ steps:
+ - task: DownloadPipelineArtifact@2
+ displayName: Download build artifacts
+ inputs:
+ artifact: build_output
+ path: $(Build.SourcesDirectory)
+ - task: UseDotNet@2
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Darwin')) # net6 already on the other images
+ displayName: Use net6
+ inputs:
+ version: 6.x
+ - task: DotNetCoreCLI@2
+ displayName: Run dotnet test
+ inputs:
+ command: test
+ projects: '**/*.Tests.UnitTests.csproj'
+ arguments: '--no-build --configuration $(buildConfiguration)'
+ testRunTitle: Unit Tests - $(Agent.OS)
+
+ - stage: Integration
+ displayName: Integration Tests
+ dependsOn: Build
+ jobs:
+ # Integration Tests (SQLite)
+ - job:
+ displayName: Integration Tests (SQLite)
+ strategy:
+ matrix:
+ Windows:
+ vmImage: 'windows-latest'
+ Linux:
+ vmImage: 'ubuntu-latest'
+ macOS:
+ vmImage: 'macOS-latest'
+ pool:
+ vmImage: $(vmImage)
+ steps:
+ - task: DownloadPipelineArtifact@2
+ displayName: Download build artifacts
+ inputs:
+ artifact: build_output
+ path: $(Build.SourcesDirectory)
+ - task: UseDotNet@2
+ displayName: Use net6
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Darwin')) # net6 already on the other images
+ inputs:
+ version: 6.x
+ - task: DotNetCoreCLI@2
+ displayName: Run dotnet test
+ inputs:
+ command: test
+ projects: '**/*.Tests.Integration.csproj'
+ arguments: '--no-build --configuration $(buildConfiguration)'
+ testRunTitle: Integration Tests SQLite - $(Agent.OS)
+ env:
+ Tests__Database__DatabaseType: 'Sqlite'
+ Umbraco__Cms__global__MainDomLock: 'FileSystemMainDomLock'
+
+ # Integration Tests (SQL Server)
+ - job:
+ timeoutInMinutes: 120
+ condition: or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.sqlServerIntegrationTests}})
+ displayName: Integration Tests (SQL Server)
+ strategy:
+ matrix:
+ Windows:
+ vmImage: 'windows-latest'
+ testDb: LocalDb
+ connectionString: N/A
+ Linux:
+ vmImage: 'ubuntu-latest'
+ testDb: SqlServer
+ connectionString: 'Server=localhost,1433;User Id=sa;Password=$(SA_PASSWORD);'
+ pool:
+ vmImage: $(vmImage)
+ steps:
+ - task: DownloadPipelineArtifact@2
+ displayName: Download build artifacts
+ inputs:
+ artifact: build_output
+ path: $(Build.SourcesDirectory)
+ - powershell: sqllocaldb start mssqllocaldb
+ displayName: Start localdb (Windows only)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
+ - powershell: docker run --name mssql -d -p 1433:1433 -e ACCEPT_EULA=Y -e SA_PASSWORD=$(SA_PASSWORD) -e MSSQL_PID=Developer mcr.microsoft.com/mssql/server:2019-latest
+ displayName: Start SQL Server (Linux only)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
+ - task: DotNetCoreCLI@2
+ displayName: Run dotnet test
+ inputs:
+ command: test
+ projects: '**/*.Tests.Integration.csproj'
+ arguments: '--no-build --configuration $(buildConfiguration)'
+ testRunTitle: Integration Tests SQL Server - $(Agent.OS)
+ env:
+ Tests__Database__DatabaseType: $(testDb)
+ Tests__Database__SQLServerMasterConnectionString: $(connectionString)
+ Umbraco__Cms__global__MainDomLock: 'SqlMainDomLock'
+
+ - stage: E2E
+ variables:
+ npm_config_cache: $(Pipeline.Workspace)/.npm_e2e
+ CYPRESS_CACHE_FOLDER: $(Pipeline.Workspace)/cypress_binaries
+ displayName: E2E Tests
+ dependsOn: Build
+ jobs:
+ # E2E Tests
+ - job:
+ displayName: E2E Tests
+ variables:
+ - name: Umbraco__CMS__Unattended__InstallUnattended # Windows only
value: true
- - name: Umbraco__CMS__Unattended__UnattendedUserName
+ - name: Umbraco__CMS__Unattended__UnattendedUserName # Windows only
value: Cypress Test
- - name: Umbraco__CMS__Unattended__UnattendedUserEmail
+ - name: Umbraco__CMS__Unattended__UnattendedUserEmail # Windows only
value: cypress@umbraco.com
- - name: Umbraco__CMS__Unattended__UnattendedUserPassword
+ - name: Umbraco__CMS__Unattended__UnattendedUserPassword # Windows only
value: UmbracoAcceptance123!
- - name: Umbraco__CMS__Global__InstallMissingDatabase
+ - name: Umbraco__CMS__Global__InstallMissingDatabase # Windows only
value: true
- jobs:
- - job: Windows_Acceptance_tests
- variables:
- - name: UmbracoDatabaseServer
- value: (LocalDB)\MSSQLLocalDB
- - name: UmbracoDatabaseName
- value: Cypress
- - name: ConnectionStrings__umbracoDbDSN
- value: Server=$(UmbracoDatabaseServer);Database=$(UmbracoDatabaseName);Integrated Security=true;
- displayName: Windows
- pool:
- vmImage: windows-latest
- steps:
- - task: UseDotNet@2
- displayName: Use .Net 6.x
- inputs:
- version: 6.x
+ - name: UmbracoDatabaseServer # Windows only
+ value: (LocalDB)\MSSQLLocalDB
+ - name: UmbracoDatabaseName # Windows only
+ value: Cypress
+ - name: ConnectionStrings__umbracoDbDSN # Windows only
+ value: Server=$(UmbracoDatabaseServer);Database=$(UmbracoDatabaseName);Integrated Security=true;
+ - name: CYPRESS_BASE_URL
+ value: http://localhost:8080
+ strategy:
+ matrix:
+ Linux:
+ vmImage: 'ubuntu-latest'
+ dockerfile: umbraco-linux.docker
+ dockerImageName: umbraco-linux
+ Windows:
+ vmImage: 'windows-latest'
+ pool:
+ vmImage: $(vmImage)
+ steps:
+ - task: DownloadPipelineArtifact@2
+ displayName: Download nupkg
+ inputs:
+ artifact: nupkg
+ path: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/misc/nupkg
+ - task: NodeTool@0
+ displayName: Use Node $(nodeVersion)
+ inputs:
+ versionSpec: $(nodeVersion)
+ - task: Cache@2
+ displayName: Cache node_modules
+ inputs:
+ key: '"npm_e2e" | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
+ restoreKeys: |
+ "npm_e2e" | "$(Agent.OS)"
+ "npm_e2e"
+ path: $(npm_config_cache)
+ - task: Cache@2
+ displayName: Cache cypress binaries
+ inputs:
+ key: '"cypress_binaries" | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
+ path: $(CYPRESS_CACHE_FOLDER)
+ - task: PowerShell@2
+ displayName: Generate Cypress.env.json
+ inputs:
+ targetType: inline
+ script: >
+ @{ username = "$(Umbraco__CMS__Unattended__UnattendedUserEmail)"; password = "$(Umbraco__CMS__Unattended__UnattendedUserPassword)" } | ConvertTo-Json | Set-Content -Path "tests/Umbraco.Tests.AcceptanceTest/cypress.env.json"#
+ - script: npm ci --no-fund --no-audit --prefer-offline
+ workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/
+ displayName: Run npm ci
+ - powershell: sqllocaldb start mssqllocaldb
+ displayName: Start localdb (Windows only)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
+ - powershell: Invoke-Sqlcmd -Query "CREATE DATABASE $env:UmbracoDatabaseName" -ServerInstance $env:UmbracoDatabaseServer
+ displayName: Create database (Windows only)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
+ # Linux containers smooth
+ - task: PowerShell@2
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
+ displayName: Build & run container (Linux only)
+ inputs:
+ workingDirectory: tests/Umbraco.Tests.AcceptanceTest/misc
+ targetType: inline
+ script: |
+ $sha = 'g$(Build.SourceVersion)'.substring(0, 8)
+ docker build -t $(dockerImageName):$sha -f $(dockerfile) .
+ mkdir -p $(Build.ArtifactStagingDirectory)/docker-images
+ docker save -o $(Build.ArtifactStagingDirectory)/docker-images/$(dockerImageName).$sha.tar $(dockerImageName):$sha
+ docker run --name $(dockerImageName) -dp 8080:5000 -e UMBRACO__CMS_GLOBAL__ID=$(UMBRACO__CMS_GLOBAL__ID) $(dockerImageName):$sha
+ docker ps
+ # Windows containers take forever.
+ # --no-launch-profile stops ASPNETCORE_ENVIRONMENT=Development which breaks the users.ts tests (smtp config = invite user button)
+ # Urls matching docker setup.
+ - task: PowerShell@2
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
+ displayName: Build & run app (Windows only)
+ inputs:
+ workingDirectory: tests/Umbraco.Tests.AcceptanceTest/misc
+ targetType: inline
+ script: |
+ dotnet new --install ./nupkg/Umbraco.Templates.*.nupkg
+ dotnet new umbraco --name Cypress -o . --no-restore
+ dotnet restore --configfile ./nuget.config
+ dotnet build --no-restore -c Release
+ Start-Process -FilePath "dotnet" -ArgumentList "run --no-build -c Release --no-launch-profile --urls $(CYPRESS_BASE_URL)"
+ - task: PowerShell@2
+ displayName: Wait for app
+ inputs:
+ targetType: inline
+ workingDirectory: tests/Umbraco.Tests.AcceptanceTest
+ script: |
+ npm i -g wait-on
+ wait-on -v --interval 1000 --timeout 120000 $(CYPRESS_BASE_URL)
+ - task: PowerShell@2
+ displayName: Run Cypress (Desktop)
+ continueOnError: true
+ inputs:
+ targetType: inline
+ workingDirectory: tests/Umbraco.Tests.AcceptanceTest
+ script: 'npm run test -- --reporter junit --reporter-options "mochaFile=results/test-output-D-[hash].xml,toConsole=true" --config="viewportHeight=1600,viewportWidth=2560,screenshotsFolder=cypress/artifacts/desktop/screenshots,videosFolder=cypress/artifacts/desktop/videos,videoUploadOnPasses=false"'
+ - task: PublishTestResults@2
+ displayName: Publish test results
+ condition: always()
+ inputs:
+ testResultsFormat: 'JUnit'
+ testResultsFiles: 'tests/Umbraco.Tests.AcceptanceTest/results/test-output-D-*.xml'
+ mergeTestResults: true
+ testRunTitle: "e2e - $(Agent.OS)"
+ - task: CopyFiles@2
+ displayName: Prepare artifacts
+ condition: always()
+ inputs:
+ sourceFolder: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/cypress/artifacts
+ targetFolder: $(Build.ArtifactStagingDirectory)/cypresss
+ - task: PublishPipelineArtifact@1
+ displayName: "Publish test artifacts"
+ condition: always()
+ inputs:
+ targetPath: $(Build.ArtifactStagingDirectory)
+ artifact: 'E2E artifacts - $(Agent.OS) - Attempt #$(System.JobAttempt)'
- - powershell: sqllocaldb start mssqllocaldb
- displayName: Start MSSQL LocalDb
- - powershell: Invoke-Sqlcmd -Query "CREATE DATABASE $env:UmbracoDatabaseName" -ServerInstance $env:UmbracoDatabaseServer
- displayName: Create database
-# - task: DotNetCoreCLI@2
-# displayName: dotnet build
-# inputs:
-# command: build
-# projects: '**/Umbraco.Web.UI.csproj'
- - task: NodeTool@0
- displayName: Use Node $(nodeVersion)
- inputs:
- versionSpec: $(nodeVersion)
- - task: Npm@1
- displayName: npm ci (Client)
- inputs:
- command: ci
- workingDir: src\Umbraco.Web.UI.Client
- verbose: false
- - task: gulp@0
- displayName: gulp build
- inputs:
- gulpFile: src\Umbraco.Web.UI.Client\gulpfile.js
- targets: build
- workingDirectory: src\Umbraco.Web.UI.Client
- - powershell: Start-Process -FilePath "dotnet" -ArgumentList "run", "--project", "src\Umbraco.Web.UI\Umbraco.Web.UI.csproj"
- displayName: dotnet run
- - task: PowerShell@1
- displayName: Generate Cypress.env.json
- inputs:
- scriptType: inlineScript
- inlineScript: >
- @{ username = $env:Umbraco__CMS__Unattended__UnattendedUserEmail; password = $env:Umbraco__CMS__Unattended__UnattendedUserPassword } | ConvertTo-Json | Set-Content -Path "tests\Umbraco.Tests.AcceptanceTest\cypress.env.json"
- - task: Npm@1
- name: PrepareTask
- displayName: npm ci (AcceptanceTest)
- inputs:
- command: ci
- workingDir: 'tests\Umbraco.Tests.AcceptanceTest'
- - task: PowerShell@2
- displayName: Run Cypress (Desktop)
- condition: always()
- continueOnError: true
- inputs:
- targetType: inline
- workingDirectory: tests\Umbraco.Tests.AcceptanceTest
- script: 'npm run test -- --reporter junit --reporter-options "mochaFile=results/test-output-D-[hash].xml,toConsole=true" --config="viewportHeight=1600,viewportWidth=2560,screenshotsFolder=cypress/artifacts/desktop/screenshots,videosFolder=cypress/artifacts/desktop/videos,videoUploadOnPasses=false"'
+ ###############################################
+ ## Release
+ ###############################################
+ - stage: Deploy_MyGet
+ displayName: MyGet pre-release
+ dependsOn:
+ - Unit
+ - Integration
+ # - E2E # TODO: Enable when stable.
+ condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.myGetDeploy}}))
+ jobs:
+ - job:
+ displayName: Push to pre-release feed
+ steps:
+ - checkout: none
+ - task: DownloadPipelineArtifact@2
+ displayName: Download nupkg
+ inputs:
+ artifact: nupkg
+ path: $(Build.ArtifactStagingDirectory)/nupkg
+ - task: NuGetCommand@2
+ displayName: Nuget push
+ inputs:
+ command: 'push'
+ packagesToPush: $(Build.ArtifactStagingDirectory)/**/*.nupkg
+ nuGetFeedType: 'external'
+ publishFeedCredentials: 'MyGet - Pre-releases'
- - task: PublishTestResults@2
- condition: always()
- inputs:
- testResultsFormat: 'JUnit'
- testResultsFiles: 'tests/Umbraco.Tests.AcceptanceTest/results/test-output-D-*.xml'
- mergeTestResults: true
- testRunTitle: "Test results Desktop"
-# - task: Npm@1
-# displayName: Run Cypress (Tablet portrait)
-# condition: always()
-# inputs:
-# workingDir: tests\Umbraco.Tests.AcceptanceTest
-# command: 'custom'
-# customCommand: 'run test -- --config="viewportHeight=1366,viewportWidth=1024,screenshotsFolder=cypress/artifacts/tablet/screenshots,videosFolder=cypress/artifacts/tablet/videos,videoUploadOnPasses=false"'
-#
-# - task: Npm@1
-# displayName: Run Cypress (Mobile protrait)
-# condition: always()
-# inputs:
-# workingDir: tests\Umbraco.Tests.AcceptanceTest
-# command: 'custom'
-# customCommand: 'run test -- --config="viewportHeight=812,viewportWidth=375,screenshotsFolder=cypress/artifacts/mobile/screenshots,videosFolder=cypress/artifacts/mobile/videos,videoUploadOnPasses=false"'
- - task: PublishPipelineArtifact@1
- displayName: "Publish test artifacts"
- inputs:
- targetPath: '$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/cypress/artifacts'
- artifact: 'Test artifacts - Windows - Attempt #$(System.JobAttempt)'
+ - stage: Deploy_NuGet
+ displayName: NuGet release
+ dependsOn:
+ - Deploy_MyGet
+ - Build_Docs
+ condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.nuGetDeploy}}))
+ jobs:
+ - job:
+ displayName: Push to NuGet
+ steps:
+ - checkout: none
+ - task: DownloadPipelineArtifact@2
+ displayName: Download nupkg
+ inputs:
+ artifact: nupkg
+ path: $(Build.ArtifactStagingDirectory)/nupkg
+ - task: NuGetCommand@2
+ displayName: Nuget push
+ inputs:
+ command: 'push'
+ packagesToPush: $(Build.ArtifactStagingDirectory)/**/*.nupkg
+ nuGetFeedType: 'external'
+ publishFeedCredentials: 'NuGet - Umbraco.*'
- - job: Linux_Acceptance_tests_SqlServer
- displayName: Linux (SQL Server)
- variables:
- - name: UmbracoDatabaseServer
- value: localhost
- - name: UmbracoDatabaseName
- value: Cypress
- - name: ConnectionStrings__umbracoDbDSN
- value: Server=localhost,1433;Database=$(UmbracoDatabaseName);User Id=sa;Password=$(SA_PASSWORD);
- services:
- mssql: mssql
- pool:
- vmImage: ubuntu-latest
- steps:
- - task: UseDotNet@2
- displayName: Use .Net 6.x
- inputs:
- version: 6.x
- - task: Bash@3
- displayName: Create database
- inputs:
- targetType: 'inline'
- script: 'sqlcmd -S . -U sa -P $SA_PASSWORD -Q "CREATE DATABASE $DBNAME"'
- env:
- DBNAME: $(UmbracoDatabaseName)
- SA_PASSWORD: $(SA_PASSWORD)
- - task: NodeTool@0
- displayName: Use Node $(nodeVersion)
- inputs:
- versionSpec: $(nodeVersion)
- - task: Npm@1
- displayName: npm ci (Client)
- inputs:
- command: ci
- workingDir: src/Umbraco.Web.UI.Client
- verbose: false
- - task: gulp@0
- displayName: gulp build
- inputs:
- gulpFile: src/Umbraco.Web.UI.Client/gulpfile.js
- targets: build
- workingDirectory: src/Umbraco.Web.UI.Client
- - task: DotNetCoreCLI@2
- displayName: dotnet build
- inputs:
- command: build
- projects: src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
- - task: Bash@3
- displayName: dotnet run
- inputs:
- targetType: 'inline'
- script: 'nohup dotnet run --no-build --project ./src/Umbraco.Web.UI/ > $(Build.ArtifactStagingDirectory)/dotnet_run_log_linux.txt &'
- - task: Bash@3
- displayName: Generate Cypress.env.json
- inputs:
- targetType: 'inline'
- script: 'echo "{ \"username\": \"$USERNAME\", \"password\": \"$PASSWORD\" }" > "tests/Umbraco.Tests.AcceptanceTest/cypress.env.json"'
- env:
- USERNAME: $(Umbraco__CMS__Unattended__UnattendedUserEmail)
- PASSWORD: $(Umbraco__CMS__Unattended__UnattendedUserPassword)
- - task: Npm@1
- name: PrepareTask
- displayName: npm ci (AcceptanceTest)
- inputs:
- command: ci
- workingDir: 'tests/Umbraco.Tests.AcceptanceTest'
- - task: Bash@3
- displayName: Run Cypress (Desktop)
- condition: always()
- continueOnError: true
- inputs:
- targetType: inline
- workingDirectory: tests/Umbraco.Tests.AcceptanceTest
- script: 'npm run test -- --reporter junit --reporter-options "mochaFile=results/test-output-D-[hash].xml,toConsole=true" --config="viewportHeight=1600,viewportWidth=2560,screenshotsFolder=cypress/artifacts/desktop/screenshots,videosFolder=cypress/artifacts/desktop/videos,videoUploadOnPasses=false"'
- - task: PublishTestResults@2
- condition: always()
- inputs:
- testResultsFormat: 'JUnit'
- testResultsFiles: 'tests/Umbraco.Tests.AcceptanceTest/results/test-output-D-*.xml'
- mergeTestResults: true
- testRunTitle: "Test results Desktop"
- # - task: Npm@1
- # displayName: Run Cypress (Tablet portrait)
- # condition: always()
- # inputs:
- # workingDir: tests/Umbraco.Tests.AcceptanceTest
- # command: 'custom'
- # customCommand: 'run test -- --config="viewportHeight=1366,viewportWidth=1024,screenshotsFolder=cypress/artifacts/tablet/screenshots,videosFolder=cypress/artifacts/tablet/videos,videoUploadOnPasses=false"'
- #
- # - task: Npm@1
- # displayName: Run Cypress (Mobile protrait)
- # condition: always()
- # inputs:
- # workingDir: tests/Umbraco.Tests.AcceptanceTest
- # command: 'custom'
- # customCommand: 'run test -- --config="viewportHeight=812,viewportWidth=375,screenshotsFolder=cypress/artifacts/mobile/screenshots,videosFolder=cypress/artifacts/mobile/videos,videoUploadOnPasses=false"'
- - task: PublishPipelineArtifact@1
- displayName: "Publish test artifacts"
- inputs:
- targetPath: '$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/cypress/artifacts'
- artifact: 'Test artifacts - Linux (SQL Server) - Attempt #$(System.JobAttempt)'
- - task: PublishPipelineArtifact@1
- displayName: "Publish run log"
- inputs:
- targetPath: '$(Build.ArtifactStagingDirectory)/dotnet_run_log_linux.txt'
- artifact: 'Test Run logs - Linux (SQL Server) - Attempt #$(System.JobAttempt)'
-
- - job: Linux_Acceptance_tests_SQLite
- displayName: Linux (SQLite)
- variables:
- - name: ConnectionStrings__umbracoDbDSN
- value: Data Source=|DataDirectory|/umbraco-cms-cypress.sqlite.db;Cache=Private;Foreign Keys=True
- - name: ConnectionStrings__umbracoDbDSN_ProviderName
- value: Microsoft.Data.SQLite
- pool:
- vmImage: ubuntu-latest
- steps:
- - task: UseDotNet@2
- displayName: Use .Net 6.x
- inputs:
- version: 6.x
- - task: NodeTool@0
- displayName: Use Node $(nodeVersion)
- inputs:
- versionSpec: $(nodeVersion)
- - task: Npm@1
- displayName: npm ci (Client)
- inputs:
- command: ci
- workingDir: src/Umbraco.Web.UI.Client
- verbose: false
- - task: gulp@0
- displayName: gulp build
- inputs:
- gulpFile: src/Umbraco.Web.UI.Client/gulpfile.js
- targets: build
- workingDirectory: src/Umbraco.Web.UI.Client
- - task: DotNetCoreCLI@2
- displayName: dotnet build
- inputs:
- command: build
- projects: src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
- - task: Bash@3
- displayName: dotnet run
- inputs:
- targetType: 'inline'
- script: 'nohup dotnet run --no-build -p ./src/Umbraco.Web.UI/ > $(Build.ArtifactStagingDirectory)/dotnet_run_log_linux.txt &'
- - task: Bash@3
- displayName: Generate Cypress.env.json
- inputs:
- targetType: 'inline'
- script: 'echo "{ \"username\": \"$USERNAME\", \"password\": \"$PASSWORD\" }" > "tests/Umbraco.Tests.AcceptanceTest/cypress.env.json"'
- env:
- USERNAME: $(Umbraco__CMS__Unattended__UnattendedUserEmail)
- PASSWORD: $(Umbraco__CMS__Unattended__UnattendedUserPassword)
- - task: Npm@1
- name: PrepareTask
- displayName: npm ci (AcceptanceTest)
- inputs:
- command: ci
- workingDir: 'tests/Umbraco.Tests.AcceptanceTest'
- - task: Bash@3
- displayName: Run Cypress (Desktop)
- condition: always()
- continueOnError: true
- inputs:
- targetType: inline
- workingDirectory: tests/Umbraco.Tests.AcceptanceTest
- script: 'npm run test -- --reporter junit --reporter-options "mochaFile=results/test-output-D-[hash].xml,toConsole=true" --config="viewportHeight=1600,viewportWidth=2560,screenshotsFolder=cypress/artifacts/desktop/screenshots,videosFolder=cypress/artifacts/desktop/videos,videoUploadOnPasses=false"'
- - task: PublishTestResults@2
- condition: always()
- inputs:
- testResultsFormat: 'JUnit'
- testResultsFiles: 'tests/Umbraco.Tests.AcceptanceTest/results/test-output-D-*.xml'
- mergeTestResults: true
- testRunTitle: "Test results Desktop"
-
- - task: PublishPipelineArtifact@1
- displayName: "Publish test artifacts"
- inputs:
- targetPath: '$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/cypress/artifacts'
- artifact: 'Test artifacts - Linux (SQLite) - Attempt #$(System.JobAttempt)'
- - task: PublishPipelineArtifact@1
- displayName: "Publish run log"
- inputs:
- targetPath: '$(Build.ArtifactStagingDirectory)/dotnet_run_log_linux.txt'
- artifact: 'Test Run logs - Linux (SQLite) - Attempt #$(System.JobAttempt)'
-
- - stage: Artifacts
- dependsOn: []
- jobs:
- - job: Build_Artifacts
- displayName: Build Artifacts
- pool:
- vmImage: windows-latest
- steps:
- - task: UseDotNet@2
- displayName: Use .Net 6.x
- inputs:
- version: 6.x
- - task: NuGetToolInstaller@1
- displayName: Use NuGet Latest
- - task: NuGetCommand@2
- displayName: Restore NuGet Packages
- inputs:
- restoreSolution: 'umbraco.sln'
- feedsToUse: config
- - task: PowerShell@1
- displayName: Update Version and Artifact Name
- inputs:
- scriptType: inlineScript
- inlineScript: >
- Write-Host "Working folder: $pwd"
-
- $ubuild = build/build.ps1 -get -continue
-
- $version = $ubuild.GetUmbracoVersion()
-
- $isRelease = [regex]::matches($env:BUILD_SOURCEBRANCH,"v\d+\/\d+.\d+.*")
-
- if ($isRelease.Count -gt 0) {
- $continuous = $version.Semver
- } else {
- $date = (Get-Date).ToString("yyyyMMdd")
- $continuous = "$($version.release)-preview$date.$(Build.BuildId)"
- $ubuild.SetUmbracoVersion($continuous)
-
- # Update the default Umbraco version in templates
- $templatePaths = Get-ChildItem 'templates/**/.template.config/template.json'
- foreach ($templatePath in $templatePaths) {
- $a = Get-Content $templatePath -Raw | ConvertFrom-Json
- if ($a.symbols -and $a.symbols.UmbracoVersion) {
- $a.symbols.UmbracoVersion.defaultValue = $continuous
- $a | ConvertTo-Json -Depth 32 | Set-Content $templatePath
- }
- }
- }
-
- Write-Host "##vso[build.updatebuildnumber]$continuous.$(Build.BuildId)"
-
- Write-Host "Building: $continuous"
- - task: PowerShell@1
- displayName: Prepare Build
- inputs:
- scriptType: inlineScript
- inlineScript: |
- Write-Host "Working folder: $pwd"
- $ubuild = build\build.ps1 -get
-
- $ubuild.PrepareBuild("vso")
- - task: PowerShell@1
- displayName: Prepare JSON Schema
- inputs:
- scriptType: inlineScript
- inlineScript: |
- Write-Host "Working folder: $pwd"
- $ubuild = build\build.ps1 -get -continue
-
- $ubuild.CompileJsonSchema()
- - task: NodeTool@0
- displayName: Use Node $(nodeVersion)
- inputs:
- versionSpec: $(nodeVersion)
- - task: Npm@1
- displayName: npm ci (Client)
- inputs:
- command: ci
- workingDir: src\Umbraco.Web.UI.Client
- verbose: false
- - task: gulp@0
- displayName: gulp build
- inputs:
- gulpFile: src\Umbraco.Web.UI.Client\gulpfile.js
- targets: build
- workingDirectory: src\Umbraco.Web.UI.Client
- publishJUnitResults: true
- testResultsFiles: '**\TESTS-*.xml'
- - task: PowerShell@1
- displayName: Prepare Packages
- inputs:
- scriptType: inlineScript
- inlineScript: |
- Write-Host "Working folder: $pwd"
- $ubuild = build\build.ps1 -get -continue
-
- $ubuild.CompileUmbraco()
- $ubuild.PreparePackages()
- - task: PowerShell@1
- displayName: Verify & Package NuGet
- inputs:
- scriptType: inlineScript
- inlineScript: |
- Write-Host "Working folder: $pwd"
- $ubuild = build\build.ps1 -get -continue
-
- $ubuild.VerifyNuGet()
- $ubuild.PackageNuGet()
- - task: CopyFiles@2
- displayName: Copy NuPkg Files to Staging
- inputs:
- SourceFolder: build.out
- Contents: '*.*nupkg'
- TargetFolder: $(build.artifactstagingdirectory)
- CleanTargetFolder: true
- - task: PublishBuildArtifacts@1
- displayName: Publish NuPkg Files
- inputs:
- PathtoPublish: $(build.artifactstagingdirectory)
- ArtifactName: nupkg
- - task: CopyFiles@2
- displayName: Copy Log Files to Staging
- inputs:
- SourceFolder: build.tmp
- Contents: '*.log'
- TargetFolder: $(build.artifactstagingdirectory)
- CleanTargetFolder: true
- condition: succeededOrFailed()
- - task: PublishBuildArtifacts@1
- displayName: Publish Log Files
- inputs:
- PathtoPublish: $(build.artifactstagingdirectory)
- ArtifactName: logs
- condition: succeededOrFailed()
- - stage: Artifacts_Docs
- displayName: 'Static Code Documentation'
- dependsOn: [Determine_build_type]
- jobs:
- - job: Generate_Docs_CSharp
- timeoutInMinutes: 60
- displayName: Generate C# Docs
- condition: eq(stageDependencies.Determine_build_type.Set_build_variables.outputs['setReleaseVariable.isRelease'], 'true')
- pool:
- vmImage: windows-latest
- steps:
- - task: UseDotNet@2
- displayName: Use .Net 6.x
- inputs:
- version: 6.x
- - task: PowerShell@2
- displayName: 'Prep build tool - C# Docs'
- inputs:
- targetType: inline
- script: |
- choco install docfx --version=2.59.0 -y
- if ($lastexitcode -ne 0){
- throw ("Error installing DocFX")
- }
- docfx metadata --loglevel Verbose "$(Build.SourcesDirectory)\src\ApiDocs\docfx.json"
- if ($lastexitcode -ne 0){
- throw ("Error generating docs.")
- }
- docfx build --loglevel Verbose "$(Build.SourcesDirectory)\src\ApiDocs\docfx.json"
- if ($lastexitcode -ne 0){
- throw ("Error generating docs.")
- }
- errorActionPreference: continue
- workingDirectory: build
- - task: ArchiveFiles@2
- displayName: 'Zip C# Docs'
- inputs:
- rootFolderOrFile: $(Build.SourcesDirectory)\src\ApiDocs\_site
- includeRootFolder: false
- archiveType: zip
- archiveFile: $(Build.ArtifactStagingDirectory)\docs\csharp-docs.zip
- replaceExistingArchive: true
- - task: PublishPipelineArtifact@1
- displayName: Publish to artifacts - C# Docs
- inputs:
- targetPath: $(Build.ArtifactStagingDirectory)\docs\csharp-docs.zip
- artifact: docs-cs
- publishLocation: pipeline
- - job: Generate_Docs_JS
- timeoutInMinutes: 60
- displayName: Generate JS Docs
- condition: eq(stageDependencies.Determine_build_type.Set_build_variables.outputs['setReleaseVariable.isRelease'], 'true')
- pool:
- vmImage: windows-latest
- steps:
- - task: PowerShell@2
- displayName: Prep build tool - JS Docs
- inputs:
- targetType: inline
- script: |
- $uenv=./build.ps1 -get -doc
- $uenv.SandboxNode()
- $uenv.CompileBelle()
- $uenv.PrepareAngularDocs()
- $uenv.RestoreNode()
- errorActionPreference: continue
- workingDirectory: build
- - task: PublishPipelineArtifact@1
- displayName: Publish to artifacts - JS Docs
- inputs:
- targetPath: $(Build.Repository.LocalPath)\build.out\
- artifact: docs
- publishLocation: pipeline
+ - stage: Upload_API_Docs
+ pool:
+ vmImage: 'windows-latest' # Apparently AzureFileCopy is windows only :(
+ variables:
+ umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['determineMajorVersion.majorVersion'] ]
+ displayName: Upload API Documention
+ dependsOn:
+ - Build
+ - Deploy_NuGet
+ condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.uploadApiDocs}}))
+ jobs:
+ - job:
+ displayName: Upload C# Docs
+ steps:
+ - checkout: none
+ - task: DownloadPipelineArtifact@2
+ displayName: Download artifact
+ inputs:
+ artifact: csharp-docs
+ path: $(Build.SourcesDirectory)
+ - task: ExtractFiles@1
+ inputs:
+ archiveFilePatterns: $(Build.SourcesDirectory)/csharp-docs.zip
+ destinationFolder: $(Build.ArtifactStagingDirectory)/csharp-docs
+ - task: AzureFileCopy@4
+ displayName: 'Copy C# Docs to blob storage'
+ inputs:
+ SourcePath: '$(Build.ArtifactStagingDirectory)/csharp-docs/*'
+ azureSubscription: umbraco-storage
+ Destination: AzureBlob
+ storage: umbracoapidocs
+ ContainerName: '$web'
+ BlobPrefix: v$(umbracoMajorVersion)/csharp
+ - job:
+ displayName: Upload js Docs
+ steps:
+ - checkout: none
+ - task: DownloadPipelineArtifact@2
+ displayName: Download artifact
+ inputs:
+ artifact: ui-docs
+ path: $(Build.SourcesDirectory)
+ - task: ExtractFiles@1
+ inputs:
+ archiveFilePatterns: $(Build.SourcesDirectory)/ui-docs.zip
+ destinationFolder: $(Build.ArtifactStagingDirectory)/ui-docs
+ - task: AzureFileCopy@4
+ displayName: 'Copy UI Docs to blob storage'
+ inputs:
+ SourcePath: '$(Build.ArtifactStagingDirectory)/ui-docs/*'
+ azureSubscription: umbraco-storage
+ Destination: AzureBlob
+ storage: umbracoapidocs
+ ContainerName: '$web'
+ BlobPrefix: v$(umbracoMajorVersion)/ui
diff --git a/build/build-bootstrap.ps1 b/build/build-bootstrap.ps1
deleted file mode 100644
index 4c946ba289..0000000000
--- a/build/build-bootstrap.ps1
+++ /dev/null
@@ -1,95 +0,0 @@
-
- # this script should be dot-sourced into the build.ps1 scripts
- # right after the parameters declaration
- # ie
- # . "$PSScriptRoot\build-bootstrap.ps1"
-
- # THIS FILE IS DISTRIBUTED AS PART OF UMBRACO.BUILD
- # DO NOT MODIFY IT - ALWAYS USED THE COMMON VERSION
-
- # ################################################################
- # BOOTSTRAP
- # ################################################################
-
- # reset errors
- $error.Clear()
-
- # ensure we have temp folder for downloads
- $scriptRoot = "$PSScriptRoot"
- $scriptTemp = "$scriptRoot\temp"
- if (-not (test-path $scriptTemp)) { mkdir $scriptTemp > $null }
-
- # get NuGet
- $cache = 4
- $nuget = "$scriptTemp\nuget.exe"
- # ensure the correct NuGet-source is used. This one is used by Umbraco
- $nugetsourceUmbraco = "https://www.myget.org/F/umbracoprereleases/api/v3/index.json"
- if (-not $local)
- {
- $source = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
- if ((test-path $nuget) -and ((ls $nuget).CreationTime -lt [DateTime]::Now.AddDays(-$cache)))
- {
- Remove-Item $nuget -force -errorAction SilentlyContinue > $null
- }
- if (-not (test-path $nuget))
- {
- Write-Host "Download NuGet..."
- [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
- Invoke-WebRequest $source -OutFile $nuget
- if (-not $?) { throw "Failed to download NuGet." }
- }
- }
- elseif (-not (test-path $nuget))
- {
- throw "Failed to locate NuGet.exe."
- }
-
- # NuGet notes
- # As soon as we use -ConfigFile, NuGet uses that file, and only that file, and does not
- # merge configuration from system level. See comments in NuGet.Client solution, class
- # NuGet.Configuration.Settings, method LoadDefaultSettings.
- # For NuGet to merge configurations, it needs to "find" the file in the current directory,
- # or above. Which means we cannot really use -ConfigFile but instead have to have Umbraco's
- # NuGet.config file at root, and always run NuGet.exe while at root or in a directory below
- # root.
-
- $solutionRoot = "$scriptRoot\.."
- $testPwd = [System.IO.Path]::GetFullPath($pwd.Path) + "\"
- $testRoot = [System.IO.Path]::GetFullPath($solutionRoot) + "\"
- if (-not $testPwd.ToLower().StartsWith($testRoot.ToLower()))
- {
- throw "Cannot run outside of the solution's root."
- }
-
- # get the build system
- if (-not $local)
- {
- $params = "-OutputDirectory", $scriptTemp, "-Verbosity", "quiet", "-PreRelease", "-Source", $nugetsourceUmbraco
- &$nuget install Umbraco.Build @params
- if (-not $?) { throw "Failed to download Umbraco.Build." }
- }
-
- # ensure we have the build system
- $ubuildPath = ls "$scriptTemp\Umbraco.Build.*" | sort -property CreationTime -descending | select -first 1
- if (-not $ubuildPath)
- {
- throw "Failed to locate the build system."
- }
-
- # boot the build system
- # this creates $global:ubuild
- return &"$ubuildPath\ps\Boot.ps1"
-
- # at that point the build.ps1 script must boot the build system
- # eg
- # $ubuild.Boot($ubuildPath.FullName, [System.IO.Path]::GetFullPath("$scriptRoot\.."),
- # @{ Local = $local; With7Zip = $false; WithNode = $false },
- # @{ continue = $continue })
- # if (-not $?) { throw "Failed to boot the build system." }
- #
- # and it's good practice to report
- # eg
- # Write-Host "Umbraco.Whatever Build"
- # Write-Host "Umbraco.Build v$($ubuild.BuildVersion)"
-
- # eof
diff --git a/build/build.ps1 b/build/build.ps1
deleted file mode 100644
index 0c2780fb03..0000000000
--- a/build/build.ps1
+++ /dev/null
@@ -1,491 +0,0 @@
-
- param (
- # get, don't execute
- [Parameter(Mandatory=$false)]
- [Alias("g")]
- [switch] $get = $false,
-
- # run local, don't download, assume everything is ready
- [Parameter(Mandatory=$false)]
- [Alias("l")]
- [Alias("loc")]
- [switch] $local = $false,
-
- # enable docfx
- [Parameter(Mandatory=$false)]
- [Alias("doc")]
- [switch] $docfx = $false,
-
- # keep the build directories, don't clear them
- [Parameter(Mandatory=$false)]
- [Alias("c")]
- [Alias("cont")]
- [switch] $continue = $false,
-
- # execute a command
- [Parameter(Mandatory=$false, ValueFromRemainingArguments=$true)]
- [String[]]
- $command
- )
-
- # ################################################################
- # BOOTSTRAP
- # ################################################################
-
- # create and boot the buildsystem
- $ubuild = &"$PSScriptRoot\build-bootstrap.ps1"
- if (-not $?) { return }
- $ubuild.Boot($PSScriptRoot,
- @{ Local = $local; WithDocFx = $docfx },
- @{ Continue = $continue })
- if ($ubuild.OnError()) { return }
-
- Write-Host "Umbraco CMS Build"
- Write-Host "Umbraco.Build v$($ubuild.BuildVersion)"
-
- # ################################################################
- # TASKS
- # ################################################################
-
- $ubuild.DefineMethod("SetMoreUmbracoVersion",
- {
- param ( $semver )
-
- $port = "" + $semver.Major + $semver.Minor + ("" + $semver.Patch).PadLeft(2, '0')
- Write-Host "Update port in launchSettings.json to $port"
- $filePath = "$($this.SolutionRoot)\src\Umbraco.Web.UI\Properties\launchSettings.json"
- $this.ReplaceFileText($filePath, `
- "http://localhost:(\d+)?", `
- "http://localhost:$port")
- })
-
- $ubuild.DefineMethod("SandboxNode",
- {
- $global:node_path = $env:path
- $nodePath = $this.BuildEnv.NodePath
- $gitExe = (Get-Command git).Source
- if (-not $gitExe) { $gitExe = (Get-Command git).Path }
- $gitPath = [System.IO.Path]::GetDirectoryName($gitExe)
- $env:path = "$nodePath;$gitPath"
-
- $global:node_nodepath = $this.ClearEnvVar("NODEPATH")
- $global:node_npmcache = $this.ClearEnvVar("NPM_CONFIG_CACHE")
- $global:node_npmprefix = $this.ClearEnvVar("NPM_CONFIG_PREFIX")
-
- # https://github.com/gruntjs/grunt-contrib-connect/issues/235
- $this.SetEnvVar("NODE_NO_HTTP2", "1")
- })
-
- $ubuild.DefineMethod("RestoreNode",
- {
- $env:path = $node_path
-
- $this.SetEnvVar("NODEPATH", $node_nodepath)
- $this.SetEnvVar("NPM_CONFIG_CACHE", $node_npmcache)
- $this.SetEnvVar("NPM_CONFIG_PREFIX", $node_npmprefix)
-
- $this.ClearEnvVar("NODE_NO_HTTP2")
- })
-
- $ubuild.DefineMethod("CompileBelle",
- {
- $src = "$($this.SolutionRoot)\src"
- $log = "$($this.BuildTemp)\belle.log"
-
-
- Write-Host "Compile Belle"
- Write-Host "Logging to $log"
-
- # get a temp clean node env (will restore)
- $this.SandboxNode()
-
- # stupid PS is going to gather all "warnings" in $error
- # so we have to take care of it else they'll bubble and kill the build
- if ($error.Count -gt 0) { return }
-
- try {
- Push-Location "$($this.SolutionRoot)\src\Umbraco.Web.UI.Client"
- Write-Output "" > $log
-
- Write-Output "### node version is:" > $log
- node -v >> $log 2>&1
- if (-not $?) { throw "Failed to report node version." }
-
- Write-Output "### npm version is:" >> $log 2>&1
- npm -v >> $log 2>&1
- if (-not $?) { throw "Failed to report npm version." }
-
- Write-Output "### clean npm cache" >> $log 2>&1
- npm cache clean --force >> $log 2>&1
- $error.Clear() # that one can fail 'cos security bug - ignore
-
- Write-Output "### npm ci" >> $log 2>&1
- npm ci >> $log 2>&1
- Write-Output ">> $? $($error.Count)" >> $log 2>&1
- # Don't really care about the messages from npm ci making us think there are errors
- $error.Clear()
-
- Write-Output "### gulp build for version $($this.Version.Release)" >> $log 2>&1
- npm run build --buildversion=$this.Version.Release >> $log 2>&1
-
- # We can ignore this warning, we need to update to node 12 at some point - https://github.com/jsdom/jsdom/issues/2939
- $indexes = [System.Collections.ArrayList]::new()
- $index = 0;
- $error | ForEach-Object {
- # Find which of the errors is the ExperimentalWarning
- if($_.ToString().Contains("ExperimentalWarning: The fs.promises API is experimental")) {
- [void]$indexes.Add($index)
- }
- $index++
- }
- $indexes | ForEach-Object {
- # Loop through the list of indexes and remove the errors that we expect and feel confident we can ignore
- $error.Remove($error[$_])
- }
-
- if (-not $?) { throw "Failed to build" } # that one is expected to work
- } finally {
- Pop-Location
-
- # FIXME: should we filter the log to find errors?
- #get-content .\build.tmp\belle.log | %{ if ($_ -match "build") { write $_}}
-
- # restore
- $this.RestoreNode()
- }
-
- # setting node_modules folder to hidden
- # used to prevent VS13 from crashing on it while loading the websites project
- # also makes sure aspnet compiler does not try to handle rogue files and chokes
- # in VSO with Microsoft.VisualC.CppCodeProvider -related errors
- # use get-item -force 'cos it might be hidden already
- Write-Host "Set hidden attribute on node_modules"
- $dir = Get-Item -force "$src\Umbraco.Web.UI.Client\node_modules"
- $dir.Attributes = $dir.Attributes -bor ([System.IO.FileAttributes]::Hidden)
- })
-
- $ubuild.DefineMethod("CompileUmbraco",
- {
- $buildConfiguration = "Release"
-
- $src = "$($this.SolutionRoot)\src"
- $log = "$($this.BuildTemp)\build.umbraco.log"
-
- Write-Host "Compile Umbraco"
- Write-Host "Logging to $log"
-
- & dotnet build "$src\Umbraco.Web.UI\Umbraco.Web.UI.csproj" `
- --configuration $buildConfiguration `
- --output "$($this.BuildTemp)\bin\\" `
- > $log
-
- # get files into WebApp\bin
- & dotnet publish "$src\Umbraco.Web.UI\Umbraco.Web.UI.csproj" `
- --configuration Release --output "$($this.BuildTemp)\WebApp\bin\\" `
- > $log
-
- # remove extra files
- $webAppBin = "$($this.BuildTemp)\WebApp\bin"
- $excludeDirs = @("$($webAppBin)\refs","$($webAppBin)\runtimes","$($webAppBin)\umbraco","$($webAppBin)\wwwroot")
- $excludeFiles = @("$($webAppBin)\appsettings.*","$($webAppBin)\*.deps.json","$($webAppBin)\*.exe","$($webAppBin)\*.config","$($webAppBin)\*.runtimeconfig.json")
- $this.RemoveDirectory($excludeDirs)
- $this.RemoveFile($excludeFiles)
-
- # copy rest of the files into WebApp
- $excludeUmbracoDirs = @("$($this.BuildTemp)\WebApp\umbraco\lib","$($this.BuildTemp)\WebApp\umbraco\Data","$($this.BuildTemp)\WebApp\umbraco\Logs")
- $this.RemoveDirectory($excludeUmbracoDirs)
- $this.CopyFiles("$($this.SolutionRoot)\src\Umbraco.Web.UI\Views", "*", "$($this.BuildTemp)\WebApp\Views")
- Copy-Item "$($this.SolutionRoot)\src\Umbraco.Web.UI\appsettings.json" "$($this.BuildTemp)\WebApp"
-
- if (-not $?) { throw "Failed to compile Umbraco.Web.UI." }
-
- # /p:UmbracoBuild tells the csproj that we are building from PS, not VS
- })
-
- $ubuild.DefineMethod("CompileJsonSchema",
- {
- Write-Host "Generating JSON Schema for AppSettings"
- Write-Host "Logging to $($this.BuildTemp)\json.schema.log"
-
- ## NOTE: Need to specify the outputfile to point to the build temp folder
- &dotnet run --project "$($this.SolutionRoot)\src\JsonSchema\JsonSchema.csproj" `
- -c Release > "$($this.BuildTemp)\json.schema.log" `
- -- `
- --outputFile "$($this.BuildTemp)\WebApp\umbraco\config\appsettings-schema.json"
- })
-
- $ubuild.DefineMethod("PrepareTests",
- {
- Write-Host "Prepare Tests"
-
- # FIXME: - idea is to avoid rebuilding everything for tests
- # but because of our weird assembly versioning (with .* stuff)
- # everything gets rebuilt all the time...
- #Copy-Files "$tmp\bin" "." "$tmp\tests"
-
- # data
- Write-Host "Copy data files"
- if (-not (Test-Path -Path "$($this.BuildTemp)\tests\Packaging" ))
- {
- Write-Host "Create packaging directory"
- mkdir "$($this.BuildTemp)\tests\Packaging" > $null
- }
- #$this.CopyFiles("$($this.SolutionRoot)\src\Umbraco.Tests\Packaging\Packages", "*", "$($this.BuildTemp)\tests\Packaging\Packages")
-
- # required for package install tests
- if (-not (Test-Path -Path "$($this.BuildTemp)\tests\bin" ))
- {
- Write-Host "Create bin directory"
- mkdir "$($this.BuildTemp)\tests\bin" > $null
- }
- })
-
- $ubuild.DefineMethod("CompileTests",
- {
- $buildConfiguration = "Release"
- $log = "$($this.BuildTemp)\msbuild.tests.log"
-
- Write-Host "Compile Tests"
- Write-Host "Logging to $log"
-
- # beware of the weird double \\ at the end of paths
- # see http://edgylogic.com/blog/powershell-and-external-commands-done-right/
- &dotnet msbuild "$($this.SolutionRoot)\tests\Umbraco.Tests\Umbraco.Tests.csproj" `
- -target:Build `
- -property:WarningLevel=0 `
- -property:Configuration=$buildConfiguration `
- -property:Platform=AnyCPU `
- -property:UseWPP_CopyWebApplication=True `
- -property:PipelineDependsOnBuild=False `
- -property:OutDir="$($this.BuildTemp)\tests\\" `
- -property:Verbosity=minimal `
- -property:UmbracoBuild=True `
- > $log
-
- if (-not $?) { throw "Failed to compile tests." }
-
- # /p:UmbracoBuild tells the csproj that we are building from PS
- })
-
- $ubuild.DefineMethod("PreparePackages",
- {
- Write-Host "Prepare Packages"
-
- $src = "$($this.SolutionRoot)\src"
- $tmp = "$($this.BuildTemp)"
-
- # cleanup build
- Write-Host "Clean build"
- $this.RemoveFile("$tmp\bin\*.dll.config")
- $this.RemoveFile("$tmp\WebApp\bin\*.dll.config")
-
- # cleanup presentation
- Write-Host "Cleanup presentation"
- $this.RemoveDirectory("$tmp\WebApp\umbraco.presentation")
-
- # create directories
- Write-Host "Create directories"
- mkdir "$tmp\WebApp\App_Data" > $null
- #mkdir "$tmp\WebApp\Media" > $null
- #mkdir "$tmp\WebApp\Views" > $null
-
- # copy various files
- Write-Host "Copy xml documentation"
- Copy-Item -force "$tmp\bin\*.xml" "$tmp\WebApp\bin"
-
- # offset the modified timestamps on all umbraco dlls, as WebResources
- # break if date is in the future, which, due to timezone offsets can happen.
- Write-Host "Offset dlls timestamps"
- Get-ChildItem -r "$tmp\*.dll" | ForEach-Object {
- $_.CreationTime = $_.CreationTime.AddHours(-11)
- $_.LastWriteTime = $_.LastWriteTime.AddHours(-11)
- }
- })
-
-
- $ubuild.DefineMethod("PrepareBuild",
- {
- Write-host "Set environment"
- $env:UMBRACO_VERSION=$this.Version.Semver.ToString()
- $env:UMBRACO_RELEASE=$this.Version.Release
- $env:UMBRACO_COMMENT=$this.Version.Comment
- $env:UMBRACO_BUILD=$this.Version.Build
- $env:UMBRACO_TMP="$($this.SolutionRoot)\build.tmp"
-
- if ($args -and $args[0] -eq "vso")
- {
- Write-host "Set VSO environment"
- # set environment variable for VSO
- # https://github.com/Microsoft/vsts-tasks/issues/375
- # https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md
- Write-Host ("##vso[task.setvariable variable=UMBRACO_VERSION;]$($this.Version.Semver.ToString())")
- Write-Host ("##vso[task.setvariable variable=UMBRACO_RELEASE;]$($this.Version.Release)")
- Write-Host ("##vso[task.setvariable variable=UMBRACO_COMMENT;]$($this.Version.Comment)")
- Write-Host ("##vso[task.setvariable variable=UMBRACO_BUILD;]$($this.Version.Build)")
-
- Write-Host ("##vso[task.setvariable variable=UMBRACO_TMP;]$($this.SolutionRoot)\build.tmp")
- }
- })
-
- $nugetsourceUmbraco = "https://api.nuget.org/v3/index.json"
-
- $ubuild.DefineMethod("RestoreNuGet",
- {
- Write-Host "Restore NuGet"
- Write-Host "Logging to $($this.BuildTemp)\nuget.restore.log"
- $params = "-Source", $nugetsourceUmbraco
- &$this.BuildEnv.NuGet restore "$($this.SolutionRoot)\umbraco.sln" > "$($this.BuildTemp)\nuget.restore.log" @params
- if (-not $?) { throw "Failed to restore NuGet packages." }
- })
-
- $ubuild.DefineMethod("PackageNuGet",
- {
- $nuspecs = "$($this.SolutionRoot)\build\NuSpecs"
- $templates = "$($this.SolutionRoot)\templates"
-
- Write-Host "Create NuGet packages"
-
- &dotnet pack "$($this.SolutionRoot)\umbraco.sln" `
- --output "$($this.BuildOutput)" `
- --verbosity detailed `
- -c Release `
- -p:PackageVersion="$($this.Version.Semver.ToString())" > "$($this.BuildTemp)\pack.umbraco.log"
-
- &$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.nuspec" `
- -Properties BuildTmp="$($this.BuildTemp)" `
- -Version "$($this.Version.Semver.ToString())" `
- -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cms.log"
- if (-not $?) { throw "Failed to pack NuGet UmbracoCms." }
-
- &$this.BuildEnv.NuGet Pack "$templates\Umbraco.Templates.nuspec" `
- -Properties BuildTmp="$($this.BuildTemp)" `
- -Version "$($this.Version.Semver.ToString())" `
- -NoDefaultExcludes `
- -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.templates.log"
- if (-not $?) { throw "Failed to pack NuGet Umbraco.Templates." }
-
- # run hook
- if ($this.HasMethod("PostPackageNuGet"))
- {
- Write-Host "Run PostPackageNuGet hook"
- $this.PostPackageNuGet();
- if (-not $?) { throw "Failed to run hook." }
- }
- })
-
- $ubuild.DefineMethod("VerifyNuGet",
- {
- $this.VerifyNuGetConsistency(
- ("UmbracoCms"),
- ("Umbraco.Core", "Umbraco.Infrastructure", "Umbraco.Web.UI", "Umbraco.Examine.Lucene", "Umbraco.PublishedCache.NuCache", "Umbraco.Web.Common", "Umbraco.Web.Website", "Umbraco.Web.BackOffice", "Umbraco.Cms.Persistence.Sqlite", "Umbraco.Cms.Persistence.SqlServer"))
- if ($this.OnError()) { return }
- })
-
- $ubuild.DefineMethod("PrepareCSharpDocs",
- {
- Write-Host "Prepare C# Documentation"
-
- $src = "$($this.SolutionRoot)\src"
- $tmp = $this.BuildTemp
- $out = $this.BuildOutput
- $DocFxJson = Join-Path -Path $src "\ApiDocs\docfx.json"
- $DocFxSiteOutput = Join-Path -Path $tmp "\_site\*.*"
-
- # run DocFx
- $DocFx = $this.BuildEnv.DocFx
-
- & $DocFx metadata $DocFxJson
- & $DocFx build $DocFxJson
-
- # zip it
- & $this.BuildEnv.Zip a -tzip -r "$out\csharp-docs.zip" $DocFxSiteOutput
- })
-
- $ubuild.DefineMethod("PrepareAngularDocs",
- {
- Write-Host "Prepare Angular Documentation"
-
- $src = "$($this.SolutionRoot)\src"
- $out = $this.BuildOutput
-
- # Check if the solution has been built
- if (!(Test-Path "$src\Umbraco.Web.UI.Client\node_modules")) {throw "Umbraco needs to be built before generating the Angular Docs"}
-
- "Moving to Umbraco.Web.UI.Docs folder"
- cd $src\Umbraco.Web.UI.Docs
-
- "Generating the docs and waiting before executing the next commands"
- & npm ci
- & npx gulp docs
-
- Pop-Location
-
- # change baseUrl
- $BaseUrl = "https://apidocs.umbraco.com/v9/ui/"
- $IndexPath = "./api/index.html"
- (Get-Content $IndexPath).replace('origin + location.href.substr(origin.length).replace(rUrl, indexFile)', "`'" + $BaseUrl + "`'") | Set-Content $IndexPath
-
- # zip it
- & $this.BuildEnv.Zip a -tzip -r "$out\ui-docs.zip" "$src\Umbraco.Web.UI.Docs\api\*.*"
- })
-
- $ubuild.DefineMethod("Build",
- {
- $error.Clear()
-
- $this.PrepareBuild()
- if ($this.OnError()) { return }
- $this.RestoreNuGet()
- if ($this.OnError()) { return }
- $this.CompileBelle()
- if ($this.OnError()) { return }
- $this.CompileUmbraco()
- if ($this.OnError()) { return }
- $this.CompileJsonSchema()
- if ($this.OnError()) { return }
- $this.PrepareTests()
- if ($this.OnError()) { return }
- $this.CompileTests()
- if ($this.OnError()) { return }
- # not running tests
- $this.PreparePackages()
- if ($this.OnError()) { return }
- $this.VerifyNuGet()
- if ($this.OnError()) { return }
- $this.PackageNuGet()
- if ($this.OnError()) { return }
- $this.PostPackageHook()
- if ($this.OnError()) { return }
-
- Write-Host "Done"
- })
-
- $ubuild.DefineMethod("PostPackageHook",
- {
- # run hook
- if ($this.HasMethod("PostPackage"))
- {
- Write-Host "Run PostPackage hook"
- $this.PostPackage();
- if (-not $?) { throw "Failed to run hook." }
- }
- })
-
- # ################################################################
- # RUN
- # ################################################################
-
- # configure
- $ubuild.ReleaseBranches = @( "master" )
-
- # run
- if (-not $get)
- {
- if ($command.Length -eq 0)
- {
- $command = @( "Build" )
- }
- $ubuild.RunMethod($command);
- if ($ubuild.OnError()) { return }
- }
- if ($get) { return $ubuild }
diff --git a/src/ApiDocs/docfx.filter.yml b/build/csharp-docs/docfx.filter.yml
similarity index 100%
rename from src/ApiDocs/docfx.filter.yml
rename to build/csharp-docs/docfx.filter.yml
diff --git a/src/ApiDocs/docfx.json b/build/csharp-docs/docfx.json
similarity index 84%
rename from src/ApiDocs/docfx.json
rename to build/csharp-docs/docfx.json
index e5f6dd7410..195f2a7fc3 100644
--- a/src/ApiDocs/docfx.json
+++ b/build/csharp-docs/docfx.json
@@ -3,18 +3,17 @@
{
"src": [
{
- "src": "../",
+ "src": "../../src",
"files": [
- "**/*.csproj",
- "**/Umbraco.Infrastructure/**/*.cs"
+ "**/*.csproj"
],
"exclude": [
"**/obj/**",
"**/bin/**",
"**/Umbraco.Web.csproj",
- "**/Umbraco.Infrastructure.csproj",
"**/Umbraco.Web.UI.csproj",
- "**/**.Test**/*.csproj"
+ "**/Umbraco.Cms.StaticAssets.csproj",
+ "**/JsonSchema.csproj"
]
}
],
diff --git a/src/ApiDocs/index.md b/build/csharp-docs/index.md
similarity index 100%
rename from src/ApiDocs/index.md
rename to build/csharp-docs/index.md
diff --git a/src/ApiDocs/toc.yml b/build/csharp-docs/toc.yml
similarity index 100%
rename from src/ApiDocs/toc.yml
rename to build/csharp-docs/toc.yml
diff --git a/src/ApiDocs/umbracotemplate/partials/class.tmpl.partial b/build/csharp-docs/umbracotemplate/partials/class.tmpl.partial
similarity index 100%
rename from src/ApiDocs/umbracotemplate/partials/class.tmpl.partial
rename to build/csharp-docs/umbracotemplate/partials/class.tmpl.partial
diff --git a/src/ApiDocs/umbracotemplate/partials/footer.tmpl.partial b/build/csharp-docs/umbracotemplate/partials/footer.tmpl.partial
similarity index 100%
rename from src/ApiDocs/umbracotemplate/partials/footer.tmpl.partial
rename to build/csharp-docs/umbracotemplate/partials/footer.tmpl.partial
diff --git a/src/ApiDocs/umbracotemplate/partials/head.tmpl.partial b/build/csharp-docs/umbracotemplate/partials/head.tmpl.partial
similarity index 100%
rename from src/ApiDocs/umbracotemplate/partials/head.tmpl.partial
rename to build/csharp-docs/umbracotemplate/partials/head.tmpl.partial
diff --git a/src/ApiDocs/umbracotemplate/partials/namespace.tmpl.partial b/build/csharp-docs/umbracotemplate/partials/namespace.tmpl.partial
similarity index 100%
rename from src/ApiDocs/umbracotemplate/partials/namespace.tmpl.partial
rename to build/csharp-docs/umbracotemplate/partials/namespace.tmpl.partial
diff --git a/src/ApiDocs/umbracotemplate/partials/navbar.tmpl.partial b/build/csharp-docs/umbracotemplate/partials/navbar.tmpl.partial
similarity index 100%
rename from src/ApiDocs/umbracotemplate/partials/navbar.tmpl.partial
rename to build/csharp-docs/umbracotemplate/partials/navbar.tmpl.partial
diff --git a/src/ApiDocs/umbracotemplate/partials/rest.tmpl.partial b/build/csharp-docs/umbracotemplate/partials/rest.tmpl.partial
similarity index 100%
rename from src/ApiDocs/umbracotemplate/partials/rest.tmpl.partial
rename to build/csharp-docs/umbracotemplate/partials/rest.tmpl.partial
diff --git a/src/ApiDocs/umbracotemplate/styles/main.css b/build/csharp-docs/umbracotemplate/styles/main.css
similarity index 100%
rename from src/ApiDocs/umbracotemplate/styles/main.css
rename to build/csharp-docs/umbracotemplate/styles/main.css
diff --git a/opened-issue-first-comment.yml b/opened-issue-first-comment.yml
new file mode 100644
index 0000000000..134a368785
--- /dev/null
+++ b/opened-issue-first-comment.yml
@@ -0,0 +1,53 @@
+name: issue-first-response
+
+on:
+ issues:
+ types: [opened]
+
+jobs:
+ send-response:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+ steps:
+ - name: Install dependencies
+ run: |
+ npm install node-fetch@2
+ - name: Fetch random comment 🗣️ and add it to the issue
+ uses: actions/github-script@v6
+ with:
+ script: |
+ const fetch = require('node-fetch')
+
+ const response = await fetch('https://collaboratorsv2.euwest01.umbraco.io/umbraco/api/comments/PostComment', {
+ method: 'post',
+ body: JSON.stringify({
+ repo: '${{ github.repository }}',
+ number: '${{ github.event.number }}',
+ actor: '${{ github.actor }}',
+ commentType: 'opened-issue-first-comment'
+ }),
+ headers: {
+ 'Authorization': 'Bearer ${{ secrets.OUR_BOT_API_TOKEN }}',
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ try {
+ const data = await response.text();
+
+ if(response.status === 200 && data !== '') {
+ github.rest.issues.createComment({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ body: data
+ });
+ } else {
+ console.log("Status code did not indicate success:", response.status);
+ console.log("Returned data:", data);
+ }
+ } catch(error) {
+ console.log(error);
+ }
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 6eaa51e431..ce54e08edd 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -1,6 +1,6 @@
-
+ 10.0.0
@@ -42,4 +42,12 @@
true
+
+
+
+ true
+ 10.0.0
+ true
+ true
+
diff --git a/src/JsonSchema/AppSettings.cs b/src/JsonSchema/AppSettings.cs
index e80666038c..fb9db8387a 100644
--- a/src/JsonSchema/AppSettings.cs
+++ b/src/JsonSchema/AppSettings.cs
@@ -35,9 +35,8 @@ namespace JsonSchema
///
public class CmsDefinition
{
- public ActiveDirectorySettings? ActiveDirectory { get; set; }
-
public ContentSettings? Content { get; set; }
+ public CoreDebugSettings? Debug { get; set; }
public ExceptionFilterSettings? ExceptionFilter { get; set; }
@@ -94,6 +93,8 @@ namespace JsonSchema
public HelpPageSettings? HelpPage { get; set; }
public InstallDefaultDataSettings? DefaultDataCreation { get; set; }
+
+ public DataTypesSettings? DataTypes { get; set; }
}
///
diff --git a/src/JsonSchema/JsonSchema.csproj b/src/JsonSchema/JsonSchema.csproj
index 97608c756b..ea0ce9b7c3 100644
--- a/src/JsonSchema/JsonSchema.csproj
+++ b/src/JsonSchema/JsonSchema.csproj
@@ -4,16 +4,20 @@
net6.0truefalse
+ false
-
-
+
+
-
+
+
+ 3.5.107
+
@@ -31,5 +35,4 @@
-
diff --git a/src/JsonSchema/Options.cs b/src/JsonSchema/Options.cs
index 83a2a8ef94..4471ee49ce 100644
--- a/src/JsonSchema/Options.cs
+++ b/src/JsonSchema/Options.cs
@@ -7,7 +7,7 @@ namespace JsonSchema
{
internal class Options
{
- [Option('o', "outputFile", Required = false, HelpText = "Set path of the output file.", Default = "../../../../Umbraco.Web.UI/umbraco/config/appsettings-schema.json")]
+ [Option('o', "outputFile", Required = false, HelpText = "Set path of the output file.", Default = "../../../../Umbraco.Web.UI/appsettings-schema.json")]
public string OutputFile { get; set; } = null!;
}
}
diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Constants.cs b/src/Umbraco.Cms.Persistence.SqlServer/Constants.cs
index 19ec0738c8..ae16a9735f 100644
--- a/src/Umbraco.Cms.Persistence.SqlServer/Constants.cs
+++ b/src/Umbraco.Cms.Persistence.SqlServer/Constants.cs
@@ -1,12 +1,12 @@
namespace Umbraco.Cms.Persistence.SqlServer;
///
-/// Constants related to SQLite.
+/// Constants related to SQLite.
///
public static class Constants
{
///
- /// SQLite provider name.
+ /// SQLite provider name.
///
public const string ProviderName = "Microsoft.Data.SqlClient";
}
diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Dtos/ColumnInSchemaDto.cs b/src/Umbraco.Cms.Persistence.SqlServer/Dtos/ColumnInSchemaDto.cs
index 65bd0b5d65..0c09f87d51 100644
--- a/src/Umbraco.Cms.Persistence.SqlServer/Dtos/ColumnInSchemaDto.cs
+++ b/src/Umbraco.Cms.Persistence.SqlServer/Dtos/ColumnInSchemaDto.cs
@@ -1,25 +1,24 @@
using NPoco;
-namespace Umbraco.Cms.Persistence.SqlServer.Dtos
+namespace Umbraco.Cms.Persistence.SqlServer.Dtos;
+
+internal class ColumnInSchemaDto
{
- internal class ColumnInSchemaDto
- {
- [Column("TABLE_NAME")]
- public string TableName { get; set; } = null!;
+ [Column("TABLE_NAME")]
+ public string TableName { get; set; } = null!;
- [Column("COLUMN_NAME")]
- public string ColumnName { get; set; } = null!;
+ [Column("COLUMN_NAME")]
+ public string ColumnName { get; set; } = null!;
- [Column("ORDINAL_POSITION")]
- public int OrdinalPosition { get; set; }
+ [Column("ORDINAL_POSITION")]
+ public int OrdinalPosition { get; set; }
- [Column("COLUMN_DEFAULT")]
- public string ColumnDefault { get; set; } = null!;
+ [Column("COLUMN_DEFAULT")]
+ public string ColumnDefault { get; set; } = null!;
- [Column("IS_NULLABLE")]
- public string IsNullable { get; set; } = null!;
+ [Column("IS_NULLABLE")]
+ public string IsNullable { get; set; } = null!;
- [Column("DATA_TYPE")]
- public string DataType { get; set; } = null!;
- }
+ [Column("DATA_TYPE")]
+ public string DataType { get; set; } = null!;
}
diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Dtos/ConstraintPerColumnDto.cs b/src/Umbraco.Cms.Persistence.SqlServer/Dtos/ConstraintPerColumnDto.cs
index 351979570c..b0299a489d 100644
--- a/src/Umbraco.Cms.Persistence.SqlServer/Dtos/ConstraintPerColumnDto.cs
+++ b/src/Umbraco.Cms.Persistence.SqlServer/Dtos/ConstraintPerColumnDto.cs
@@ -1,16 +1,15 @@
using NPoco;
-namespace Umbraco.Cms.Persistence.SqlServer.Dtos
+namespace Umbraco.Cms.Persistence.SqlServer.Dtos;
+
+internal class ConstraintPerColumnDto
{
- internal class ConstraintPerColumnDto
- {
- [Column("TABLE_NAME")]
- public string TableName { get; set; } = null!;
+ [Column("TABLE_NAME")]
+ public string TableName { get; set; } = null!;
- [Column("COLUMN_NAME")]
- public string ColumnName { get; set; } = null!;
+ [Column("COLUMN_NAME")]
+ public string ColumnName { get; set; } = null!;
- [Column("CONSTRAINT_NAME")]
- public string ConstraintName { get; set; } = null!;
- }
+ [Column("CONSTRAINT_NAME")]
+ public string ConstraintName { get; set; } = null!;
}
diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Dtos/ConstraintPerTableDto.cs b/src/Umbraco.Cms.Persistence.SqlServer/Dtos/ConstraintPerTableDto.cs
index 3a633d4e0e..fe87ef2909 100644
--- a/src/Umbraco.Cms.Persistence.SqlServer/Dtos/ConstraintPerTableDto.cs
+++ b/src/Umbraco.Cms.Persistence.SqlServer/Dtos/ConstraintPerTableDto.cs
@@ -1,13 +1,12 @@
using NPoco;
-namespace Umbraco.Cms.Persistence.SqlServer.Dtos
-{
- internal class ConstraintPerTableDto
- {
- [Column("TABLE_NAME")]
- public string TableName { get; set; } = null!;
+namespace Umbraco.Cms.Persistence.SqlServer.Dtos;
- [Column("CONSTRAINT_NAME")]
- public string ConstraintName { get; set; } = null!;
- }
+internal class ConstraintPerTableDto
+{
+ [Column("TABLE_NAME")]
+ public string TableName { get; set; } = null!;
+
+ [Column("CONSTRAINT_NAME")]
+ public string ConstraintName { get; set; } = null!;
}
diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Dtos/DefaultConstraintPerColumnDto.cs b/src/Umbraco.Cms.Persistence.SqlServer/Dtos/DefaultConstraintPerColumnDto.cs
index e0e1dfbe2f..a1bde415a3 100644
--- a/src/Umbraco.Cms.Persistence.SqlServer/Dtos/DefaultConstraintPerColumnDto.cs
+++ b/src/Umbraco.Cms.Persistence.SqlServer/Dtos/DefaultConstraintPerColumnDto.cs
@@ -1,18 +1,18 @@
using NPoco;
-namespace Umbraco.Cms.Persistence.SqlServer.Dtos
+namespace Umbraco.Cms.Persistence.SqlServer.Dtos;
+
+internal class DefaultConstraintPerColumnDto
{
- internal class DefaultConstraintPerColumnDto
- {
- [Column("TABLE_NAME")] public string TableName { get; set; } = null!;
+ [Column("TABLE_NAME")]
+ public string TableName { get; set; } = null!;
- [Column("COLUMN_NAME")]
- public string ColumnName { get; set; } = null!;
+ [Column("COLUMN_NAME")]
+ public string ColumnName { get; set; } = null!;
- [Column("NAME")]
- public string Name { get; set; } = null!;
+ [Column("NAME")]
+ public string Name { get; set; } = null!;
- [Column("DEFINITION")]
- public string Definition { get; set; } = null!;
- }
+ [Column("DEFINITION")]
+ public string Definition { get; set; } = null!;
}
diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Dtos/DefinedIndexDto.cs b/src/Umbraco.Cms.Persistence.SqlServer/Dtos/DefinedIndexDto.cs
index e78f354e46..e85d91f1dd 100644
--- a/src/Umbraco.Cms.Persistence.SqlServer/Dtos/DefinedIndexDto.cs
+++ b/src/Umbraco.Cms.Persistence.SqlServer/Dtos/DefinedIndexDto.cs
@@ -1,20 +1,18 @@
using NPoco;
-namespace Umbraco.Cms.Persistence.SqlServer.Dtos
+namespace Umbraco.Cms.Persistence.SqlServer.Dtos;
+
+internal class DefinedIndexDto
{
- internal class DefinedIndexDto
- {
+ [Column("TABLE_NAME")]
+ public string TableName { get; set; } = null!;
- [Column("TABLE_NAME")]
- public string TableName { get; set; } = null!;
+ [Column("INDEX_NAME")]
+ public string IndexName { get; set; } = null!;
- [Column("INDEX_NAME")]
- public string IndexName { get; set; } = null!;
+ [Column("COLUMN_NAME")]
+ public string ColumnName { get; set; } = null!;
- [Column("COLUMN_NAME")]
- public string ColumnName { get; set; } = null!;
-
- [Column("UNIQUE")]
- public short Unique { get; set; }
- }
+ [Column("UNIQUE")]
+ public short Unique { get; set; }
}
diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Interceptors/SqlServerAddMiniProfilerInterceptor.cs b/src/Umbraco.Cms.Persistence.SqlServer/Interceptors/SqlServerAddMiniProfilerInterceptor.cs
index 7c5df6c497..43541ec2a3 100644
--- a/src/Umbraco.Cms.Persistence.SqlServer/Interceptors/SqlServerAddMiniProfilerInterceptor.cs
+++ b/src/Umbraco.Cms.Persistence.SqlServer/Interceptors/SqlServerAddMiniProfilerInterceptor.cs
@@ -1,11 +1,12 @@
using System.Data.Common;
using NPoco;
using StackExchange.Profiling;
+using StackExchange.Profiling.Data;
namespace Umbraco.Cms.Persistence.SqlServer.Interceptors;
public class SqlServerAddMiniProfilerInterceptor : SqlServerConnectionInterceptor
{
public override DbConnection OnConnectionOpened(IDatabase database, DbConnection conn)
- => new StackExchange.Profiling.Data.ProfiledDbConnection(conn, MiniProfiler.Current);
+ => new ProfiledDbConnection(conn, MiniProfiler.Current);
}
diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Interceptors/SqlServerAddRetryPolicyInterceptor.cs b/src/Umbraco.Cms.Persistence.SqlServer/Interceptors/SqlServerAddRetryPolicyInterceptor.cs
index bdf5745d42..139efea85f 100644
--- a/src/Umbraco.Cms.Persistence.SqlServer/Interceptors/SqlServerAddRetryPolicyInterceptor.cs
+++ b/src/Umbraco.Cms.Persistence.SqlServer/Interceptors/SqlServerAddRetryPolicyInterceptor.cs
@@ -21,8 +21,12 @@ public class SqlServerAddRetryPolicyInterceptor : SqlServerConnectionInterceptor
return conn;
}
- RetryPolicy? connectionRetryPolicy = RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(_connectionStrings.CurrentValue.ConnectionString);
- RetryPolicy? commandRetryPolicy = RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(_connectionStrings.CurrentValue.ConnectionString);
+ RetryPolicy? connectionRetryPolicy =
+ RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(_connectionStrings.CurrentValue
+ .ConnectionString);
+ RetryPolicy? commandRetryPolicy =
+ RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(_connectionStrings.CurrentValue
+ .ConnectionString);
if (connectionRetryPolicy == null && commandRetryPolicy == null)
{
diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Services/BulkDataReader.cs b/src/Umbraco.Cms.Persistence.SqlServer/Services/BulkDataReader.cs
index e9784ce270..d74511bf11 100644
--- a/src/Umbraco.Cms.Persistence.SqlServer/Services/BulkDataReader.cs
+++ b/src/Umbraco.Cms.Persistence.SqlServer/Services/BulkDataReader.cs
@@ -3,1503 +3,1490 @@ using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Globalization;
+using System.Text;
using Microsoft.Data.SqlClient;
-namespace Umbraco.Cms.Persistence.SqlServer.Services
+namespace Umbraco.Cms.Persistence.SqlServer.Services;
+
+///
+/// A base implementation of that is suitable for
+/// .
+///
+///
+/// Borrowed from Microsoft:
+/// See: https://blogs.msdn.microsoft.com/anthonybloesch/2013/01/23/bulk-loading-data-with-idatareader-and-sqlbulkcopy/
+/// This implementation is designed to be very memory efficient requiring few memory resources and to support
+/// rapid transfer of data to SQL Server.
+/// Subclasses should implement , ,
+/// , ,
+/// .
+/// If they contain disposable resources they should override .
+/// SD: Alternatively, we could have used a LinqEntityDataReader which is nicer to use but it uses quite a lot of
+/// reflection and
+/// I thought this would just be quicker.
+/// Simple example of that:
+/// https://github.com/gridsum/DataflowEx/blob/master/Gridsum.DataflowEx/Databases/BulkDataReader.cs
+/// Full example of that:
+/// https://github.com/matthewschrager/Repository/blob/master/Repository.EntityFramework/EntityDataReader.cs
+/// So we know where to find that if we ever need it, these would convert any Linq data source to an IDataReader
+///
+internal abstract class BulkDataReader : IDataReader
{
+ #region Fields
+
///
- /// A base implementation of that is suitable for .
+ /// The containing the input row set's schema information
+ ///
+ /// requires to function correctly.
+ ///
+ private DataTable? _schemaTable = new();
+
+ ///
+ /// The mapping from the row set input to the target table's columns.
+ ///
+ private List? _columnMappings = new();
+
+ #endregion
+
+ #region Subclass utility routines
+
+ ///
+ /// The mapping from the row set input to the target table's columns.
///
///
- ///
- /// Borrowed from Microsoft:
- /// See: https://blogs.msdn.microsoft.com/anthonybloesch/2013/01/23/bulk-loading-data-with-idatareader-and-sqlbulkcopy/
- ///
- /// This implementation is designed to be very memory efficient requiring few memory resources and to support
- /// rapid transfer of data to SQL Server.
- ///
- /// Subclasses should implement , ,
- /// , , .
- /// If they contain disposable resources they should override .
- ///
- /// SD: Alternatively, we could have used a LinqEntityDataReader which is nicer to use but it uses quite a lot of reflection and
- /// I thought this would just be quicker.
- /// Simple example of that: https://github.com/gridsum/DataflowEx/blob/master/Gridsum.DataflowEx/Databases/BulkDataReader.cs
- /// Full example of that: https://github.com/matthewschrager/Repository/blob/master/Repository.EntityFramework/EntityDataReader.cs
- /// So we know where to find that if we ever need it, these would convert any Linq data source to an IDataReader
- ///
+ /// If necessary, will be called to initialize the mapping.
///
- internal abstract class BulkDataReader : IDataReader
+ public ReadOnlyCollection ColumnMappings
{
-
- #region Fields
-
- ///
- /// The containing the input row set's schema information
- /// requires to function correctly.
- ///
- private DataTable? _schemaTable = new DataTable();
-
- ///
- /// The mapping from the row set input to the target table's columns.
- ///
- private List? _columnMappings = new List();
-
- #endregion
-
- #region Subclass utility routines
-
- ///
- /// The mapping from the row set input to the target table's columns.
- ///
- ///
- /// If necessary, will be called to initialize the mapping.
- ///
- public ReadOnlyCollection ColumnMappings
+ get
{
- get
+ if (_columnMappings?.Count == 0)
{
- if (this._columnMappings?.Count == 0)
+ // Need to add the column definitions and mappings.
+ AddSchemaTableRows();
+
+ if (_columnMappings.Count == 0)
{
- // Need to add the column definitions and mappings.
- AddSchemaTableRows();
-
- if (this._columnMappings.Count == 0)
- {
- throw new InvalidOperationException("AddSchemaTableRows did not add rows.");
- }
-
- Debug.Assert(this._schemaTable?.Rows.Count == FieldCount);
+ throw new InvalidOperationException("AddSchemaTableRows did not add rows.");
}
- return new ReadOnlyCollection(_columnMappings!);
+ Debug.Assert(_schemaTable?.Rows.Count == FieldCount);
+ }
+
+ return new ReadOnlyCollection(_columnMappings!);
+ }
+ }
+
+ ///
+ /// The name of the input row set's schema.
+ ///
+ ///
+ /// This may be different from the target schema but usually they are identical.
+ ///
+ protected abstract string SchemaName
+ {
+ get;
+ }
+
+ ///
+ /// The name of the input row set's table.
+ ///
+ ///
+ /// This may be different from the target table but usually they are identical.
+ ///
+ protected abstract string TableName
+ {
+ get;
+ }
+
+ ///
+ /// Adds the input row set's schema to the object.
+ ///
+ ///
+ /// Call
+ ///
+ /// to do this for each row.
+ ///
+ ///
+ protected abstract void AddSchemaTableRows();
+
+ ///
+ /// For each , the optional columns that may have values.
+ ///
+ ///
+ /// This is used for checking the parameters of
+ ///
+ /// .
+ ///
+ ///
+ private static readonly Dictionary> AllowedOptionalColumnCombinations = new()
+ {
+ {SqlDbType.BigInt, new List()},
+ {SqlDbType.Binary, new List {SchemaTableColumn.ColumnSize}},
+ {SqlDbType.Bit, new List()},
+ {SqlDbType.Char, new List {SchemaTableColumn.ColumnSize}},
+ {SqlDbType.Date, new List()},
+ {SqlDbType.DateTime, new List()},
+ {SqlDbType.DateTime2, new List {SchemaTableColumn.NumericPrecision}},
+ {SqlDbType.DateTimeOffset, new List {SchemaTableColumn.NumericPrecision}},
+ {SqlDbType.Decimal, new List {SchemaTableColumn.NumericPrecision, SchemaTableColumn.NumericScale}},
+ {SqlDbType.Float, new List {SchemaTableColumn.NumericPrecision, SchemaTableColumn.NumericScale}},
+ {SqlDbType.Image, new List()},
+ {SqlDbType.Int, new List()},
+ {SqlDbType.Money, new List()},
+ {SqlDbType.NChar, new List {SchemaTableColumn.ColumnSize}},
+ {SqlDbType.NText, new List()},
+ {SqlDbType.NVarChar, new List {SchemaTableColumn.ColumnSize}},
+ {SqlDbType.Real, new List()},
+ {SqlDbType.SmallDateTime, new List()},
+ {SqlDbType.SmallInt, new List()},
+ {SqlDbType.SmallMoney, new List()},
+ {SqlDbType.Structured, new List()},
+ {SqlDbType.Text, new List()},
+ {SqlDbType.Time, new List {SchemaTableColumn.NumericPrecision}},
+ {SqlDbType.Timestamp, new List()},
+ {SqlDbType.TinyInt, new List()},
+ {SqlDbType.Udt, new List {DataTypeNameSchemaColumn}},
+ {SqlDbType.UniqueIdentifier, new List()},
+ {SqlDbType.VarBinary, new List {SchemaTableColumn.ColumnSize}},
+ {SqlDbType.VarChar, new List {SchemaTableColumn.ColumnSize}},
+ {SqlDbType.Variant, new List()},
+ {
+ SqlDbType.Xml,
+ new List
+ {
+ XmlSchemaCollectionDatabaseSchemaColumn,
+ XmlSchemaCollectionOwningSchemaSchemaColumn,
+ XmlSchemaCollectionNameSchemaColumn
}
}
+ };
- ///
- /// The name of the input row set's schema.
- ///
- ///
- /// This may be different from the target schema but usually they are identical.
- ///
- protected abstract string SchemaName
+ ///
+ /// A helper method to support .
+ ///
+ ///
+ /// This methods does extensive argument checks. These errors will cause hard to diagnose exceptions in latter
+ /// processing so it is important to detect them when they can be easily associated with the code defect.
+ ///
+ ///
+ /// The combination of values for the parameters is not supported.
+ ///
+ ///
+ /// A null value for the parameter is not supported.
+ ///
+ ///
+ /// The name of the column.
+ ///
+ ///
+ /// The size of the column which may be null if not applicable.
+ ///
+ ///
+ /// The precision of the column which may be null if not applicable.
+ ///
+ ///
+ /// The scale of the column which may be null if not applicable.
+ ///
+ ///
+ /// Are the column values unique (i.e. never duplicated)?
+ ///
+ ///
+ /// Is the column part of the primary key?
+ ///
+ ///
+ /// Is the column nullable (i.e. optional)?
+ ///
+ ///
+ /// The corresponding .
+ ///
+ ///
+ /// The schema name of the UDT.
+ ///
+ ///
+ /// The type name of the UDT.
+ ///
+ ///
+ /// For XML columns the schema collection's database name. Otherwise, null.
+ ///
+ ///
+ /// For XML columns the schema collection's schema name. Otherwise, null.
+ ///
+ ///
+ /// For XML columns the schema collection's name. Otherwise, null.
+ ///
+ ///
+ protected void AddSchemaTableRow(
+ string columnName,
+ int? columnSize,
+ short? numericPrecision,
+ short? numericScale,
+ bool isUnique,
+ bool isKey,
+ bool allowDbNull,
+ SqlDbType providerType,
+ string? udtSchema,
+ string? udtType,
+ string? xmlSchemaCollectionDatabase,
+ string? xmlSchemaCollectionOwningSchema,
+ string? xmlSchemaCollectionName)
+ {
+ if (string.IsNullOrEmpty(columnName))
{
- get;
+ throw new ArgumentException("columnName must be a nonempty string.");
}
- ///
- /// The name of the input row set's table.
- ///
- ///
- /// This may be different from the target table but usually they are identical.
- ///
- protected abstract string TableName
+ if (columnSize.HasValue && columnSize.Value <= 0)
{
- get;
+ throw new ArgumentOutOfRangeException("columnSize");
}
- ///
- /// Adds the input row set's schema to the object.
- ///
- ///
- /// Call
- /// to do this for each row.
- ///
- ///
- protected abstract void AddSchemaTableRows();
-
- ///
- /// For each , the optional columns that may have values.
- ///
- ///
- /// This is used for checking the parameters of .
- ///
- ///
- private static readonly Dictionary> AllowedOptionalColumnCombinations = new Dictionary>
+ if (numericPrecision.HasValue && numericPrecision.Value <= 0)
{
- { SqlDbType.BigInt, new List { } },
- { SqlDbType.Binary, new List { SchemaTableColumn.ColumnSize } },
- { SqlDbType.Bit, new List { } },
- { SqlDbType.Char, new List { SchemaTableColumn.ColumnSize } },
- { SqlDbType.Date, new List { } },
- { SqlDbType.DateTime, new List { } },
- { SqlDbType.DateTime2, new List { SchemaTableColumn.NumericPrecision } },
- { SqlDbType.DateTimeOffset, new List { SchemaTableColumn.NumericPrecision } },
- { SqlDbType.Decimal, new List { SchemaTableColumn.NumericPrecision, SchemaTableColumn.NumericScale } },
- { SqlDbType.Float, new List { SchemaTableColumn.NumericPrecision, SchemaTableColumn.NumericScale } },
- { SqlDbType.Image, new List { } },
- { SqlDbType.Int, new List { } },
- { SqlDbType.Money, new List { } },
- { SqlDbType.NChar, new List { SchemaTableColumn.ColumnSize } },
- { SqlDbType.NText, new List { } },
- { SqlDbType.NVarChar, new List { SchemaTableColumn.ColumnSize } },
- { SqlDbType.Real, new List { } },
- { SqlDbType.SmallDateTime, new List { } },
- { SqlDbType.SmallInt, new List { } },
- { SqlDbType.SmallMoney, new List { } },
- { SqlDbType.Structured, new List { } },
- { SqlDbType.Text, new List { } },
- { SqlDbType.Time, new List { SchemaTableColumn.NumericPrecision } },
- { SqlDbType.Timestamp, new List { } },
- { SqlDbType.TinyInt, new List { } },
- { SqlDbType.Udt, new List { BulkDataReader.DataTypeNameSchemaColumn } },
- { SqlDbType.UniqueIdentifier, new List { } },
- { SqlDbType.VarBinary, new List { SchemaTableColumn.ColumnSize } },
- { SqlDbType.VarChar, new List { SchemaTableColumn.ColumnSize } },
- { SqlDbType.Variant, new List { } },
- { SqlDbType.Xml, new List { BulkDataReader.XmlSchemaCollectionDatabaseSchemaColumn, BulkDataReader.XmlSchemaCollectionOwningSchemaSchemaColumn, BulkDataReader.XmlSchemaCollectionNameSchemaColumn } }
- };
+ throw new ArgumentOutOfRangeException("numericPrecision");
+ }
- ///
- /// A helper method to support .
- ///
- ///
- /// This methods does extensive argument checks. These errors will cause hard to diagnose exceptions in latter
- /// processing so it is important to detect them when they can be easily associated with the code defect.
- ///
- ///
- /// The combination of values for the parameters is not supported.
- ///
- ///
- /// A null value for the parameter is not supported.
- ///
- ///
- /// The name of the column.
- ///
- ///
- /// The size of the column which may be null if not applicable.
- ///
- ///
- /// The precision of the column which may be null if not applicable.
- ///
- ///
- /// The scale of the column which may be null if not applicable.
- ///
- ///
- /// Are the column values unique (i.e. never duplicated)?
- ///
- ///
- /// Is the column part of the primary key?
- ///
- ///
- /// Is the column nullable (i.e. optional)?
- ///
- ///
- /// The corresponding .
- ///
- ///
- /// The schema name of the UDT.
- ///
- ///
- /// The type name of the UDT.
- ///
- ///
- /// For XML columns the schema collection's database name. Otherwise, null.
- ///
- ///
- /// For XML columns the schema collection's schema name. Otherwise, null.
- ///
- ///
- /// For XML columns the schema collection's name. Otherwise, null.
- ///
- ///
- protected void AddSchemaTableRow(string columnName,
- int? columnSize,
- short? numericPrecision,
- short? numericScale,
- bool isUnique,
- bool isKey,
- bool allowDbNull,
- SqlDbType providerType,
- string? udtSchema,
- string? udtType,
- string? xmlSchemaCollectionDatabase,
- string? xmlSchemaCollectionOwningSchema,
- string? xmlSchemaCollectionName)
+ if (numericScale.HasValue && numericScale.Value < 0)
{
- if (string.IsNullOrEmpty(columnName))
- {
- throw new ArgumentException("columnName must be a nonempty string.");
- }
- else if (columnSize.HasValue && columnSize.Value <= 0)
- {
- throw new ArgumentOutOfRangeException("columnSize");
- }
- else if (numericPrecision.HasValue && numericPrecision.Value <= 0)
- {
- throw new ArgumentOutOfRangeException("numericPrecision");
- }
- else if (numericScale.HasValue && numericScale.Value < 0)
- {
- throw new ArgumentOutOfRangeException("columnSize");
- }
+ throw new ArgumentOutOfRangeException("columnSize");
+ }
- List? allowedOptionalColumnList;
- if (BulkDataReader.AllowedOptionalColumnCombinations.TryGetValue(providerType, out allowedOptionalColumnList))
+ if (AllowedOptionalColumnCombinations.TryGetValue(providerType, out List? allowedOptionalColumnList))
+ {
+ if ((columnSize.HasValue && !allowedOptionalColumnList.Contains(SchemaTableColumn.ColumnSize)) ||
+ (numericPrecision.HasValue &&
+ !allowedOptionalColumnList.Contains(SchemaTableColumn.NumericPrecision)) ||
+ (numericScale.HasValue && !allowedOptionalColumnList.Contains(SchemaTableColumn.NumericScale)) ||
+ (udtSchema != null && !allowedOptionalColumnList.Contains(DataTypeNameSchemaColumn)) ||
+ (udtType != null && !allowedOptionalColumnList.Contains(DataTypeNameSchemaColumn)) ||
+ (xmlSchemaCollectionDatabase != null &&
+ !allowedOptionalColumnList.Contains(XmlSchemaCollectionDatabaseSchemaColumn)) ||
+ (xmlSchemaCollectionOwningSchema != null &&
+ !allowedOptionalColumnList.Contains(XmlSchemaCollectionOwningSchemaSchemaColumn)) ||
+ (xmlSchemaCollectionName != null &&
+ !allowedOptionalColumnList.Contains(XmlSchemaCollectionNameSchemaColumn)))
{
- if ((columnSize.HasValue && !allowedOptionalColumnList.Contains(SchemaTableColumn.ColumnSize)) ||
- (numericPrecision.HasValue && !allowedOptionalColumnList.Contains(SchemaTableColumn.NumericPrecision)) ||
- (numericScale.HasValue && !allowedOptionalColumnList.Contains(SchemaTableColumn.NumericScale)) ||
- (udtSchema != null && !allowedOptionalColumnList.Contains(BulkDataReader.DataTypeNameSchemaColumn)) ||
- (udtType != null && !allowedOptionalColumnList.Contains(BulkDataReader.DataTypeNameSchemaColumn)) ||
- (xmlSchemaCollectionDatabase != null && !allowedOptionalColumnList.Contains(BulkDataReader.XmlSchemaCollectionDatabaseSchemaColumn)) ||
- (xmlSchemaCollectionOwningSchema != null && !allowedOptionalColumnList.Contains(BulkDataReader.XmlSchemaCollectionOwningSchemaSchemaColumn)) ||
- (xmlSchemaCollectionName != null && !allowedOptionalColumnList.Contains(BulkDataReader.XmlSchemaCollectionNameSchemaColumn)))
+ throw new ArgumentException("Columns are set that are incompatible with the value of providerType.");
+ }
+ }
+ else
+ {
+ throw new ArgumentException("providerType is unsupported.");
+ }
+
+ Type dataType; // Corresponding CLR type.
+ string dataTypeName; // Corresponding SQL Server type.
+ var isLong = false; // Is the column a large value column (e.g. nvarchar(max))?
+
+ switch (providerType)
+ {
+ case SqlDbType.BigInt:
+ dataType = typeof(long);
+ dataTypeName = "bigint";
+ break;
+
+ case SqlDbType.Binary:
+ dataType = typeof(byte[]);
+
+ if (!columnSize.HasValue)
{
- throw new ArgumentException("Columns are set that are incompatible with the value of providerType.");
+ throw new ArgumentException("columnSize must be specified for \"binary\" type columns.");
}
- }
- else
- {
- throw new ArgumentException("providerType is unsupported.");
- }
- Type dataType; // Corresponding CLR type.
- string dataTypeName; // Corresponding SQL Server type.
- bool isLong = false; // Is the column a large value column (e.g. nvarchar(max))?
+ if (columnSize > 8000)
+ {
+ throw new ArgumentOutOfRangeException("columnSize");
+ }
- switch (providerType)
- {
- case SqlDbType.BigInt:
- dataType = typeof(long);
- dataTypeName = "bigint";
- break;
+ dataTypeName = string.Format(
+ CultureInfo.InvariantCulture,
+ "binary({0})",
+ columnSize.Value);
+ break;
- case SqlDbType.Binary:
- dataType = typeof(byte[]);
+ case SqlDbType.Bit:
+ dataType = typeof(bool);
+ dataTypeName = "bit";
+ break;
- if (!columnSize.HasValue)
- {
- throw new ArgumentException("columnSize must be specified for \"binary\" type columns.");
- }
- else if (columnSize > 8000)
- {
- throw new ArgumentOutOfRangeException("columnSize");
- }
+ case SqlDbType.Char:
+ dataType = typeof(string);
- dataTypeName = string.Format(CultureInfo.InvariantCulture,
- "binary({0})",
- columnSize.Value);
- break;
+ if (!columnSize.HasValue)
+ {
+ throw new ArgumentException("columnSize must be specified for \"char\" type columns.");
+ }
- case SqlDbType.Bit:
- dataType = typeof(bool);
- dataTypeName = "bit";
- break;
+ if (columnSize > 8000)
+ {
+ throw new ArgumentOutOfRangeException("columnSize");
+ }
- case SqlDbType.Char:
- dataType = typeof(string);
+ dataTypeName = string.Format(
+ CultureInfo.InvariantCulture,
+ "char({0})",
+ columnSize.Value);
+ break;
- if (!columnSize.HasValue)
- {
- throw new ArgumentException("columnSize must be specified for \"char\" type columns.");
- }
- else if (columnSize > 8000)
- {
- throw new ArgumentOutOfRangeException("columnSize");
- }
+ case SqlDbType.Date:
+ dataType = typeof(DateTime);
+ dataTypeName = "date";
+ break;
- dataTypeName = string.Format(CultureInfo.InvariantCulture,
- "char({0})",
- columnSize.Value);
- break;
+ case SqlDbType.DateTime:
+ dataType = typeof(DateTime);
+ dataTypeName = "datetime";
+ break;
- case SqlDbType.Date:
- dataType = typeof(DateTime);
- dataTypeName = "date";
- break;
+ case SqlDbType.DateTime2:
+ dataType = typeof(DateTime);
- case SqlDbType.DateTime:
- dataType = typeof(DateTime);
- dataTypeName = "datetime";
- break;
-
- case SqlDbType.DateTime2:
- dataType = typeof(DateTime);
-
- if (numericPrecision.HasValue)
- {
- if (numericPrecision.Value > 7)
- {
- throw new ArgumentOutOfRangeException("numericPrecision");
- }
-
- dataTypeName = string.Format(CultureInfo.InvariantCulture,
- "datetime2({0})",
- numericPrecision.Value);
- }
- else
- {
- dataTypeName = "datetime2";
- }
- break;
-
- case SqlDbType.DateTimeOffset:
- dataType = typeof(DateTimeOffset);
-
- if (numericPrecision.HasValue)
- {
- if (numericPrecision.Value > 7)
- {
- throw new ArgumentOutOfRangeException("numericPrecision");
- }
-
- dataTypeName = string.Format(CultureInfo.InvariantCulture,
- "datetimeoffset({0})",
- numericPrecision.Value);
- }
- else
- {
- dataTypeName = "datetimeoffset";
- }
- break;
-
- case SqlDbType.Decimal:
- dataType = typeof(decimal);
-
- if (!numericPrecision.HasValue || !numericScale.HasValue)
- {
- throw new ArgumentException("numericPrecision and numericScale must be specified for \"decimal\" type columns.");
- }
- else if (numericPrecision > 38)
- {
- throw new ArgumentOutOfRangeException("numericPrecision");
- }
- else if (numericScale.Value > numericPrecision.Value)
- {
- throw new ArgumentException("numericScale must not be larger than numericPrecision for \"decimal\" type columns.");
- }
-
- dataTypeName = string.Format(CultureInfo.InvariantCulture,
- "decimal({0}, {1})",
- numericPrecision.Value,
- numericScale.Value);
- break;
-
- case SqlDbType.Float:
- dataType = typeof(double);
-
- if (!numericPrecision.HasValue)
- {
- throw new ArgumentException("numericPrecision must be specified for \"float\" type columns");
- }
- else if (numericPrecision > 53)
+ if (numericPrecision.HasValue)
+ {
+ if (numericPrecision.Value > 7)
{
throw new ArgumentOutOfRangeException("numericPrecision");
}
- dataTypeName = string.Format(CultureInfo.InvariantCulture,
- "float({0})",
- numericPrecision.Value);
- break;
+ dataTypeName = string.Format(
+ CultureInfo.InvariantCulture,
+ "datetime2({0})",
+ numericPrecision.Value);
+ }
+ else
+ {
+ dataTypeName = "datetime2";
+ }
- case SqlDbType.Image:
- dataType = typeof(byte[]);
- dataTypeName = "image";
- break;
+ break;
- case SqlDbType.Int:
- dataType = typeof(int);
- dataTypeName = "int";
- break;
+ case SqlDbType.DateTimeOffset:
+ dataType = typeof(DateTimeOffset);
- case SqlDbType.Money:
- dataType = typeof(decimal);
- dataTypeName = "money";
- break;
-
- case SqlDbType.NChar:
- dataType = typeof(string);
-
- if (!columnSize.HasValue)
+ if (numericPrecision.HasValue)
+ {
+ if (numericPrecision.Value > 7)
{
- throw new ArgumentException("columnSize must be specified for \"nchar\" type columns");
+ throw new ArgumentOutOfRangeException("numericPrecision");
}
- else if (columnSize > 4000)
+
+ dataTypeName = string.Format(
+ CultureInfo.InvariantCulture,
+ "datetimeoffset({0})",
+ numericPrecision.Value);
+ }
+ else
+ {
+ dataTypeName = "datetimeoffset";
+ }
+
+ break;
+
+ case SqlDbType.Decimal:
+ dataType = typeof(decimal);
+
+ if (!numericPrecision.HasValue || !numericScale.HasValue)
+ {
+ throw new ArgumentException(
+ "numericPrecision and numericScale must be specified for \"decimal\" type columns.");
+ }
+
+ if (numericPrecision > 38)
+ {
+ throw new ArgumentOutOfRangeException("numericPrecision");
+ }
+
+ if (numericScale.Value > numericPrecision.Value)
+ {
+ throw new ArgumentException(
+ "numericScale must not be larger than numericPrecision for \"decimal\" type columns.");
+ }
+
+ dataTypeName = string.Format(
+ CultureInfo.InvariantCulture,
+ "decimal({0}, {1})",
+ numericPrecision.Value,
+ numericScale.Value);
+ break;
+
+ case SqlDbType.Float:
+ dataType = typeof(double);
+
+ if (!numericPrecision.HasValue)
+ {
+ throw new ArgumentException("numericPrecision must be specified for \"float\" type columns");
+ }
+
+ if (numericPrecision > 53)
+ {
+ throw new ArgumentOutOfRangeException("numericPrecision");
+ }
+
+ dataTypeName = string.Format(
+ CultureInfo.InvariantCulture,
+ "float({0})",
+ numericPrecision.Value);
+ break;
+
+ case SqlDbType.Image:
+ dataType = typeof(byte[]);
+ dataTypeName = "image";
+ break;
+
+ case SqlDbType.Int:
+ dataType = typeof(int);
+ dataTypeName = "int";
+ break;
+
+ case SqlDbType.Money:
+ dataType = typeof(decimal);
+ dataTypeName = "money";
+ break;
+
+ case SqlDbType.NChar:
+ dataType = typeof(string);
+
+ if (!columnSize.HasValue)
+ {
+ throw new ArgumentException("columnSize must be specified for \"nchar\" type columns");
+ }
+
+ if (columnSize > 4000)
+ {
+ throw new ArgumentOutOfRangeException("columnSize");
+ }
+
+ dataTypeName = string.Format(
+ CultureInfo.InvariantCulture,
+ "nchar({0})",
+ columnSize.Value);
+ break;
+
+ case SqlDbType.NText:
+ dataType = typeof(string);
+ dataTypeName = "ntext";
+ break;
+
+ case SqlDbType.NVarChar:
+ dataType = typeof(string);
+
+ if (columnSize.HasValue)
+ {
+ if (columnSize > 4000)
{
throw new ArgumentOutOfRangeException("columnSize");
}
- dataTypeName = string.Format(CultureInfo.InvariantCulture,
- "nchar({0})",
- columnSize.Value);
- break;
+ dataTypeName = string.Format(
+ CultureInfo.InvariantCulture,
+ "nvarchar({0})",
+ columnSize.Value);
+ }
+ else
+ {
+ isLong = true;
- case SqlDbType.NText:
- dataType = typeof(string);
- dataTypeName = "ntext";
- break;
+ dataTypeName = "nvarchar(max)";
+ }
- case SqlDbType.NVarChar:
- dataType = typeof(string);
+ break;
- if (columnSize.HasValue)
+ case SqlDbType.Real:
+ dataType = typeof(float);
+ dataTypeName = "real";
+ break;
+
+ case SqlDbType.SmallDateTime:
+ dataType = typeof(DateTime);
+ dataTypeName = "smalldatetime";
+ break;
+
+ case SqlDbType.SmallInt:
+ dataType = typeof(short);
+ dataTypeName = "smallint";
+ break;
+
+ case SqlDbType.SmallMoney:
+ dataType = typeof(decimal);
+ dataTypeName = "smallmoney";
+ break;
+
+ // SqlDbType.Structured not supported because it related to nested rowsets.
+
+ case SqlDbType.Text:
+ dataType = typeof(string);
+ dataTypeName = "text";
+ break;
+
+ case SqlDbType.Time:
+ dataType = typeof(TimeSpan);
+
+ if (numericPrecision.HasValue)
+ {
+ if (numericPrecision > 7)
{
- if (columnSize > 4000)
- {
- throw new ArgumentOutOfRangeException("columnSize");
- }
-
- dataTypeName = string.Format(CultureInfo.InvariantCulture,
- "nvarchar({0})",
- columnSize.Value);
- }
- else
- {
- isLong = true;
-
- dataTypeName = "nvarchar(max)";
- }
- break;
-
- case SqlDbType.Real:
- dataType = typeof(float);
- dataTypeName = "real";
- break;
-
- case SqlDbType.SmallDateTime:
- dataType = typeof(DateTime);
- dataTypeName = "smalldatetime";
- break;
-
- case SqlDbType.SmallInt:
- dataType = typeof(short);
- dataTypeName = "smallint";
- break;
-
- case SqlDbType.SmallMoney:
- dataType = typeof(decimal);
- dataTypeName = "smallmoney";
- break;
-
- // SqlDbType.Structured not supported because it related to nested rowsets.
-
- case SqlDbType.Text:
- dataType = typeof(string);
- dataTypeName = "text";
- break;
-
- case SqlDbType.Time:
- dataType = typeof(TimeSpan);
-
- if (numericPrecision.HasValue)
- {
- if (numericPrecision > 7)
- {
- throw new ArgumentOutOfRangeException("numericPrecision");
- }
-
- dataTypeName = string.Format(CultureInfo.InvariantCulture,
- "time({0})",
- numericPrecision.Value);
- }
- else
- {
- dataTypeName = "time";
- }
- break;
-
-
- // SqlDbType.Timestamp not supported because rowversions are not settable.
-
- case SqlDbType.TinyInt:
- dataType = typeof(byte);
- dataTypeName = "tinyint";
- break;
-
- case SqlDbType.Udt:
- if (string.IsNullOrEmpty(udtSchema))
- {
- throw new ArgumentException("udtSchema must be nonnull and nonempty for \"UDT\" columns.");
- }
- else if (string.IsNullOrEmpty(udtType))
- {
- throw new ArgumentException("udtType must be nonnull and nonempty for \"UDT\" columns.");
+ throw new ArgumentOutOfRangeException("numericPrecision");
}
- dataType = typeof(object);
- using (SqlCommandBuilder commandBuilder = new SqlCommandBuilder())
+ dataTypeName = string.Format(
+ CultureInfo.InvariantCulture,
+ "time({0})",
+ numericPrecision.Value);
+ }
+ else
+ {
+ dataTypeName = "time";
+ }
+
+ break;
+
+
+ // SqlDbType.Timestamp not supported because rowversions are not settable.
+
+ case SqlDbType.TinyInt:
+ dataType = typeof(byte);
+ dataTypeName = "tinyint";
+ break;
+
+ case SqlDbType.Udt:
+ if (string.IsNullOrEmpty(udtSchema))
+ {
+ throw new ArgumentException("udtSchema must be nonnull and nonempty for \"UDT\" columns.");
+ }
+
+ if (string.IsNullOrEmpty(udtType))
+ {
+ throw new ArgumentException("udtType must be nonnull and nonempty for \"UDT\" columns.");
+ }
+
+ dataType = typeof(object);
+ using (var commandBuilder = new SqlCommandBuilder())
+ {
+ dataTypeName = commandBuilder.QuoteIdentifier(udtSchema) + "." +
+ commandBuilder.QuoteIdentifier(udtType);
+ }
+
+ break;
+
+ case SqlDbType.UniqueIdentifier:
+ dataType = typeof(Guid);
+ dataTypeName = "uniqueidentifier";
+ break;
+
+ case SqlDbType.VarBinary:
+ dataType = typeof(byte[]);
+
+ if (columnSize.HasValue)
+ {
+ if (columnSize > 8000)
{
- dataTypeName = commandBuilder.QuoteIdentifier(udtSchema) + "." + commandBuilder.QuoteIdentifier(udtType);
+ throw new ArgumentOutOfRangeException("columnSize");
}
- break;
- case SqlDbType.UniqueIdentifier:
- dataType = typeof(Guid);
- dataTypeName = "uniqueidentifier";
- break;
+ dataTypeName = string.Format(
+ CultureInfo.InvariantCulture,
+ "varbinary({0})",
+ columnSize.Value);
+ }
+ else
+ {
+ isLong = true;
- case SqlDbType.VarBinary:
- dataType = typeof(byte[]);
+ dataTypeName = "varbinary(max)";
+ }
- if (columnSize.HasValue)
+ break;
+
+ case SqlDbType.VarChar:
+ dataType = typeof(string);
+
+ if (columnSize.HasValue)
+ {
+ if (columnSize > 8000)
{
- if (columnSize > 8000)
- {
- throw new ArgumentOutOfRangeException("columnSize");
- }
-
- dataTypeName = string.Format(CultureInfo.InvariantCulture,
- "varbinary({0})",
- columnSize.Value);
+ throw new ArgumentOutOfRangeException("columnSize");
}
- else
+
+ dataTypeName = string.Format(
+ CultureInfo.InvariantCulture,
+ "varchar({0})",
+ columnSize.Value);
+ }
+ else
+ {
+ isLong = true;
+
+ dataTypeName = "varchar(max)";
+ }
+
+ break;
+
+ case SqlDbType.Variant:
+ dataType = typeof(object);
+ dataTypeName = "sql_variant";
+ break;
+
+ case SqlDbType.Xml:
+ dataType = typeof(string);
+
+ if (xmlSchemaCollectionName == null)
+ {
+ if (xmlSchemaCollectionDatabase != null || xmlSchemaCollectionOwningSchema != null)
{
- isLong = true;
-
- dataTypeName = "varbinary(max)";
+ throw new ArgumentException(
+ "xmlSchemaCollectionDatabase and xmlSchemaCollectionOwningSchema must be null if xmlSchemaCollectionName is null for \"xml\" columns.");
}
- break;
- case SqlDbType.VarChar:
- dataType = typeof(string);
-
- if (columnSize.HasValue)
+ dataTypeName = "xml";
+ }
+ else
+ {
+ if (xmlSchemaCollectionName.Length == 0)
{
- if (columnSize > 8000)
- {
- throw new ArgumentOutOfRangeException("columnSize");
- }
-
- dataTypeName = string.Format(CultureInfo.InvariantCulture,
- "varchar({0})",
- columnSize.Value);
+ throw new ArgumentException(
+ "xmlSchemaCollectionName must be nonempty or null for \"xml\" columns.");
}
- else
+
+ if (xmlSchemaCollectionDatabase != null &&
+ xmlSchemaCollectionDatabase.Length == 0)
{
- isLong = true;
-
- dataTypeName = "varchar(max)";
+ throw new ArgumentException(
+ "xmlSchemaCollectionDatabase must be null or nonempty for \"xml\" columns.");
}
- break;
- case SqlDbType.Variant:
- dataType = typeof(object);
- dataTypeName = "sql_variant";
- break;
-
- case SqlDbType.Xml:
- dataType = typeof(string);
-
- if (xmlSchemaCollectionName == null)
+ if (xmlSchemaCollectionOwningSchema != null &&
+ xmlSchemaCollectionOwningSchema.Length == 0)
{
- if (xmlSchemaCollectionDatabase != null || xmlSchemaCollectionOwningSchema != null)
- {
- throw new ArgumentException("xmlSchemaCollectionDatabase and xmlSchemaCollectionOwningSchema must be null if xmlSchemaCollectionName is null for \"xml\" columns.");
- }
-
- dataTypeName = "xml";
+ throw new ArgumentException(
+ "xmlSchemaCollectionOwningSchema must be null or nonempty for \"xml\" columns.");
}
- else
+
+ var schemaCollection = new StringBuilder("xml(");
+
+ if (xmlSchemaCollectionDatabase != null)
{
- if (xmlSchemaCollectionName.Length == 0)
- {
- throw new ArgumentException("xmlSchemaCollectionName must be nonempty or null for \"xml\" columns.");
- }
- else if (xmlSchemaCollectionDatabase != null &&
- xmlSchemaCollectionDatabase.Length == 0)
- {
- throw new ArgumentException("xmlSchemaCollectionDatabase must be null or nonempty for \"xml\" columns.");
- }
- else if (xmlSchemaCollectionOwningSchema != null &&
- xmlSchemaCollectionOwningSchema.Length == 0)
- {
- throw new ArgumentException("xmlSchemaCollectionOwningSchema must be null or nonempty for \"xml\" columns.");
- }
-
- System.Text.StringBuilder schemaCollection = new System.Text.StringBuilder("xml(");
-
- if (xmlSchemaCollectionDatabase != null)
- {
- schemaCollection.Append("[" + xmlSchemaCollectionDatabase + "]");
- }
-
- schemaCollection.Append("[" + (xmlSchemaCollectionOwningSchema == null ? SchemaName : xmlSchemaCollectionOwningSchema) + "]");
- schemaCollection.Append("[" + xmlSchemaCollectionName + "]");
-
- dataTypeName = schemaCollection.ToString();
+ schemaCollection.Append("[" + xmlSchemaCollectionDatabase + "]");
}
- break;
- default:
- throw new ArgumentOutOfRangeException("providerType");
+ schemaCollection.Append("[" + (xmlSchemaCollectionOwningSchema ?? SchemaName) + "]");
+ schemaCollection.Append("[" + xmlSchemaCollectionName + "]");
- }
+ dataTypeName = schemaCollection.ToString();
+ }
- this._schemaTable?.Rows.Add(columnName,
- _schemaTable.Rows.Count,
- columnSize,
- numericPrecision,
- numericScale,
- isUnique,
- isKey,
- "TraceServer",
- "TraceWarehouse",
- columnName,
- SchemaName,
- TableName,
- dataType,
- allowDbNull,
- providerType,
- false, // isAliased
- false, // isExpression
- false, // isIdentity,
- false, // isAutoIncrement,
- false, // isRowVersion,
- false, // isHidden,
- isLong,
- true, // isReadOnly,
- dataType,
- dataTypeName,
- xmlSchemaCollectionDatabase,
- xmlSchemaCollectionOwningSchema,
- xmlSchemaCollectionName);
+ break;
- this._columnMappings?.Add(new SqlBulkCopyColumnMapping(columnName, columnName));
+ default:
+ throw new ArgumentOutOfRangeException("providerType");
}
- #endregion
+ _schemaTable?.Rows.Add(
+ columnName,
+ _schemaTable.Rows.Count,
+ columnSize,
+ numericPrecision,
+ numericScale,
+ isUnique,
+ isKey,
+ "TraceServer",
+ "TraceWarehouse",
+ columnName,
+ SchemaName,
+ TableName,
+ dataType,
+ allowDbNull,
+ providerType,
+ false, // isAliased
+ false, // isExpression
+ false, // isIdentity,
+ false, // isAutoIncrement,
+ false, // isRowVersion,
+ false, // isHidden,
+ isLong,
+ true, // isReadOnly,
+ dataType,
+ dataTypeName,
+ xmlSchemaCollectionDatabase,
+ xmlSchemaCollectionOwningSchema,
+ xmlSchemaCollectionName);
- #region Constructors
+ _columnMappings?.Add(new SqlBulkCopyColumnMapping(columnName, columnName));
+ }
- private const string IsIdentitySchemaColumn = "IsIdentity";
+ #endregion
- private const string DataTypeNameSchemaColumn = "DataTypeName";
+ #region Constructors
- private const string XmlSchemaCollectionDatabaseSchemaColumn = "XmlSchemaCollectionDatabase";
+ private const string IsIdentitySchemaColumn = "IsIdentity";
- private const string XmlSchemaCollectionOwningSchemaSchemaColumn = "XmlSchemaCollectionOwningSchema";
+ private const string DataTypeNameSchemaColumn = "DataTypeName";
- private const string XmlSchemaCollectionNameSchemaColumn = "XmlSchemaCollectionName";
+ private const string XmlSchemaCollectionDatabaseSchemaColumn = "XmlSchemaCollectionDatabase";
- ///
- /// Constructor.
- ///
- protected BulkDataReader()
+ private const string XmlSchemaCollectionOwningSchemaSchemaColumn = "XmlSchemaCollectionOwningSchema";
+
+ private const string XmlSchemaCollectionNameSchemaColumn = "XmlSchemaCollectionName";
+
+ ///
+ /// Constructor.
+ ///
+ protected BulkDataReader()
+ {
+ _schemaTable.Locale = CultureInfo.InvariantCulture;
+
+ DataColumnCollection columns = _schemaTable.Columns;
+
+ columns.Add(SchemaTableColumn.ColumnName, typeof(string));
+ columns.Add(SchemaTableColumn.ColumnOrdinal, typeof(int));
+ columns.Add(SchemaTableColumn.ColumnSize, typeof(int));
+ columns.Add(SchemaTableColumn.NumericPrecision, typeof(short));
+ columns.Add(SchemaTableColumn.NumericScale, typeof(short));
+ columns.Add(SchemaTableColumn.IsUnique, typeof(bool));
+ columns.Add(SchemaTableColumn.IsKey, typeof(bool));
+ columns.Add(SchemaTableOptionalColumn.BaseServerName, typeof(string));
+ columns.Add(SchemaTableOptionalColumn.BaseCatalogName, typeof(string));
+ columns.Add(SchemaTableColumn.BaseColumnName, typeof(string));
+ columns.Add(SchemaTableColumn.BaseSchemaName, typeof(string));
+ columns.Add(SchemaTableColumn.BaseTableName, typeof(string));
+ columns.Add(SchemaTableColumn.DataType, typeof(Type));
+ columns.Add(SchemaTableColumn.AllowDBNull, typeof(bool));
+ columns.Add(SchemaTableColumn.ProviderType, typeof(int));
+ columns.Add(SchemaTableColumn.IsAliased, typeof(bool));
+ columns.Add(SchemaTableColumn.IsExpression, typeof(bool));
+ columns.Add(IsIdentitySchemaColumn, typeof(bool));
+ columns.Add(SchemaTableOptionalColumn.IsAutoIncrement, typeof(bool));
+ columns.Add(SchemaTableOptionalColumn.IsRowVersion, typeof(bool));
+ columns.Add(SchemaTableOptionalColumn.IsHidden, typeof(bool));
+ columns.Add(SchemaTableColumn.IsLong, typeof(bool));
+ columns.Add(SchemaTableOptionalColumn.IsReadOnly, typeof(bool));
+ columns.Add(SchemaTableOptionalColumn.ProviderSpecificDataType, typeof(Type));
+ columns.Add(DataTypeNameSchemaColumn, typeof(string));
+ columns.Add(XmlSchemaCollectionDatabaseSchemaColumn, typeof(string));
+ columns.Add(XmlSchemaCollectionOwningSchemaSchemaColumn, typeof(string));
+ columns.Add(XmlSchemaCollectionNameSchemaColumn, typeof(string));
+ }
+
+ #endregion
+
+ #region IDataReader
+
+ ///
+ /// Gets a value indicating the depth of nesting for the current row. (Inherited from .)
+ ///
+ ///
+ /// does not support nested result sets so this method always returns 0.
+ ///
+ ///
+ public int Depth => 0;
+
+ ///
+ /// Gets the number of columns in the current row. (Inherited from .)
+ ///
+ ///
+ public int FieldCount => GetSchemaTable().Rows.Count;
+
+ ///
+ /// Is the bulk copy process open?
+ ///
+ private bool _isOpen = true;
+
+ ///
+ /// Gets a value indicating whether the data reader is closed. (Inherited from .)
+ ///
+ ///
+ public bool IsClosed => !_isOpen;
+
+ ///
+ /// Gets the column located at the specified index. (Inherited from .)
+ ///
+ ///
+ /// No column with the specified index was found.
+ ///
+ ///
+ /// The zero-based index of the column to get.
+ ///
+ ///
+ /// The column located at the specified index as an .
+ ///
+ ///
+ public object this[int i] => GetValue(i);
+
+ ///
+ /// Gets the column with the specified name. (Inherited from .)
+ ///
+ ///
+ /// No column with the specified name was found.
+ ///
+ ///
+ /// The name of the column to find.
+ ///
+ ///
+ /// The column located at the specified name as an .
+ ///
+ ///
+ public object this[string name] => GetValue(GetOrdinal(name));
+
+ ///
+ /// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement. (Inherited from
+ /// .)
+ ///
+ ///
+ /// Always returns -1 which is the expected behaviour for statements.
+ ///
+ ///
+ public virtual int RecordsAffected => -1;
+
+ ///
+ /// Closes the . (Inherited from .)
+ ///
+ ///
+ public void Close() => _isOpen = false;
+
+ ///
+ /// Gets the value of the specified column as a . (Inherited from .)
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The value of the column.
+ ///
+ ///
+ public bool GetBoolean(int i) => (bool)GetValue(i);
+
+ ///
+ /// Gets the value of the specified column as a . (Inherited from .)
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The value of the column.
+ ///
+ ///
+ public byte GetByte(int i) => (byte)GetValue(i);
+
+ ///
+ /// Reads a stream of bytes from the specified column offset into the buffer as an array, starting at the given buffer
+ /// offset.
+ /// (Inherited from .)
+ ///
+ ///
+ /// If you pass a buffer that is null, returns the length of the row in bytes.
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The index within the field from which to start the read operation.
+ ///
+ ///
+ /// The buffer into which to read the stream of bytes.
+ ///
+ ///
+ /// The index for buffer to start the read operation.
+ ///
+ ///
+ /// The number of bytes to read.
+ ///
+ ///
+ /// The actual number of bytes read.
+ ///
+ ///
+ public long GetBytes(
+ int i,
+ long fieldOffset,
+ byte[]? buffer,
+ int bufferoffset,
+ int length)
+ {
+ var data = (byte[])GetValue(i);
+
+ if (buffer != null)
{
- this._schemaTable.Locale = System.Globalization.CultureInfo.InvariantCulture;
-
- DataColumnCollection columns = _schemaTable.Columns;
-
- columns.Add(SchemaTableColumn.ColumnName, typeof(string));
- columns.Add(SchemaTableColumn.ColumnOrdinal, typeof(int));
- columns.Add(SchemaTableColumn.ColumnSize, typeof(int));
- columns.Add(SchemaTableColumn.NumericPrecision, typeof(short));
- columns.Add(SchemaTableColumn.NumericScale, typeof(short));
- columns.Add(SchemaTableColumn.IsUnique, typeof(bool));
- columns.Add(SchemaTableColumn.IsKey, typeof(bool));
- columns.Add(SchemaTableOptionalColumn.BaseServerName, typeof(string));
- columns.Add(SchemaTableOptionalColumn.BaseCatalogName, typeof(string));
- columns.Add(SchemaTableColumn.BaseColumnName, typeof(string));
- columns.Add(SchemaTableColumn.BaseSchemaName, typeof(string));
- columns.Add(SchemaTableColumn.BaseTableName, typeof(string));
- columns.Add(SchemaTableColumn.DataType, typeof(Type));
- columns.Add(SchemaTableColumn.AllowDBNull, typeof(bool));
- columns.Add(SchemaTableColumn.ProviderType, typeof(int));
- columns.Add(SchemaTableColumn.IsAliased, typeof(bool));
- columns.Add(SchemaTableColumn.IsExpression, typeof(bool));
- columns.Add(BulkDataReader.IsIdentitySchemaColumn, typeof(bool));
- columns.Add(SchemaTableOptionalColumn.IsAutoIncrement, typeof(bool));
- columns.Add(SchemaTableOptionalColumn.IsRowVersion, typeof(bool));
- columns.Add(SchemaTableOptionalColumn.IsHidden, typeof(bool));
- columns.Add(SchemaTableColumn.IsLong, typeof(bool));
- columns.Add(SchemaTableOptionalColumn.IsReadOnly, typeof(bool));
- columns.Add(SchemaTableOptionalColumn.ProviderSpecificDataType, typeof(Type));
- columns.Add(BulkDataReader.DataTypeNameSchemaColumn, typeof(string));
- columns.Add(BulkDataReader.XmlSchemaCollectionDatabaseSchemaColumn, typeof(string));
- columns.Add(BulkDataReader.XmlSchemaCollectionOwningSchemaSchemaColumn, typeof(string));
- columns.Add(BulkDataReader.XmlSchemaCollectionNameSchemaColumn, typeof(string));
+ Array.Copy(data, fieldOffset, buffer, bufferoffset, length);
}
- #endregion
+ return data.LongLength;
+ }
- #region IDataReader
+ ///
+ /// Gets the value of the specified column as a . (Inherited from .)
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The value of the column.
+ ///
+ ///
+ public char GetChar(int i)
+ {
+ char result;
- ///
- /// Gets a value indicating the depth of nesting for the current row. (Inherited from .)
- ///
- ///
- /// does not support nested result sets so this method always returns 0.
- ///
- ///
- public int Depth
+ var data = GetValue(i);
+ var dataAsChar = data as char?;
+
+ if (dataAsChar.HasValue)
{
- get { return 0; }
+ result = dataAsChar.Value;
+ }
+ else if (data is char[] dataAsCharArray &&
+ dataAsCharArray.Length == 1)
+ {
+ result = dataAsCharArray[0];
+ }
+ else if (data is string dataAsString &&
+ dataAsString.Length == 1)
+ {
+ result = dataAsString[0];
+ }
+ else
+ {
+ throw new InvalidOperationException("GetValue did not return a Char compatible type.");
}
- ///
- /// Gets the number of columns in the current row. (Inherited from .)
- ///
- ///
- public int FieldCount
+ return result;
+ }
+
+ ///
+ /// Reads a stream of characters from the specified column offset into the buffer as an array, starting at the given
+ /// buffer offset.
+ /// (Inherited from .)
+ ///
+ ///
+ /// If you pass a buffer that is null, returns the length of the row in bytes.
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The index within the field from which to start the read operation.
+ ///
+ ///
+ /// The buffer into which to read the stream of characters.
+ ///
+ ///
+ /// The index for buffer to start the read operation.
+ ///
+ ///
+ /// The number of characters to read.
+ ///
+ ///
+ /// The actual number of characters read.
+ ///
+ ///
+ public long GetChars(
+ int i,
+ long fieldoffset,
+ char[]? buffer,
+ int bufferoffset,
+ int length)
+ {
+ var data = GetValue(i);
+
+ var dataAsCharArray = data as char[];
+
+ if (data is string dataAsString)
{
- get { return GetSchemaTable().Rows.Count; }
+ dataAsCharArray = dataAsString.ToCharArray((int)fieldoffset, length);
+ }
+ else if (dataAsCharArray == null)
+ {
+ throw new InvalidOperationException("GetValue did not return either a Char array or a String.");
}
- ///
- /// Is the bulk copy process open?
- ///
- bool _isOpen = true;
-
- ///
- /// Gets a value indicating whether the data reader is closed. (Inherited from .)
- ///
- ///
- public bool IsClosed
+ if (buffer != null)
{
- get { return !_isOpen; }
+ Array.Copy(dataAsCharArray, fieldoffset, buffer, bufferoffset, length);
}
- ///
- /// Gets the column located at the specified index. (Inherited from .)
- ///
- ///
- /// No column with the specified index was found.
- ///
- ///
- /// The zero-based index of the column to get.
- ///
- ///
- /// The column located at the specified index as an .
- ///
- ///
- public object this[int i]
+ return dataAsCharArray.LongLength;
+ }
+
+ ///
+ /// Returns an IDataReader for the specified column ordinal. (Inherited from .)
+ ///
+ ///
+ /// does not support nested result sets so this method always returns null.
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The for the specified column ordinal (null).
+ ///
+ ///
+ public IDataReader GetData(int i)
+ {
+ if (i < 0 || i >= FieldCount)
{
- get { return GetValue(i); }
+ throw new ArgumentOutOfRangeException("i");
}
- ///
- /// Gets the column with the specified name. (Inherited from .)
- ///
- ///
- /// No column with the specified name was found.
- ///
- ///
- /// The name of the column to find.
- ///
- ///
- /// The column located at the specified name as an .
- ///
- ///
- public object this[string name]
+ return null!;
+ }
+
+ ///
+ /// The data type information for the specified field. (Inherited from .)
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The data type information for the specified field.
+ ///
+ ///
+ public string GetDataTypeName(int i) => GetFieldType(i).Name;
+
+ ///
+ /// Gets the value of the specified column as a . (Inherited from .)
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The value of the column.
+ ///
+ ///
+ public DateTime GetDateTime(int i) => (DateTime)GetValue(i);
+
+ ///
+ /// Gets the value of the specified column as a .
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The value of the column.
+ ///
+ public DateTimeOffset GetDateTimeOffset(int i) => (DateTimeOffset)GetValue(i);
+
+ ///
+ /// Gets the value of the specified column as a . (Inherited from .)
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The value of the column.
+ ///
+ ///
+ public decimal GetDecimal(int i) => (decimal)GetValue(i);
+
+ ///
+ /// Gets the value of the specified column as a . (Inherited from .)
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The value of the column.
+ ///
+ ///
+ public double GetDouble(int i) => (double)GetValue(i);
+
+ ///
+ /// Gets the information corresponding to the type of that would be returned
+ /// from .
+ /// (Inherited from .)
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The information corresponding to the type of that would be returned from
+ /// .
+ ///
+ ///
+ public Type GetFieldType(int i) => (Type)GetSchemaTable().Rows[i][SchemaTableColumn.DataType];
+
+ ///
+ /// Gets the value of the specified column as a . (Inherited from .)
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The value of the column.
+ ///
+ ///
+ public float GetFloat(int i) => (float)this[i];
+
+ ///
+ /// Gets the value of the specified column as a . (Inherited from .)
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The value of the column.
+ ///
+ ///
+ public Guid GetGuid(int i) => (Guid)GetValue(i);
+
+ ///
+ /// Gets the value of the specified column as a . (Inherited from .)
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The value of the column.
+ ///
+ ///
+ public short GetInt16(int i) => (short)GetValue(i);
+
+ ///
+ /// Gets the value of the specified column as a . (Inherited from .)
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The value of the column.
+ ///
+ ///
+ public int GetInt32(int i) => (int)GetValue(i);
+
+ ///
+ /// Gets the value of the specified column as a . (Inherited from .)
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The value of the column.
+ ///
+ ///
+ public long GetInt64(int i) => (long)GetValue(i);
+
+ ///
+ /// Gets the name for the field to find. (Inherited from .)
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The name of the field or the empty string (""), if there is no value to return.
+ ///
+ ///
+ public string GetName(int i) => (string)GetSchemaTable().Rows[i][SchemaTableColumn.ColumnName];
+
+ ///
+ /// Return the index of the named field. (Inherited from .)
+ ///
+ ///
+ /// The index of the named field was not found.
+ ///
+ ///
+ /// The name of the field to find.
+ ///
+ ///
+ /// The index of the named field.
+ ///
+ ///
+ public int GetOrdinal(string name)
+ {
+ if (name == null) // Empty strings are handled as a IndexOutOfRangeException.
{
- get { return GetValue(GetOrdinal(name)); }
+ throw new ArgumentNullException("name");
}
- ///
- /// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement. (Inherited from .)
- ///
- ///
- /// Always returns -1 which is the expected behaviour for statements.
- ///
- ///
- public virtual int RecordsAffected
- {
- get { return -1; }
- }
+ var result = -1;
- ///
- /// Closes the . (Inherited from .)
- ///
- ///
- public void Close()
- {
- this._isOpen = false;
- }
+ var rowCount = FieldCount;
- ///
- /// Gets the value of the specified column as a . (Inherited from .)
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The value of the column.
- ///
- ///
- public bool GetBoolean(int i)
- {
- return (bool)GetValue(i);
- }
+ DataRowCollection schemaRows = GetSchemaTable().Rows;
- ///
- /// Gets the value of the specified column as a . (Inherited from .)
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The value of the column.
- ///
- ///
- public byte GetByte(int i)
+ // Case sensitive search
+ for (var ordinal = 0; ordinal < rowCount; ordinal++)
{
- return (byte)GetValue(i);
- }
-
- ///
- /// Reads a stream of bytes from the specified column offset into the buffer as an array, starting at the given buffer offset.
- /// (Inherited from .)
- ///
- ///
- /// If you pass a buffer that is null, returns the length of the row in bytes.
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The index within the field from which to start the read operation.
- ///
- ///
- /// The buffer into which to read the stream of bytes.
- ///
- ///
- /// The index for buffer to start the read operation.
- ///
- ///
- /// The number of bytes to read.
- ///
- ///
- /// The actual number of bytes read.
- ///
- ///
- public long GetBytes(int i,
- long fieldOffset,
- byte[]? buffer,
- int bufferoffset,
- int length)
- {
- byte[] data = (byte[])GetValue(i);
-
- if (buffer != null)
+ if (string.Equals((string)schemaRows[ordinal][SchemaTableColumn.ColumnName], name, StringComparison.Ordinal))
{
- Array.Copy(data, fieldOffset, buffer, bufferoffset, length);
+ result = ordinal;
}
-
- return data.LongLength;
}
- ///
- /// Gets the value of the specified column as a . (Inherited from .)
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The value of the column.
- ///
- ///
- public char GetChar(int i)
+ if (result == -1)
{
- char result;
-
- object data = GetValue(i);
- char? dataAsChar = data as char?;
- char[]? dataAsCharArray = data as char[];
- string? dataAsString = data as string;
-
- if (dataAsChar.HasValue)
+ // Case insensitive search.
+ for (var ordinal = 0; ordinal < rowCount; ordinal++)
{
- result = dataAsChar.Value;
- }
- else if (dataAsCharArray != null &&
- dataAsCharArray.Length == 1)
- {
- result = dataAsCharArray[0];
- }
- else if (dataAsString != null &&
- dataAsString.Length == 1)
- {
- result = dataAsString[0];
- }
- else
- {
- throw new InvalidOperationException("GetValue did not return a Char compatible type.");
- }
-
- return result;
- }
-
- ///
- /// Reads a stream of characters from the specified column offset into the buffer as an array, starting at the given buffer offset.
- /// (Inherited from .)
- ///
- ///
- /// If you pass a buffer that is null, returns the length of the row in bytes.
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The index within the field from which to start the read operation.
- ///
- ///
- /// The buffer into which to read the stream of characters.
- ///
- ///
- /// The index for buffer to start the read operation.
- ///
- ///
- /// The number of characters to read.
- ///
- ///
- /// The actual number of characters read.
- ///
- ///
- public long GetChars(int i,
- long fieldoffset,
- char[]? buffer,
- int bufferoffset,
- int length)
- {
- object data = GetValue(i);
-
- string? dataAsString = data as string;
- char[]? dataAsCharArray = data as char[];
-
- if (dataAsString != null)
- {
- dataAsCharArray = dataAsString.ToCharArray((int)fieldoffset, length);
- }
- else if (dataAsCharArray == null)
- {
- throw new InvalidOperationException("GetValue did not return either a Char array or a String.");
- }
-
- if (buffer != null)
- {
- Array.Copy(dataAsCharArray, fieldoffset, buffer, bufferoffset, length);
- }
-
- return dataAsCharArray.LongLength;
- }
-
- ///
- /// Returns an IDataReader for the specified column ordinal. (Inherited from .)
- ///
- ///
- /// does not support nested result sets so this method always returns null.
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The for the specified column ordinal (null).
- ///
- ///
- public IDataReader GetData(int i)
- {
- if (i < 0 || i >= this.FieldCount)
- {
- throw new ArgumentOutOfRangeException("i");
- }
-
- return null!;
- }
-
- ///
- /// The data type information for the specified field. (Inherited from .)
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The data type information for the specified field.
- ///
- ///
- public string GetDataTypeName(int i)
- {
- return GetFieldType(i).Name;
- }
-
- ///
- /// Gets the value of the specified column as a . (Inherited from .)
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The value of the column.
- ///
- ///
- public DateTime GetDateTime(int i)
- {
- return (DateTime)GetValue(i);
- }
-
- ///
- /// Gets the value of the specified column as a .
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The value of the column.
- ///
- public DateTimeOffset GetDateTimeOffset(int i)
- {
- return (DateTimeOffset)GetValue(i);
- }
-
- ///
- /// Gets the value of the specified column as a . (Inherited from .)
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The value of the column.
- ///
- ///
- public decimal GetDecimal(int i)
- {
- return (decimal)GetValue(i);
- }
-
- ///
- /// Gets the value of the specified column as a . (Inherited from .)
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The value of the column.
- ///
- ///
- public double GetDouble(int i)
- {
- return (double)GetValue(i);
- }
-
- ///
- /// Gets the information corresponding to the type of that would be returned from .
- /// (Inherited from .)
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The information corresponding to the type of that would be returned from .
- ///
- ///
- public Type GetFieldType(int i)
- {
- return (Type)GetSchemaTable().Rows[i][SchemaTableColumn.DataType];
- }
-
- ///
- /// Gets the value of the specified column as a . (Inherited from .)
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The value of the column.
- ///
- ///
- public float GetFloat(int i)
- {
- return (float)this[i];
- }
-
- ///
- /// Gets the value of the specified column as a . (Inherited from .)
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The value of the column.
- ///
- ///
- public Guid GetGuid(int i)
- {
- return (Guid)GetValue(i);
- }
-
- ///
- /// Gets the value of the specified column as a . (Inherited from .)
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The value of the column.
- ///
- ///
- public short GetInt16(int i)
- {
- return (short)GetValue(i);
- }
-
- ///
- /// Gets the value of the specified column as a . (Inherited from .)
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The value of the column.
- ///
- ///
- public int GetInt32(int i)
- {
- return (int)GetValue(i);
- }
-
- ///
- /// Gets the value of the specified column as a . (Inherited from .)
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The value of the column.
- ///
- ///
- public long GetInt64(int i)
- {
- return (long)GetValue(i);
- }
-
- ///
- /// Gets the name for the field to find. (Inherited from .)
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The name of the field or the empty string (""), if there is no value to return.
- ///
- ///
- public string GetName(int i)
- {
- return (string)GetSchemaTable().Rows[i][SchemaTableColumn.ColumnName];
- }
-
- ///
- /// Return the index of the named field. (Inherited from .)
- ///
- ///
- /// The index of the named field was not found.
- ///
- ///
- /// The name of the field to find.
- ///
- ///
- /// The index of the named field.
- ///
- ///
- public int GetOrdinal(string name)
- {
- if (name == null) // Empty strings are handled as a IndexOutOfRangeException.
- {
- throw new ArgumentNullException("name");
- }
-
- int result = -1;
-
- int rowCount = FieldCount;
-
- DataRowCollection schemaRows = GetSchemaTable().Rows;
-
- // Case sensitive search
- for (int ordinal = 0; ordinal < rowCount; ordinal++)
- {
- if (String.Equals((string)schemaRows[ordinal][SchemaTableColumn.ColumnName], name, StringComparison.Ordinal))
+ if (string.Equals((string)schemaRows[ordinal][SchemaTableColumn.ColumnName], name, StringComparison.OrdinalIgnoreCase))
{
result = ordinal;
}
}
-
- if (result == -1)
- {
- // Case insensitive search.
- for (int ordinal = 0; ordinal < rowCount; ordinal++)
- {
- if (String.Equals((string)schemaRows[ordinal][SchemaTableColumn.ColumnName], name, StringComparison.OrdinalIgnoreCase))
- {
- result = ordinal;
- }
- }
- }
-
- if (result == -1)
- {
- throw new IndexOutOfRangeException(name);
- }
-
- return result;
}
- ///
- /// Returns a that describes the column metadata of the . (Inherited from .)
- ///
- ///
- /// The is closed.
- ///
- ///
- /// A that describes the column metadata.
- ///
- ///
- public DataTable GetSchemaTable()
+ if (result == -1)
{
- if (IsClosed)
- {
- throw new InvalidOperationException("The IDataReader is closed.");
- }
-
- if (_schemaTable?.Rows.Count == 0)
- {
- // Need to add the column definitions and mappings
- _schemaTable.TableName = TableName;
-
- AddSchemaTableRows();
-
- Debug.Assert(_schemaTable.Rows.Count == FieldCount);
- }
-
- return _schemaTable!;
+ throw new IndexOutOfRangeException(name);
}
- ///
- /// Gets the value of the specified column as a . (Inherited from .)
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The value of the column.
- ///
- ///
- public string GetString(int i)
- {
- return (string)GetValue(i);
- }
-
- ///
- /// Gets the value of the specified column as a .
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The value of the column.
- ///
- public TimeSpan GetTimeSpan(int i)
- {
- return (TimeSpan)GetValue(i);
- }
-
- ///
- /// Gets the value of the specified column as a . (Inherited from .)
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// The value of the column.
- ///
- ///
- public abstract object GetValue(int i);
-
- ///
- /// Populates an array of objects with the column values of the current record. (Inherited from .)
- ///
- ///
- /// was null.
- ///
- ///
- /// An array of to copy the attribute fields into.
- ///
- ///
- /// The number of instances of in the array.
- ///
- ///
- public int GetValues(object[] values)
- {
- if (values == null)
- {
- throw new ArgumentNullException("values");
- }
-
- int fieldCount = Math.Min(FieldCount, values.Length);
-
- for (int i = 0; i < fieldCount; i++)
- {
- values[i] = GetValue(i);
- }
-
- return fieldCount;
- }
-
- ///
- /// Return whether the specified field is set to null. (Inherited from .)
- ///
- ///
- /// The index passed was outside the range of 0 through .
- ///
- ///
- /// The zero-based column ordinal.
- ///
- ///
- /// True if the specified field is set to null; otherwise, false.
- ///
- ///
- public bool IsDBNull(int i)
- {
- object data = GetValue(i);
-
- return data == null || Convert.IsDBNull(data);
- }
-
- ///
- /// Advances the data reader to the next result, when reading the results of batch SQL statements. (Inherited from .)
- ///
- ///
- /// for returns a single result set so false is always returned.
- ///
- ///
- /// True if there are more rows; otherwise, false. for returns a single result set so false is always returned.
- ///
- ///
- public bool NextResult()
- {
- return false;
- }
-
- ///
- /// Advances the to the next record. (Inherited from .)
- ///
- ///
- /// True if there are more rows; otherwise, false.
- ///
- ///
- public abstract bool Read();
-
- #endregion
-
- #region IDisposable
-
- ///
- /// Has the object been disposed?
- ///
- bool _disposed = false;
-
- ///
- /// Dispose of any disposable and expensive resources.
- ///
- ///
- /// Is this call the result of a call?
- ///
- protected virtual void Dispose(bool disposing)
- {
- if (!this._disposed)
- {
- this._disposed = true;
-
- if (disposing)
- {
- if (_schemaTable != null)
- {
- _schemaTable.Dispose();
- this._schemaTable = null;
- }
-
- this._columnMappings = null;
-
- this._isOpen = false;
-
- GC.SuppressFinalize(this);
- }
- }
- }
-
- ///
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. (Inherited from .)
- ///
- ///
- public void Dispose()
- {
- Dispose(true);
- }
-
- ///
- /// Finalizer
- ///
- ///
- /// has no unmanaged resources but a subclass may thus a finalizer is required.
- ///
- ~BulkDataReader()
- {
- Dispose(false);
- }
-
- #endregion
-
+ return result;
}
+
+ ///
+ /// Returns a that describes the column metadata of the . (Inherited
+ /// from .)
+ ///
+ ///
+ /// The is closed.
+ ///
+ ///
+ /// A that describes the column metadata.
+ ///
+ ///
+ public DataTable GetSchemaTable()
+ {
+ if (IsClosed)
+ {
+ throw new InvalidOperationException("The IDataReader is closed.");
+ }
+
+ if (_schemaTable?.Rows.Count == 0)
+ {
+ // Need to add the column definitions and mappings
+ _schemaTable.TableName = TableName;
+
+ AddSchemaTableRows();
+
+ Debug.Assert(_schemaTable.Rows.Count == FieldCount);
+ }
+
+ return _schemaTable!;
+ }
+
+ ///
+ /// Gets the value of the specified column as a . (Inherited from .)
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The value of the column.
+ ///
+ ///
+ public string GetString(int i) => (string)GetValue(i);
+
+ ///
+ /// Gets the value of the specified column as a .
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The value of the column.
+ ///
+ public TimeSpan GetTimeSpan(int i) => (TimeSpan)GetValue(i);
+
+ ///
+ /// Gets the value of the specified column as a . (Inherited from .)
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// The value of the column.
+ ///
+ ///
+ public abstract object GetValue(int i);
+
+ ///
+ /// Populates an array of objects with the column values of the current record. (Inherited from
+ /// .)
+ ///
+ ///
+ /// was null.
+ ///
+ ///
+ /// An array of to copy the attribute fields into.
+ ///
+ ///
+ /// The number of instances of in the array.
+ ///
+ ///
+ public int GetValues(object[] values)
+ {
+ if (values == null)
+ {
+ throw new ArgumentNullException("values");
+ }
+
+ var fieldCount = Math.Min(FieldCount, values.Length);
+
+ for (var i = 0; i < fieldCount; i++)
+ {
+ values[i] = GetValue(i);
+ }
+
+ return fieldCount;
+ }
+
+ ///
+ /// Return whether the specified field is set to null. (Inherited from .)
+ ///
+ ///
+ /// The index passed was outside the range of 0 through .
+ ///
+ ///
+ /// The zero-based column ordinal.
+ ///
+ ///
+ /// True if the specified field is set to null; otherwise, false.
+ ///
+ ///
+ public bool IsDBNull(int i)
+ {
+ var data = GetValue(i);
+
+ return data == null || Convert.IsDBNull(data);
+ }
+
+ ///
+ /// Advances the data reader to the next result, when reading the results of batch SQL statements. (Inherited from
+ /// .)
+ ///
+ ///
+ /// for returns a single result set so false is always returned.
+ ///
+ ///
+ /// True if there are more rows; otherwise, false. for returns a
+ /// single result set so false is always returned.
+ ///
+ ///
+ public bool NextResult() => false;
+
+ ///
+ /// Advances the to the next record. (Inherited from .)
+ ///
+ ///
+ /// True if there are more rows; otherwise, false.
+ ///
+ ///
+ public abstract bool Read();
+
+ #endregion
+
+ #region IDisposable
+
+ ///
+ /// Has the object been disposed?
+ ///
+ private bool _disposed;
+
+ ///
+ /// Dispose of any disposable and expensive resources.
+ ///
+ ///
+ /// Is this call the result of a call?
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+
+ if (disposing)
+ {
+ if (_schemaTable != null)
+ {
+ _schemaTable.Dispose();
+ _schemaTable = null;
+ }
+
+ _columnMappings = null;
+
+ _isOpen = false;
+
+ GC.SuppressFinalize(this);
+ }
+ }
+ }
+
+ ///
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. (Inherited
+ /// from .)
+ ///
+ ///
+ public void Dispose() => Dispose(true);
+
+ ///
+ /// Finalizer
+ ///
+ ///
+ /// has no unmanaged resources but a subclass may thus a finalizer is required.
+ ///
+ ~BulkDataReader() => Dispose(false);
+
+ #endregion
}
diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Services/MicrosoftSqlSyntaxProviderBase.cs b/src/Umbraco.Cms.Persistence.SqlServer/Services/MicrosoftSqlSyntaxProviderBase.cs
index 4856e1e117..7256317c15 100644
--- a/src/Umbraco.Cms.Persistence.SqlServer/Services/MicrosoftSqlSyntaxProviderBase.cs
+++ b/src/Umbraco.Cms.Persistence.SqlServer/Services/MicrosoftSqlSyntaxProviderBase.cs
@@ -6,216 +6,218 @@ using Umbraco.Cms.Core.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions;
using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax;
-namespace Umbraco.Cms.Persistence.SqlServer.Services
+namespace Umbraco.Cms.Persistence.SqlServer.Services;
+
+///
+/// Abstract class for defining MS sql implementations
+///
+///
+public abstract class MicrosoftSqlSyntaxProviderBase : SqlSyntaxProviderBase
+ where TSyntax : ISqlSyntaxProvider
{
- ///
- /// Abstract class for defining MS sql implementations
- ///
- ///
- public abstract class MicrosoftSqlSyntaxProviderBase : SqlSyntaxProviderBase
- where TSyntax : ISqlSyntaxProvider
+ private readonly ILogger _logger;
+
+ protected MicrosoftSqlSyntaxProviderBase()
{
- private readonly ILogger _logger;
+ _logger = StaticApplicationLogging.CreateLogger();
- protected MicrosoftSqlSyntaxProviderBase()
+ AutoIncrementDefinition = "IDENTITY(1,1)";
+ GuidColumnDefinition = "UniqueIdentifier";
+ RealColumnDefinition = "FLOAT";
+ BoolColumnDefinition = "BIT";
+ DecimalColumnDefinition = "DECIMAL(38,6)";
+ TimeColumnDefinition = "TIME"; //SQLSERVER 2008+
+ BlobColumnDefinition = "VARBINARY(MAX)";
+ }
+
+ public override string RenameTable => "sp_rename '{0}', '{1}'";
+
+ public override string AddColumn => "ALTER TABLE {0} ADD {1}";
+
+ public override string GetQuotedTableName(string? tableName)
+ {
+ if (tableName?.Contains(".") == false)
{
- _logger = StaticApplicationLogging.CreateLogger();
-
- AutoIncrementDefinition = "IDENTITY(1,1)";
- GuidColumnDefinition = "UniqueIdentifier";
- RealColumnDefinition = "FLOAT";
- BoolColumnDefinition = "BIT";
- DecimalColumnDefinition = "DECIMAL(38,6)";
- TimeColumnDefinition = "TIME"; //SQLSERVER 2008+
- BlobColumnDefinition = "VARBINARY(MAX)";
+ return $"[{tableName}]";
}
- public override string RenameTable => "sp_rename '{0}', '{1}'";
+ var tableNameParts = tableName?.Split(Core.Constants.CharArrays.Period, 2);
+ return $"[{tableNameParts?[0]}].[{tableNameParts?[1]}]";
+ }
- public override string AddColumn => "ALTER TABLE {0} ADD {1}";
+ public override string GetQuotedColumnName(string? columnName) => $"[{columnName}]";
- public override string GetQuotedTableName(string? tableName)
+ public override string GetQuotedName(string? name) => $"[{name}]";
+
+ public override string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType)
+ {
+ switch (columnType)
{
- if (tableName?.Contains(".") == false)
- return $"[{tableName}]";
+ case TextColumnType.NVarchar:
+ return base.GetStringColumnEqualComparison(column, paramIndex, columnType);
+ case TextColumnType.NText:
+ //MSSQL doesn't allow for = comparison with NText columns but allows this syntax
+ return $"{column} LIKE @{paramIndex}";
+ default:
+ throw new ArgumentOutOfRangeException(nameof(columnType));
+ }
+ }
- var tableNameParts = tableName?.Split(Cms.Core.Constants.CharArrays.Period, 2);
- return $"[{tableNameParts?[0]}].[{tableNameParts?[1]}]";
+ public override string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType)
+ {
+ switch (columnType)
+ {
+ case TextColumnType.NVarchar:
+ return base.GetStringColumnWildcardComparison(column, paramIndex, columnType);
+ case TextColumnType.NText:
+ //MSSQL doesn't allow for upper methods with NText columns
+ return $"{column} LIKE @{paramIndex}";
+ default:
+ throw new ArgumentOutOfRangeException(nameof(columnType));
+ }
+ }
+
+ ///
+ /// This uses a the DbTypeMap created and custom mapping to resolve the SqlDbType
+ ///
+ ///
+ ///
+ public virtual SqlDbType GetSqlDbType(Type clrType)
+ {
+ DbType dbType = DbTypeMap.ColumnDbTypeMap[clrType];
+ return GetSqlDbType(dbType);
+ }
+
+ ///
+ /// Returns the mapped SqlDbType for the DbType specified
+ ///
+ ///
+ ///
+ public virtual SqlDbType GetSqlDbType(DbType dbType)
+ {
+ SqlDbType sqlDbType;
+
+ //SEE: https://msdn.microsoft.com/en-us/library/cc716729(v=vs.110).aspx
+ // and https://msdn.microsoft.com/en-us/library/yy6y35y8%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
+ switch (dbType)
+ {
+ case DbType.AnsiString:
+ sqlDbType = SqlDbType.VarChar;
+ break;
+ case DbType.Binary:
+ sqlDbType = SqlDbType.VarBinary;
+ break;
+ case DbType.Byte:
+ sqlDbType = SqlDbType.TinyInt;
+ break;
+ case DbType.Boolean:
+ sqlDbType = SqlDbType.Bit;
+ break;
+ case DbType.Currency:
+ sqlDbType = SqlDbType.Money;
+ break;
+ case DbType.Date:
+ sqlDbType = SqlDbType.Date;
+ break;
+ case DbType.DateTime:
+ sqlDbType = SqlDbType.DateTime;
+ break;
+ case DbType.Decimal:
+ sqlDbType = SqlDbType.Decimal;
+ break;
+ case DbType.Double:
+ sqlDbType = SqlDbType.Float;
+ break;
+ case DbType.Guid:
+ sqlDbType = SqlDbType.UniqueIdentifier;
+ break;
+ case DbType.Int16:
+ sqlDbType = SqlDbType.SmallInt;
+ break;
+ case DbType.Int32:
+ sqlDbType = SqlDbType.Int;
+ break;
+ case DbType.Int64:
+ sqlDbType = SqlDbType.BigInt;
+ break;
+ case DbType.Object:
+ sqlDbType = SqlDbType.Variant;
+ break;
+ case DbType.SByte:
+ throw new NotSupportedException("Inferring a SqlDbType from SByte is not supported.");
+ case DbType.Single:
+ sqlDbType = SqlDbType.Real;
+ break;
+ case DbType.String:
+ sqlDbType = SqlDbType.NVarChar;
+ break;
+ case DbType.Time:
+ sqlDbType = SqlDbType.Time;
+ break;
+ case DbType.UInt16:
+ throw new NotSupportedException("Inferring a SqlDbType from UInt16 is not supported.");
+ case DbType.UInt32:
+ throw new NotSupportedException("Inferring a SqlDbType from UInt32 is not supported.");
+ case DbType.UInt64:
+ throw new NotSupportedException("Inferring a SqlDbType from UInt64 is not supported.");
+ case DbType.VarNumeric:
+ throw new NotSupportedException("Inferring a VarNumeric from UInt64 is not supported.");
+ case DbType.AnsiStringFixedLength:
+ sqlDbType = SqlDbType.Char;
+ break;
+ case DbType.StringFixedLength:
+ sqlDbType = SqlDbType.NChar;
+ break;
+ case DbType.Xml:
+ sqlDbType = SqlDbType.Xml;
+ break;
+ case DbType.DateTime2:
+ sqlDbType = SqlDbType.DateTime2;
+ break;
+ case DbType.DateTimeOffset:
+ sqlDbType = SqlDbType.DateTimeOffset;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
}
- public override string GetQuotedColumnName(string? columnName) => $"[{columnName}]";
+ return sqlDbType;
+ }
- public override string GetQuotedName(string? name) => $"[{name}]";
+ public override void HandleCreateTable(IDatabase database, TableDefinition tableDefinition, bool skipKeysAndIndexes = false)
+ {
+ var createSql = Format(tableDefinition);
+ var createPrimaryKeySql = FormatPrimaryKey(tableDefinition);
+ List foreignSql = Format(tableDefinition.ForeignKeys);
- public override string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType)
+ _logger.LogInformation("Create table:\n {Sql}", createSql);
+ database.Execute(new Sql(createSql));
+
+ if (skipKeysAndIndexes)
{
- switch (columnType)
- {
- case TextColumnType.NVarchar:
- return base.GetStringColumnEqualComparison(column, paramIndex, columnType);
- case TextColumnType.NText:
- //MSSQL doesn't allow for = comparison with NText columns but allows this syntax
- return $"{column} LIKE @{paramIndex}";
- default:
- throw new ArgumentOutOfRangeException(nameof(columnType));
- }
+ return;
}
- public override string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType)
+ //If any statements exists for the primary key execute them here
+ if (string.IsNullOrEmpty(createPrimaryKeySql) == false)
{
- switch (columnType)
- {
- case TextColumnType.NVarchar:
- return base.GetStringColumnWildcardComparison(column, paramIndex, columnType);
- case TextColumnType.NText:
- //MSSQL doesn't allow for upper methods with NText columns
- return $"{column} LIKE @{paramIndex}";
- default:
- throw new ArgumentOutOfRangeException(nameof(columnType));
- }
+ _logger.LogInformation("Create Primary Key:\n {Sql}", createPrimaryKeySql);
+ database.Execute(new Sql(createPrimaryKeySql));
}
- ///
- /// This uses a the DbTypeMap created and custom mapping to resolve the SqlDbType
- ///
- ///
- ///
- public virtual SqlDbType GetSqlDbType(Type clrType)
+ List indexSql = Format(tableDefinition.Indexes);
+ //Loop through index statements and execute sql
+ foreach (var sql in indexSql)
{
- var dbType = DbTypeMap.ColumnDbTypeMap[clrType];
- return GetSqlDbType(dbType);
+ _logger.LogInformation("Create Index:\n {Sql}", sql);
+ database.Execute(new Sql(sql));
}
- ///
- /// Returns the mapped SqlDbType for the DbType specified
- ///
- ///
- ///
- public virtual SqlDbType GetSqlDbType(DbType dbType)
+ //Loop through foreignkey statements and execute sql
+ foreach (var sql in foreignSql)
{
- SqlDbType sqlDbType;
-
- //SEE: https://msdn.microsoft.com/en-us/library/cc716729(v=vs.110).aspx
- // and https://msdn.microsoft.com/en-us/library/yy6y35y8%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
- switch (dbType)
- {
- case DbType.AnsiString:
- sqlDbType = SqlDbType.VarChar;
- break;
- case DbType.Binary:
- sqlDbType = SqlDbType.VarBinary;
- break;
- case DbType.Byte:
- sqlDbType = SqlDbType.TinyInt;
- break;
- case DbType.Boolean:
- sqlDbType = SqlDbType.Bit;
- break;
- case DbType.Currency:
- sqlDbType = SqlDbType.Money;
- break;
- case DbType.Date:
- sqlDbType = SqlDbType.Date;
- break;
- case DbType.DateTime:
- sqlDbType = SqlDbType.DateTime;
- break;
- case DbType.Decimal:
- sqlDbType = SqlDbType.Decimal;
- break;
- case DbType.Double:
- sqlDbType = SqlDbType.Float;
- break;
- case DbType.Guid:
- sqlDbType = SqlDbType.UniqueIdentifier;
- break;
- case DbType.Int16:
- sqlDbType = SqlDbType.SmallInt;
- break;
- case DbType.Int32:
- sqlDbType = SqlDbType.Int;
- break;
- case DbType.Int64:
- sqlDbType = SqlDbType.BigInt;
- break;
- case DbType.Object:
- sqlDbType = SqlDbType.Variant;
- break;
- case DbType.SByte:
- throw new NotSupportedException("Inferring a SqlDbType from SByte is not supported.");
- case DbType.Single:
- sqlDbType = SqlDbType.Real;
- break;
- case DbType.String:
- sqlDbType = SqlDbType.NVarChar;
- break;
- case DbType.Time:
- sqlDbType = SqlDbType.Time;
- break;
- case DbType.UInt16:
- throw new NotSupportedException("Inferring a SqlDbType from UInt16 is not supported.");
- case DbType.UInt32:
- throw new NotSupportedException("Inferring a SqlDbType from UInt32 is not supported.");
- case DbType.UInt64:
- throw new NotSupportedException("Inferring a SqlDbType from UInt64 is not supported.");
- case DbType.VarNumeric:
- throw new NotSupportedException("Inferring a VarNumeric from UInt64 is not supported.");
- case DbType.AnsiStringFixedLength:
- sqlDbType = SqlDbType.Char;
- break;
- case DbType.StringFixedLength:
- sqlDbType = SqlDbType.NChar;
- break;
- case DbType.Xml:
- sqlDbType = SqlDbType.Xml;
- break;
- case DbType.DateTime2:
- sqlDbType = SqlDbType.DateTime2;
- break;
- case DbType.DateTimeOffset:
- sqlDbType = SqlDbType.DateTimeOffset;
- break;
- default:
- throw new ArgumentOutOfRangeException();
- }
- return sqlDbType;
- }
-
- public override void HandleCreateTable(IDatabase database, TableDefinition tableDefinition, bool skipKeysAndIndexes = false)
- {
- var createSql = Format(tableDefinition);
- var createPrimaryKeySql = FormatPrimaryKey(tableDefinition);
- List foreignSql = Format(tableDefinition.ForeignKeys);
-
- _logger.LogInformation("Create table:\n {Sql}", createSql);
- database.Execute(new Sql(createSql));
-
- if (skipKeysAndIndexes)
- {
- return;
- }
-
- //If any statements exists for the primary key execute them here
- if (string.IsNullOrEmpty(createPrimaryKeySql) == false)
- {
- _logger.LogInformation("Create Primary Key:\n {Sql}", createPrimaryKeySql);
- database.Execute(new Sql(createPrimaryKeySql));
- }
-
- List indexSql = Format(tableDefinition.Indexes);
- //Loop through index statements and execute sql
- foreach (var sql in indexSql)
- {
- _logger.LogInformation("Create Index:\n {Sql}", sql);
- database.Execute(new Sql(sql));
- }
-
- //Loop through foreignkey statements and execute sql
- foreach (var sql in foreignSql)
- {
- _logger.LogInformation("Create Foreign Key:\n {Sql}", sql);
- database.Execute(new Sql(sql));
- }
+ _logger.LogInformation("Create Foreign Key:\n {Sql}", sql);
+ database.Execute(new Sql(sql));
}
}
}
diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Services/PocoDataDataReader.cs b/src/Umbraco.Cms.Persistence.SqlServer/Services/PocoDataDataReader.cs
index 8a05e78258..2b9d35b959 100644
--- a/src/Umbraco.Cms.Persistence.SqlServer/Services/PocoDataDataReader.cs
+++ b/src/Umbraco.Cms.Persistence.SqlServer/Services/PocoDataDataReader.cs
@@ -4,142 +4,166 @@ using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions;
using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax;
-namespace Umbraco.Cms.Persistence.SqlServer.Services
+namespace Umbraco.Cms.Persistence.SqlServer.Services;
+
+///
+/// A data reader used for reading collections of PocoData entity types
+///
+///
+/// We are using a custom data reader so that tons of memory is not consumed when rebuilding this table, previously
+/// we'd generate SQL insert statements, but we'd have to put all of the XML structures into memory first.
+/// Alternatively
+/// we can use .net's DataTable, but this also requires putting everything into memory. By using a DataReader we don't
+/// have to
+/// store every content item and it's XML structure in memory to get it into the DB, we can stream it into the db with
+/// this
+/// reader.
+///
+internal class PocoDataDataReader : BulkDataReader
+ where TSyntax : ISqlSyntaxProvider
{
- ///
- /// A data reader used for reading collections of PocoData entity types
- ///
- ///
- /// We are using a custom data reader so that tons of memory is not consumed when rebuilding this table, previously
- /// we'd generate SQL insert statements, but we'd have to put all of the XML structures into memory first. Alternatively
- /// we can use .net's DataTable, but this also requires putting everything into memory. By using a DataReader we don't have to
- /// store every content item and it's XML structure in memory to get it into the DB, we can stream it into the db with this
- /// reader.
- ///
- internal class PocoDataDataReader : BulkDataReader
- where TSyntax : ISqlSyntaxProvider
+ private readonly ColumnDefinition[] _columnDefinitions;
+ private readonly IEnumerator _enumerator;
+ private readonly PocoColumn[] _readerColumns;
+ private readonly MicrosoftSqlSyntaxProviderBase _sqlSyntaxProvider;
+ private readonly TableDefinition _tableDefinition;
+ private int _recordsAffected = -1;
+
+ public PocoDataDataReader(
+ IEnumerable dataSource,
+ PocoData pd,
+ MicrosoftSqlSyntaxProviderBase sqlSyntaxProvider)
{
- private readonly MicrosoftSqlSyntaxProviderBase _sqlSyntaxProvider;
- private readonly TableDefinition _tableDefinition;
- private readonly PocoColumn[] _readerColumns;
- private readonly IEnumerator _enumerator;
- private readonly ColumnDefinition[] _columnDefinitions;
- private int _recordsAffected = -1;
-
- public PocoDataDataReader(
- IEnumerable dataSource,
- PocoData pd,
- MicrosoftSqlSyntaxProviderBase sqlSyntaxProvider)
+ if (dataSource == null)
{
- if (dataSource == null) throw new ArgumentNullException(nameof(dataSource));
- if (sqlSyntaxProvider == null) throw new ArgumentNullException(nameof(sqlSyntaxProvider));
-
- _tableDefinition = DefinitionFactory.GetTableDefinition(pd.Type, sqlSyntaxProvider);
- if (_tableDefinition == null) throw new InvalidOperationException("No table definition found for type " + pd.Type);
-
- // only real columns, exclude result/computed columns
- // Like NPoco does: https://github.com/schotime/NPoco/blob/5117a55fde57547e928246c044fd40bd00b2d7d1/src/NPoco.SqlServer/SqlBulkCopyHelper.cs#L59
- _readerColumns = pd.Columns
- .Where(x => x.Value.ResultColumn == false && x.Value.ComputedColumn == false)
- .Select(x => x.Value)
- .ToArray();
-
- _sqlSyntaxProvider = sqlSyntaxProvider;
- _enumerator = dataSource.GetEnumerator();
- _columnDefinitions = _tableDefinition.Columns.ToArray();
-
+ throw new ArgumentNullException(nameof(dataSource));
}
- protected override string SchemaName => _tableDefinition.SchemaName;
-
- protected override string TableName => _tableDefinition.Name;
-
- public override int RecordsAffected => _recordsAffected <= 0 ? -1 : _recordsAffected;
-
- ///
- /// This will automatically add the schema rows based on the Poco table definition and the columns passed in
- ///
- protected override void AddSchemaTableRows()
+ if (sqlSyntaxProvider == null)
{
- //var colNames = _readerColumns.Select(x => x.ColumnName).ToArray();
- //foreach (var col in _columnDefinitions.Where(x => colNames.Contains(x.Name, StringComparer.OrdinalIgnoreCase)))
- foreach (var col in _columnDefinitions)
+ throw new ArgumentNullException(nameof(sqlSyntaxProvider));
+ }
+
+ _tableDefinition = DefinitionFactory.GetTableDefinition(pd.Type, sqlSyntaxProvider);
+ if (_tableDefinition == null)
+ {
+ throw new InvalidOperationException("No table definition found for type " + pd.Type);
+ }
+
+ // only real columns, exclude result/computed columns
+ // Like NPoco does: https://github.com/schotime/NPoco/blob/5117a55fde57547e928246c044fd40bd00b2d7d1/src/NPoco.SqlServer/SqlBulkCopyHelper.cs#L59
+ _readerColumns = pd.Columns
+ .Where(x => x.Value.ResultColumn == false && x.Value.ComputedColumn == false)
+ .Select(x => x.Value)
+ .ToArray();
+
+ _sqlSyntaxProvider = sqlSyntaxProvider;
+ _enumerator = dataSource.GetEnumerator();
+ _columnDefinitions = _tableDefinition.Columns.ToArray();
+ }
+
+ protected override string SchemaName => _tableDefinition.SchemaName;
+
+ protected override string TableName => _tableDefinition.Name;
+
+ public override int RecordsAffected => _recordsAffected <= 0 ? -1 : _recordsAffected;
+
+ ///
+ /// This will automatically add the schema rows based on the Poco table definition and the columns passed in
+ ///
+ protected override void AddSchemaTableRows()
+ {
+ //var colNames = _readerColumns.Select(x => x.ColumnName).ToArray();
+ //foreach (var col in _columnDefinitions.Where(x => colNames.Contains(x.Name, StringComparer.OrdinalIgnoreCase)))
+ foreach (ColumnDefinition col in _columnDefinitions)
+ {
+ SqlDbType sqlDbType;
+ if (col.CustomDbType.HasValue)
{
- SqlDbType sqlDbType;
- if (col.CustomDbType.HasValue)
+ //get the SqlDbType from the 'special type'
+ switch (col.CustomDbType)
{
- //get the SqlDbType from the 'special type'
- switch (col.CustomDbType)
- {
- case var x when x == SpecialDbType.NTEXT:
- sqlDbType = SqlDbType.NText;
- break;
- case var x when x == SpecialDbType.NCHAR:
- sqlDbType = SqlDbType.NChar;
- break;
- case var x when x == SpecialDbType.NVARCHARMAX:
- sqlDbType = SqlDbType.NVarChar;
- break;
- default:
- throw new ArgumentOutOfRangeException("The custom DB type " + col.CustomDbType + " is not supported for bulk import statements.");
- }
+ case var x when x == SpecialDbType.NTEXT:
+ sqlDbType = SqlDbType.NText;
+ break;
+ case var x when x == SpecialDbType.NCHAR:
+ sqlDbType = SqlDbType.NChar;
+ break;
+ case var x when x == SpecialDbType.NVARCHARMAX:
+ sqlDbType = SqlDbType.NVarChar;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException("The custom DB type " + col.CustomDbType +
+ " is not supported for bulk import statements.");
}
- else if (col.Type.HasValue)
- {
- //get the SqlDbType from the DbType
- sqlDbType = _sqlSyntaxProvider.GetSqlDbType(col.Type.Value);
- }
- else
- {
- //get the SqlDbType from the CLR type
- sqlDbType = _sqlSyntaxProvider.GetSqlDbType(col.PropertyType);
- }
-
- AddSchemaTableRow(
- col.Name,
- col.Size > 0 ? (int?)col.Size : null,
- col.Precision > 0 ? (short?)col.Precision : null,
- null, col.IsUnique, col.IsIdentity, col.IsNullable, sqlDbType,
- null, null, null, null, null);
}
- }
-
- ///
- /// Get the value from the column index for the current object
- ///
- ///
- ///
- public override object GetValue(int i)
- {
- return _enumerator.Current == null ? null! : _readerColumns[i].GetValue(_enumerator.Current);
- }
-
- ///
- /// Advance the cursor
- ///
- ///
- public override bool Read()
- {
- var result = _enumerator.MoveNext();
- if (result)
+ else if (col.Type.HasValue)
{
- if (_recordsAffected == -1)
- _recordsAffected = 0;
- _recordsAffected++;
+ //get the SqlDbType from the DbType
+ sqlDbType = _sqlSyntaxProvider.GetSqlDbType(col.Type.Value);
}
- return result;
+ else
+ {
+ //get the SqlDbType from the CLR type
+ sqlDbType = _sqlSyntaxProvider.GetSqlDbType(col.PropertyType);
+ }
+
+ AddSchemaTableRow(
+ col.Name,
+ col.Size > 0 ? col.Size : null,
+ col.Precision > 0 ? (short?)col.Precision : null,
+ null,
+ col.IsUnique,
+ col.IsIdentity,
+ col.IsNullable,
+ sqlDbType,
+ null,
+ null,
+ null,
+ null,
+ null);
+ }
+ }
+
+ ///
+ /// Get the value from the column index for the current object
+ ///
+ ///
+ ///
+ public override object GetValue(int i) =>
+ _enumerator.Current == null ? null! : _readerColumns[i].GetValue(_enumerator.Current);
+
+ ///
+ /// Advance the cursor
+ ///
+ ///
+ public override bool Read()
+ {
+ var result = _enumerator.MoveNext();
+ if (result)
+ {
+ if (_recordsAffected == -1)
+ {
+ _recordsAffected = 0;
+ }
+
+ _recordsAffected++;
}
- ///
- /// Ensure the enumerator is disposed
- ///
- ///
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
+ return result;
+ }
- if (disposing)
- _enumerator.Dispose();
+ ///
+ /// Ensure the enumerator is disposed
+ ///
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (disposing)
+ {
+ _enumerator.Dispose();
}
}
}
diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlAzureDatabaseProviderMetadata.cs b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlAzureDatabaseProviderMetadata.cs
index cf74a8549f..0dbc62fb49 100644
--- a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlAzureDatabaseProviderMetadata.cs
+++ b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlAzureDatabaseProviderMetadata.cs
@@ -6,13 +6,13 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Persistence.SqlServer.Services;
///
-/// Provider metadata for SQL Azure
+/// Provider metadata for SQL Azure
///
[DataContract]
public class SqlAzureDatabaseProviderMetadata : IDatabaseProviderMetadata
{
///
- public Guid Id => new ("7858e827-8951-4fe0-a7fe-6883011b1f1b");
+ public Guid Id => new("7858e827-8951-4fe0-a7fe-6883011b1f1b");
///
public int SortOrder => 3;
@@ -59,37 +59,49 @@ public class SqlAzureDatabaseProviderMetadata : IDatabaseProviderMetadata
var password = databaseModel.Password;
if (server.Contains(".") && ServerStartsWithTcp(server) == false)
+ {
server = $"tcp:{server}";
+ }
if (server.Contains(".") == false && ServerStartsWithTcp(server))
{
- string serverName = server.Contains(",")
+ var serverName = server.Contains(",")
? server.Substring(0, server.IndexOf(",", StringComparison.Ordinal))
: server;
var portAddition = string.Empty;
if (server.Contains(","))
+ {
portAddition = server.Substring(server.IndexOf(",", StringComparison.Ordinal));
+ }
server = $"{serverName}.database.windows.net{portAddition}";
}
if (ServerStartsWithTcp(server) == false)
+ {
server = $"tcp:{server}.database.windows.net";
+ }
if (server.Contains(",") == false)
+ {
server = $"{server},1433";
+ }
if (user.Contains("@") == false)
{
var userDomain = server;
if (ServerStartsWithTcp(server))
+ {
userDomain = userDomain.Substring(userDomain.IndexOf(":", StringComparison.Ordinal) + 1);
+ }
if (userDomain.Contains("."))
+ {
userDomain = userDomain.Substring(0, userDomain.IndexOf(".", StringComparison.Ordinal));
+ }
user = $"{user}@{userDomain}";
}
diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlLocalDbDatabaseProviderMetadata.cs b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlLocalDbDatabaseProviderMetadata.cs
index 1741b8ffe1..30a503e5f9 100644
--- a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlLocalDbDatabaseProviderMetadata.cs
+++ b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlLocalDbDatabaseProviderMetadata.cs
@@ -7,13 +7,13 @@ using Umbraco.Cms.Infrastructure.Persistence;
namespace Umbraco.Cms.Persistence.SqlServer.Services;
///
-/// Provider metadata for SQL Server LocalDb
+/// Provider metadata for SQL Server LocalDb
///
[DataContract]
public class SqlLocalDbDatabaseProviderMetadata : IDatabaseProviderMetadata
{
///
- public Guid Id => new ("05a7e9ed-aa6a-43af-a309-63422c87c675");
+ public Guid Id => new("05a7e9ed-aa6a-43af-a309-63422c87c675");
///
public int SortOrder => 1;
diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerBulkSqlInsertProvider.cs b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerBulkSqlInsertProvider.cs
index cfd30bbd90..dbaab82ad4 100644
--- a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerBulkSqlInsertProvider.cs
+++ b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerBulkSqlInsertProvider.cs
@@ -1,74 +1,85 @@
using System.Data;
+using System.Data.Common;
using Microsoft.Data.SqlClient;
using NPoco;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Extensions;
-namespace Umbraco.Cms.Persistence.SqlServer.Services
+namespace Umbraco.Cms.Persistence.SqlServer.Services;
+
+///
+/// A bulk sql insert provider for Sql Server
+///
+public class SqlServerBulkSqlInsertProvider : IBulkSqlInsertProvider
{
- ///
- /// A bulk sql insert provider for Sql Server
- ///
- public class SqlServerBulkSqlInsertProvider : IBulkSqlInsertProvider
+ public string ProviderName => Constants.ProviderName;
+
+ public int BulkInsertRecords(IUmbracoDatabase database, IEnumerable records)
{
- public string ProviderName => Constants.ProviderName;
-
- public int BulkInsertRecords(IUmbracoDatabase database, IEnumerable records)
+ T[] recordsA = records.ToArray();
+ if (recordsA.Length == 0)
{
- var recordsA = records.ToArray();
- if (recordsA.Length == 0) return 0;
-
- var pocoData = database.PocoDataFactory.ForType(typeof(T));
- if (pocoData == null) throw new InvalidOperationException("Could not find PocoData for " + typeof(T));
-
- return BulkInsertRecordsSqlServer(database, pocoData, recordsA);
+ return 0;
}
- ///
- /// Bulk-insert records using SqlServer BulkCopy method.
- ///
- /// The type of the records.
- /// The database.
- /// The PocoData object corresponding to the record's type.
- /// The records.
- /// The number of records that were inserted.
- private int BulkInsertRecordsSqlServer(IUmbracoDatabase database, PocoData pocoData, IEnumerable records)
+ PocoData? pocoData = database.PocoDataFactory.ForType(typeof(T));
+ if (pocoData == null)
{
- // TODO: The main reason this exists is because the NPoco InsertBulk method doesn't return the number of items.
- // It is worth investigating the performance of this vs NPoco's because we use a custom BulkDataReader
- // which in theory should be more efficient than NPocos way of building up an in-memory DataTable.
+ throw new InvalidOperationException("Could not find PocoData for " + typeof(T));
+ }
- // create command against the original database.Connection
- using (var command = database.CreateCommand(database.Connection, CommandType.Text, string.Empty))
+ return BulkInsertRecordsSqlServer(database, pocoData, recordsA);
+ }
+
+ ///
+ /// Bulk-insert records using SqlServer BulkCopy method.
+ ///
+ /// The type of the records.
+ /// The database.
+ /// The PocoData object corresponding to the record's type.
+ /// The records.
+ /// The number of records that were inserted.
+ private int BulkInsertRecordsSqlServer(IUmbracoDatabase database, PocoData pocoData, IEnumerable records)
+ {
+ // TODO: The main reason this exists is because the NPoco InsertBulk method doesn't return the number of items.
+ // It is worth investigating the performance of this vs NPoco's because we use a custom BulkDataReader
+ // which in theory should be more efficient than NPocos way of building up an in-memory DataTable.
+
+ // create command against the original database.Connection
+ using (DbCommand command = database.CreateCommand(database.Connection, CommandType.Text, string.Empty))
+ {
+ // use typed connection and transaction or SqlBulkCopy
+ SqlConnection tConnection = NPocoDatabaseExtensions.GetTypedConnection(database.Connection);
+ SqlTransaction tTransaction =
+ NPocoDatabaseExtensions.GetTypedTransaction(command.Transaction);
+ var tableName = pocoData.TableInfo.TableName;
+
+ if (database.SqlContext.SqlSyntax is not SqlServerSyntaxProvider syntax)
{
- // use typed connection and transaction or SqlBulkCopy
- var tConnection = NPocoDatabaseExtensions.GetTypedConnection(database.Connection);
- var tTransaction = NPocoDatabaseExtensions.GetTypedTransaction(command.Transaction);
- var tableName = pocoData.TableInfo.TableName;
+ throw new NotSupportedException("SqlSyntax must be SqlServerSyntaxProvider.");
+ }
- var syntax = database.SqlContext.SqlSyntax as SqlServerSyntaxProvider;
- if (syntax == null) throw new NotSupportedException("SqlSyntax must be SqlServerSyntaxProvider.");
+ using (var copy = new SqlBulkCopy(tConnection, SqlBulkCopyOptions.Default, tTransaction)
+ {
+ // 0 = no bulk copy timeout. If a timeout occurs it will be an connection/command timeout.
+ BulkCopyTimeout = 0,
+ DestinationTableName = tableName,
- using (var copy = new SqlBulkCopy(tConnection, SqlBulkCopyOptions.Default, tTransaction)
+ // be consistent with NPoco: https://github.com/schotime/NPoco/blob/5117a55fde57547e928246c044fd40bd00b2d7d1/src/NPoco.SqlServer/SqlBulkCopyHelper.cs#L50
+ BatchSize = 4096,
+ })
+ using (var bulkReader = new PocoDataDataReader(records, pocoData, syntax))
+ {
+ // we need to add column mappings here because otherwise columns will be matched by their order and if the order of them are different in the DB compared
+ // to the order in which they are declared in the model then this will not work, so instead we will add column mappings by name so that this explicitly uses
+ // the names instead of their ordering.
+ foreach (SqlBulkCopyColumnMapping col in bulkReader.ColumnMappings)
{
- BulkCopyTimeout = 0, // 0 = no bulk copy timeout. If a timeout occurs it will be an connection/command timeout.
- DestinationTableName = tableName,
- // be consistent with NPoco: https://github.com/schotime/NPoco/blob/5117a55fde57547e928246c044fd40bd00b2d7d1/src/NPoco.SqlServer/SqlBulkCopyHelper.cs#L50
- BatchSize = 4096
- })
- using (var bulkReader = new PocoDataDataReader(records, pocoData, syntax))
- {
- //we need to add column mappings here because otherwise columns will be matched by their order and if the order of them are different in the DB compared
- //to the order in which they are declared in the model then this will not work, so instead we will add column mappings by name so that this explicitly uses
- //the names instead of their ordering.
- foreach (var col in bulkReader.ColumnMappings)
- {
- copy.ColumnMappings.Add(col.DestinationColumn, col.DestinationColumn);
- }
-
- copy.WriteToServer(bulkReader);
- return bulkReader.RecordsAffected;
+ copy.ColumnMappings.Add(col.DestinationColumn, col.DestinationColumn);
}
+
+ copy.WriteToServer(bulkReader);
+ return bulkReader.RecordsAffected;
}
}
}
diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDatabaseCreator.cs b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDatabaseCreator.cs
index 205519d0b1..dd092e820d 100644
--- a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDatabaseCreator.cs
+++ b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDatabaseCreator.cs
@@ -1,61 +1,60 @@
using Microsoft.Data.SqlClient;
using Umbraco.Cms.Infrastructure.Persistence;
-namespace Umbraco.Cms.Persistence.SqlServer.Services
+namespace Umbraco.Cms.Persistence.SqlServer.Services;
+
+public class SqlServerDatabaseCreator : IDatabaseCreator
{
- public class SqlServerDatabaseCreator : IDatabaseCreator
+ public string ProviderName => Constants.ProviderName;
+
+ public void Create(string connectionString)
{
- public string ProviderName => Constants.ProviderName;
+ var builder = new SqlConnectionStringBuilder(connectionString);
- public void Create(string connectionString)
+ // Get connection string without database specific information
+ var masterBuilder = new SqlConnectionStringBuilder(builder.ConnectionString)
{
- var builder = new SqlConnectionStringBuilder(connectionString);
+ AttachDBFilename = string.Empty,
+ InitialCatalog = string.Empty
+ };
+ var masterConnectionString = masterBuilder.ConnectionString;
- // Get connection string without database specific information
- var masterBuilder = new SqlConnectionStringBuilder(builder.ConnectionString)
+ string fileName = builder.AttachDBFilename,
+ database = builder.InitialCatalog;
+
+ // Create database
+ if (!string.IsNullOrEmpty(fileName) && !File.Exists(fileName))
+ {
+ if (string.IsNullOrWhiteSpace(database))
{
- AttachDBFilename = string.Empty,
- InitialCatalog = string.Empty
- };
- var masterConnectionString = masterBuilder.ConnectionString;
-
- string fileName = builder.AttachDBFilename,
- database = builder.InitialCatalog;
-
- // Create database
- if (!string.IsNullOrEmpty(fileName) && !File.Exists(fileName))
- {
- if (string.IsNullOrWhiteSpace(database))
- {
- // Use a temporary database name
- database = "Umbraco-" + Guid.NewGuid();
- }
-
- using var connection = new SqlConnection(masterConnectionString);
- connection.Open();
-
- using var command = new SqlCommand(
- $"CREATE DATABASE [{database}] ON (NAME='{database}', FILENAME='{fileName}');" +
- $"ALTER DATABASE [{database}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;" +
- $"EXEC sp_detach_db @dbname='{database}';",
- connection);
- command.ExecuteNonQuery();
-
- connection.Close();
+ // Use a temporary database name
+ database = "Umbraco-" + Guid.NewGuid();
}
- else if (!string.IsNullOrEmpty(database))
- {
- using var connection = new SqlConnection(masterConnectionString);
- connection.Open();
- using var command = new SqlCommand(
- $"IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = '{database}') " +
- $"CREATE DATABASE [{database}];",
- connection);
- command.ExecuteNonQuery();
+ using var connection = new SqlConnection(masterConnectionString);
+ connection.Open();
- connection.Close();
- }
+ using var command = new SqlCommand(
+ $"CREATE DATABASE [{database}] ON (NAME='{database}', FILENAME='{fileName}');" +
+ $"ALTER DATABASE [{database}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;" +
+ $"EXEC sp_detach_db @dbname='{database}';",
+ connection);
+ command.ExecuteNonQuery();
+
+ connection.Close();
+ }
+ else if (!string.IsNullOrEmpty(database))
+ {
+ using var connection = new SqlConnection(masterConnectionString);
+ connection.Open();
+
+ using var command = new SqlCommand(
+ $"IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = '{database}') " +
+ $"CREATE DATABASE [{database}];",
+ connection);
+ command.ExecuteNonQuery();
+
+ connection.Close();
}
}
}
diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDatabaseProviderMetadata.cs b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDatabaseProviderMetadata.cs
index 8c840f1778..8b36736804 100644
--- a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDatabaseProviderMetadata.cs
+++ b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDatabaseProviderMetadata.cs
@@ -5,13 +5,13 @@ using Umbraco.Cms.Infrastructure.Persistence;
namespace Umbraco.Cms.Persistence.SqlServer.Services;
///
-/// Provider metadata for SQL Server
+/// Provider metadata for SQL Server
///
[DataContract]
public class SqlServerDatabaseProviderMetadata : IDatabaseProviderMetadata
{
///
- public Guid Id => new ("5e1ad149-1951-4b74-90bf-2ac2aada9e73");
+ public Guid Id => new("5e1ad149-1951-4b74-90bf-2ac2aada9e73");
///
public int SortOrder => 2;
diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDistributedLockingMechanism.cs b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDistributedLockingMechanism.cs
index 7c8effb2b3..358eae2e38 100644
--- a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDistributedLockingMechanism.cs
+++ b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDistributedLockingMechanism.cs
@@ -13,17 +13,17 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Persistence.SqlServer.Services;
///
-/// SQL Server implementation of .
+/// SQL Server implementation of .
///
public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism
{
+ private readonly IOptionsMonitor _connectionStrings;
+ private readonly IOptionsMonitor _globalSettings;
private readonly ILogger _logger;
private readonly Lazy _scopeAccessor; // Hooray it's a circular dependency.
- private readonly IOptionsMonitor _globalSettings;
- private readonly IOptionsMonitor _connectionStrings;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
public SqlServerDistributedLockingMechanism(
ILogger logger,
@@ -39,7 +39,7 @@ public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism
///
public bool Enabled => _connectionStrings.CurrentValue.IsConnectionStringConfigured() &&
- _connectionStrings.CurrentValue.ProviderName == Constants.ProviderName;
+ string.Equals(_connectionStrings.CurrentValue.ProviderName,Constants.ProviderName, StringComparison.InvariantCultureIgnoreCase);
///
public IDistributedLock ReadLock(int lockId, TimeSpan? obtainLockTimeout = null)
@@ -104,11 +104,9 @@ public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism
public DistributedLockType LockType { get; }
- public void Dispose()
- {
+ public void Dispose() =>
// Mostly no op, cleaned up by completing transaction in scope.
_parent._logger.LogDebug("Dropped {lockType} for id {id}", LockType, LockId);
- }
public override string ToString()
=> $"SqlServerDistributedLock({LockId}, {LockType}";
@@ -124,19 +122,21 @@ public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism
if (!db.InTransaction)
{
- throw new InvalidOperationException("SqlServerDistributedLockingMechanism requires a transaction to function.");
+ throw new InvalidOperationException(
+ "SqlServerDistributedLockingMechanism requires a transaction to function.");
}
if (db.Transaction.IsolationLevel < IsolationLevel.ReadCommitted)
{
- throw new InvalidOperationException("A transaction with minimum ReadCommitted isolation level is required.");
+ throw new InvalidOperationException(
+ "A transaction with minimum ReadCommitted isolation level is required.");
}
const string query = "SELECT value FROM umbracoLock WITH (REPEATABLEREAD) WHERE id=@id";
db.Execute("SET LOCK_TIMEOUT " + _timeout.TotalMilliseconds + ";");
- var i = db.ExecuteScalar(query, new {id = LockId});
+ var i = db.ExecuteScalar(query, new { id = LockId });
if (i == null)
{
@@ -156,19 +156,22 @@ public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism
if (!db.InTransaction)
{
- throw new InvalidOperationException("SqlServerDistributedLockingMechanism requires a transaction to function.");
+ throw new InvalidOperationException(
+ "SqlServerDistributedLockingMechanism requires a transaction to function.");
}
if (db.Transaction.IsolationLevel < IsolationLevel.ReadCommitted)
{
- throw new InvalidOperationException("A transaction with minimum ReadCommitted isolation level is required.");
+ throw new InvalidOperationException(
+ "A transaction with minimum ReadCommitted isolation level is required.");
}
- const string query = @"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id";
+ const string query =
+ @"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id";
db.Execute("SET LOCK_TIMEOUT " + _timeout.TotalMilliseconds + ";");
- var i = db.Execute(query, new {id = LockId});
+ var i = db.Execute(query, new { id = LockId });
if (i == 0)
{
diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerSyntaxProvider.cs b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerSyntaxProvider.cs
index a4fc33d98c..64ed9e3566 100644
--- a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerSyntaxProvider.cs
+++ b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerSyntaxProvider.cs
@@ -1,4 +1,5 @@
using System.Data;
+using System.Data.Common;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Logging;
@@ -12,146 +13,133 @@ using Umbraco.Cms.Persistence.SqlServer.Dtos;
using Umbraco.Extensions;
using ColumnInfo = Umbraco.Cms.Infrastructure.Persistence.SqlSyntax.ColumnInfo;
-namespace Umbraco.Cms.Persistence.SqlServer.Services
+namespace Umbraco.Cms.Persistence.SqlServer.Services;
+
+///
+/// Represents an SqlSyntaxProvider for Sql Server.
+///
+public class SqlServerSyntaxProvider : MicrosoftSqlSyntaxProviderBase
{
- ///
- /// Represents an SqlSyntaxProvider for Sql Server.
- ///
- public class SqlServerSyntaxProvider : MicrosoftSqlSyntaxProviderBase
+ public enum EngineEdition
{
- private readonly IOptions _globalSettings;
- private readonly ILogger _logger;
+ Unknown = 0,
+ Desktop = 1,
+ Standard = 2,
+ Enterprise = 3, // Also developer edition
+ Express = 4,
+ Azure = 5,
+ }
- public SqlServerSyntaxProvider(IOptions globalSettings)
- : this(globalSettings, StaticApplicationLogging.CreateLogger())
+ public enum VersionName
+ {
+ Invalid = -1,
+ Unknown = 0,
+ V7 = 1,
+ V2000 = 2,
+ V2005 = 3,
+ V2008 = 4,
+ V2012 = 5,
+ V2014 = 6,
+ V2016 = 7,
+ V2017 = 8,
+ V2019 = 9,
+ Other = 99,
+ }
+
+ private readonly IOptions _globalSettings;
+ private readonly ILogger _logger;
+
+ public SqlServerSyntaxProvider(IOptions globalSettings)
+ : this(globalSettings, StaticApplicationLogging.CreateLogger())
+ {
+ }
+
+ public SqlServerSyntaxProvider(IOptions globalSettings, ILogger logger)
+ {
+ _globalSettings = globalSettings;
+ _logger = logger;
+ }
+
+ public override string ProviderName => Constants.ProviderName;
+
+ public ServerVersionInfo? ServerVersion { get; private set; }
+
+ public override string DbProvider => ServerVersion?.IsAzure ?? false ? "SqlAzure" : "SqlServer";
+
+ public override IsolationLevel DefaultIsolationLevel => IsolationLevel.ReadCommitted;
+
+ public override string DeleteDefaultConstraint => "ALTER TABLE {0} DROP CONSTRAINT {2}";
+
+ public override string DropIndex => "DROP INDEX {0} ON {1}";
+
+ public override string RenameColumn => "sp_rename '{0}.{1}', '{2}', 'COLUMN'";
+
+ public override string CreateIndex => "CREATE {0}{1}INDEX {2} ON {3} ({4}){5}";
+
+ public override DatabaseType GetUpdatedDatabaseType(DatabaseType current, string? connectionString)
+ {
+ var setting = _globalSettings.Value.DatabaseFactoryServerVersion;
+ var fromSettings = false;
+
+ if (setting.IsNullOrWhiteSpace() || !setting.StartsWith("SqlServer.")
+ || !Enum.TryParse(setting.Substring("SqlServer.".Length), out VersionName versionName, true))
{
+ versionName = GetSetVersion(connectionString, ProviderName, _logger).ProductVersionName;
}
- public SqlServerSyntaxProvider(IOptions globalSettings, ILogger logger)
+ _logger.LogDebug("SqlServer {SqlServerVersion}, DatabaseType is {DatabaseType} ({Source}).", versionName, DatabaseType.SqlServer2012, fromSettings ? "settings" : "detected");
+
+ return DatabaseType.SqlServer2012;
+ }
+
+ private static VersionName MapProductVersion(string productVersion)
+ {
+ var firstPart = string.IsNullOrWhiteSpace(productVersion)
+ ? "??"
+ : productVersion.Split(Core.Constants.CharArrays.Period)[0];
+ switch (firstPart)
{
- _globalSettings = globalSettings;
- _logger = logger;
+ case "??":
+ return VersionName.Invalid;
+ case "15":
+ return VersionName.V2019;
+ case "14":
+ return VersionName.V2017;
+ case "13":
+ return VersionName.V2016;
+ case "12":
+ return VersionName.V2014;
+ case "11":
+ return VersionName.V2012;
+ case "10":
+ return VersionName.V2008;
+ case "9":
+ return VersionName.V2005;
+ case "8":
+ return VersionName.V2000;
+ case "7":
+ return VersionName.V7;
+ default:
+ return VersionName.Other;
+ }
+ }
+
+ internal ServerVersionInfo GetSetVersion(string? connectionString, string? providerName, ILogger logger)
+ {
+ // var factory = DbProviderFactories.GetFactory(providerName);
+ SqlClientFactory? factory = SqlClientFactory.Instance;
+ DbConnection? connection = factory.CreateConnection();
+
+ if (connection == null)
+ {
+ throw new InvalidOperationException($"Could not create a connection for provider \"{providerName}\".");
}
- public override string ProviderName => Constants.ProviderName;
+ // Edition: "Express Edition", "Windows Azure SQL Database..."
+ // EngineEdition: 1/Desktop 2/Standard 3/Enterprise 4/Express 5/Azure
+ // ProductLevel: RTM, SPx, CTP...
- public ServerVersionInfo? ServerVersion { get; private set; }
-
- public enum VersionName
- {
- Invalid = -1,
- Unknown = 0,
- V7 = 1,
- V2000 = 2,
- V2005 = 3,
- V2008 = 4,
- V2012 = 5,
- V2014 = 6,
- V2016 = 7,
- V2017 = 8,
- V2019 = 9,
- Other = 99
- }
-
- public enum EngineEdition
- {
- Unknown = 0,
- Desktop = 1,
- Standard = 2,
- Enterprise = 3,// Also developer edition
- Express = 4,
- Azure = 5
- }
-
- public override DatabaseType GetUpdatedDatabaseType(DatabaseType current, string? connectionString)
- {
- var setting = _globalSettings.Value.DatabaseFactoryServerVersion;
- var fromSettings = false;
-
- if (setting.IsNullOrWhiteSpace() || !setting.StartsWith("SqlServer.")
- || !Enum.TryParse(setting.Substring("SqlServer.".Length), out var versionName, true))
- {
- versionName = GetSetVersion(connectionString, ProviderName, _logger).ProductVersionName;
- }
-
- _logger.LogDebug("SqlServer {SqlServerVersion}, DatabaseType is {DatabaseType} ({Source}).", versionName, DatabaseType.SqlServer2012, fromSettings ? "settings" : "detected");
-
- return DatabaseType.SqlServer2012;
- }
-
- public class ServerVersionInfo
- {
- public ServerVersionInfo()
- {
- ProductVersionName = VersionName.Unknown;
- EngineEdition = EngineEdition.Unknown;
- }
-
- public ServerVersionInfo(string edition, string instanceName, string productVersion, EngineEdition engineEdition, string machineName, string productLevel)
- {
- Edition = edition;
- InstanceName = instanceName;
- ProductVersion = productVersion;
- ProductVersionName = MapProductVersion(ProductVersion);
- EngineEdition = engineEdition;
- MachineName = machineName;
- ProductLevel = productLevel;
- }
-
- public string? Edition { get; }
- public string? InstanceName { get; }
- public string? ProductVersion { get; }
- public VersionName ProductVersionName { get; }
- public EngineEdition EngineEdition { get; }
- public bool IsAzure => EngineEdition == EngineEdition.Azure;
- public string? MachineName { get; }
- public string? ProductLevel { get; }
- }
-
- private static VersionName MapProductVersion(string productVersion)
- {
- var firstPart = string.IsNullOrWhiteSpace(productVersion) ? "??" : productVersion.Split(Cms.Core.Constants.CharArrays.Period)[0];
- switch (firstPart)
- {
- case "??":
- return VersionName.Invalid;
- case "15":
- return VersionName.V2019;
- case "14":
- return VersionName.V2017;
- case "13":
- return VersionName.V2016;
- case "12":
- return VersionName.V2014;
- case "11":
- return VersionName.V2012;
- case "10":
- return VersionName.V2008;
- case "9":
- return VersionName.V2005;
- case "8":
- return VersionName.V2000;
- case "7":
- return VersionName.V7;
- default:
- return VersionName.Other;
- }
- }
-
- internal ServerVersionInfo GetSetVersion(string? connectionString, string? providerName, ILogger logger)
- {
- //var factory = DbProviderFactories.GetFactory(providerName);
- var factory = SqlClientFactory.Instance;
- var connection = factory.CreateConnection();
-
- if (connection == null)
- throw new InvalidOperationException($"Could not create a connection for provider \"{providerName}\".");
-
- // Edition: "Express Edition", "Windows Azure SQL Database..."
- // EngineEdition: 1/Desktop 2/Standard 3/Enterprise 4/Express 5/Azure
- // ProductLevel: RTM, SPx, CTP...
-
- const string sql = @"select
+ const string sql = @"select
SERVERPROPERTY('Edition') Edition,
SERVERPROPERTY('EditionID') EditionId,
SERVERPROPERTY('InstanceName') InstanceName,
@@ -163,99 +151,102 @@ namespace Umbraco.Cms.Persistence.SqlServer.Services
SERVERPROPERTY('ResourceLastUpdateDateTime') ResourceLastUpdateDateTime,
SERVERPROPERTY('ProductLevel') ProductLevel;";
- string GetString(IDataReader reader, int ordinal, string defaultValue)
- => reader.IsDBNull(ordinal) ? defaultValue : reader.GetString(ordinal);
+ string GetString(IDataReader reader, int ordinal, string defaultValue)
+ {
+ return reader.IsDBNull(ordinal) ? defaultValue : reader.GetString(ordinal);
+ }
- int GetInt32(IDataReader reader, int ordinal, int defaultValue)
- => reader.IsDBNull(ordinal) ? defaultValue : reader.GetInt32(ordinal);
+ int GetInt32(IDataReader reader, int ordinal, int defaultValue)
+ {
+ return reader.IsDBNull(ordinal) ? defaultValue : reader.GetInt32(ordinal);
+ }
- connection.ConnectionString = connectionString;
- ServerVersionInfo version;
- using (connection)
+ connection.ConnectionString = connectionString;
+ ServerVersionInfo version;
+ using (connection)
+ {
+ try
{
- try
+ connection.Open();
+ DbCommand command = connection.CreateCommand();
+ command.CommandText = sql;
+ using (DbDataReader reader = command.ExecuteReader())
{
- connection.Open();
- var command = connection.CreateCommand();
- command.CommandText = sql;
- using (var reader = command.ExecuteReader())
- {
- reader.Read();
- // InstanceName can be NULL for the default instance
- version = new ServerVersionInfo(
- GetString(reader, 0, "Unknown"),
- GetString(reader, 2, "(default)"),
- GetString(reader, 3, string.Empty),
- (EngineEdition) GetInt32(reader, 5, 0),
- GetString(reader, 7, "DEFAULT"),
- GetString(reader, 9, "Unknown"));
- }
- connection.Close();
- }
- catch (Exception e)
- {
- logger.LogError(e, "Failed to detected SqlServer version.");
- version = new ServerVersionInfo(); // all unknown
+ reader.Read();
+ // InstanceName can be NULL for the default instance
+ version = new ServerVersionInfo(
+ GetString(reader, 0, "Unknown"),
+ GetString(reader, 2, "(default)"),
+ GetString(reader, 3, string.Empty),
+ (EngineEdition)GetInt32(reader, 5, 0),
+ GetString(reader, 7, "DEFAULT"),
+ GetString(reader, 9, "Unknown"));
}
+
+ connection.Close();
+ }
+ catch (Exception e)
+ {
+ logger.LogError(e, "Failed to detected SqlServer version.");
+ version = new ServerVersionInfo(); // all unknown
}
-
- return ServerVersion = version;
}
- ///
- /// SQL Server stores default values assigned to columns as constraints, it also stores them with named values, this is the only
- /// server type that does this, therefore this method doesn't exist on any other syntax provider
- ///
- ///
- public IEnumerable> GetDefaultConstraintsPerColumn(IDatabase db)
- {
- var items = db.Fetch("SELECT TableName = t.Name, ColumnName = c.Name, dc.Name, dc.[Definition] FROM sys.tables t INNER JOIN sys.default_constraints dc ON t.object_id = dc.parent_object_id INNER JOIN sys.columns c ON dc.parent_object_id = c.object_id AND c.column_id = dc.parent_column_id INNER JOIN sys.schemas as s on t.[schema_id] = s.[schema_id] WHERE s.name = (SELECT SCHEMA_NAME())");
- return items.Select(x => new Tuple(x.TableName, x.ColumnName, x.Name, x.Definition));
- }
+ return ServerVersion = version;
+ }
- public override string DbProvider => ServerVersion?.IsAzure ?? false ? "SqlAzure" : "SqlServer";
+ ///
+ /// SQL Server stores default values assigned to columns as constraints, it also stores them with named values, this is
+ /// the only
+ /// server type that does this, therefore this method doesn't exist on any other syntax provider
+ ///
+ ///
+ public IEnumerable> GetDefaultConstraintsPerColumn(IDatabase db)
+ {
+ List? items = db.Fetch(
+ "SELECT TableName = t.Name, ColumnName = c.Name, dc.Name, dc.[Definition] FROM sys.tables t INNER JOIN sys.default_constraints dc ON t.object_id = dc.parent_object_id INNER JOIN sys.columns c ON dc.parent_object_id = c.object_id AND c.column_id = dc.parent_column_id INNER JOIN sys.schemas as s on t.[schema_id] = s.[schema_id] WHERE s.name = (SELECT SCHEMA_NAME())");
+ return items.Select(x =>
+ new Tuple(x.TableName, x.ColumnName, x.Name, x.Definition));
+ }
- public override IEnumerable GetTablesInSchema(IDatabase db)
- {
- return db.Fetch("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())");
- }
+ public override IEnumerable GetTablesInSchema(IDatabase db) => db.Fetch(
+ "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())");
- public override IsolationLevel DefaultIsolationLevel => IsolationLevel.ReadCommitted;
+ public override IEnumerable GetColumnsInSchema(IDatabase db)
+ {
+ List? items = db.Fetch(
+ "SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())");
+ return
+ items.Select(
+ item =>
+ new ColumnInfo(item.TableName, item.ColumnName, item.OrdinalPosition, item.ColumnDefault, item.IsNullable, item.DataType)).ToList();
+ }
- public override IEnumerable GetColumnsInSchema(IDatabase db)
- {
- var items = db.Fetch("SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())");
- return
- items.Select(
- item =>
- new ColumnInfo(item.TableName, item.ColumnName, item.OrdinalPosition, item.ColumnDefault,
- item.IsNullable, item.DataType)).ToList();
- }
+ ///
+ public override IEnumerable> GetConstraintsPerTable(IDatabase db)
+ {
+ List items =
+ db.Fetch(
+ "SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())");
+ return items.Select(item => new Tuple(item.TableName, item.ConstraintName)).ToList();
+ }
- ///
- public override IEnumerable> GetConstraintsPerTable(IDatabase db)
- {
- var items =
- db.Fetch(
- "SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())");
- return items.Select(item => new Tuple(item.TableName, item.ConstraintName)).ToList();
- }
+ ///
+ public override IEnumerable> GetConstraintsPerColumn(IDatabase db)
+ {
+ List? items =
+ db.Fetch(
+ "SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())");
+ return items.Select(item =>
+ new Tuple(item.TableName, item.ColumnName, item.ConstraintName)).ToList();
+ }
- ///
- public override IEnumerable> GetConstraintsPerColumn(IDatabase db)
- {
- var items =
- db.Fetch(
- "SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())");
- return items.Select(item => new Tuple(item.TableName, item.ColumnName, item.ConstraintName)).ToList();
- }
-
- ///
- public override IEnumerable> GetDefinedIndexes(IDatabase db)
- {
- var items =
- db.Fetch(
- @"select T.name as TABLE_NAME, I.name as INDEX_NAME, AC.Name as COLUMN_NAME,
+ ///
+ public override IEnumerable> GetDefinedIndexes(IDatabase db)
+ {
+ List? items =
+ db.Fetch(
+ @"select T.name as TABLE_NAME, I.name as INDEX_NAME, AC.Name as COLUMN_NAME,
CASE WHEN I.is_unique_constraint = 1 OR I.is_unique = 1 THEN 1 ELSE 0 END AS [UNIQUE]
from sys.tables as T inner join sys.indexes as I on T.[object_id] = I.[object_id]
inner join sys.index_columns as IC on IC.[object_id] = I.[object_id] and IC.[index_id] = I.[index_id]
@@ -263,182 +254,203 @@ from sys.tables as T inner join sys.indexes as I on T.[object_id] = I.[object_id
inner join sys.schemas as S on T.[schema_id] = S.[schema_id]
WHERE S.name = (SELECT SCHEMA_NAME()) AND I.is_primary_key = 0
order by T.name, I.name");
- return items.Select(item => new Tuple(item.TableName, item.IndexName, item.ColumnName,
- item.Unique == 1)).ToList();
+ return items.Select(item => new Tuple(item.TableName, item.IndexName, item.ColumnName, item.Unique == 1)).ToList();
+ }
- }
-
- ///
- public override bool TryGetDefaultConstraint(IDatabase db, string? tableName, string columnName, [MaybeNullWhen(false)] out string constraintName)
- {
- constraintName = db.Fetch(@"select con.[name] as [constraintName]
+ ///
+ public override bool TryGetDefaultConstraint(IDatabase db, string? tableName, string columnName, [MaybeNullWhen(false)] out string constraintName)
+ {
+ constraintName = db.Fetch(
+ @"select con.[name] as [constraintName]
from sys.default_constraints con
join sys.columns col on con.object_id=col.default_object_id
join sys.tables tbl on col.object_id=tbl.object_id
-where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName)
- .FirstOrDefault();
- return !constraintName.IsNullOrWhiteSpace();
- }
-
- public override bool DoesTableExist(IDatabase db, string tableName)
- {
- var result =
- db.ExecuteScalar("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @TableName AND TABLE_SCHEMA = (SELECT SCHEMA_NAME())",
- new { TableName = tableName });
-
- return result > 0;
- }
-
- public override string FormatColumnRename(string? tableName, string? oldName, string? newName)
- {
- return string.Format(RenameColumn, tableName, oldName, newName);
- }
-
- public override string FormatTableRename(string? oldName, string? newName)
- {
- return string.Format(RenameTable, oldName, newName);
- }
-
- protected override string FormatIdentity(ColumnDefinition column)
- {
- return column.IsIdentity ? GetIdentityString(column) : string.Empty;
- }
-
- public override Sql SelectTop(Sql sql, int top)
- {
- return new Sql(sql.SqlContext, sql.SQL.Insert(sql.SQL.IndexOf(' '), " TOP " + top), sql.Arguments);
- }
-
- private static string GetIdentityString(ColumnDefinition column)
- {
- return "IDENTITY(1,1)";
- }
-
- protected override string? FormatSystemMethods(SystemMethods systemMethod)
- {
- switch (systemMethod)
- {
- case SystemMethods.NewGuid:
- return "NEWID()";
- case SystemMethods.CurrentDateTime:
- return "GETDATE()";
- //case SystemMethods.NewSequentialId:
- // return "NEWSEQUENTIALID()";
- //case SystemMethods.CurrentUTCDateTime:
- // return "GETUTCDATE()";
- }
-
- return null;
- }
-
- public override string DeleteDefaultConstraint => "ALTER TABLE {0} DROP CONSTRAINT {2}";
-
- public override string DropIndex => "DROP INDEX {0} ON {1}";
-
- public override string RenameColumn => "sp_rename '{0}.{1}', '{2}', 'COLUMN'";
-
- public override string CreateIndex => "CREATE {0}{1}INDEX {2} ON {3} ({4}){5}";
- public override string Format(IndexDefinition index)
- {
- var name = string.IsNullOrEmpty(index.Name)
- ? $"IX_{index.TableName}_{index.ColumnName}"
- : index.Name;
-
- var columns = index.Columns.Any()
- ? string.Join(",", index.Columns.Select(x => GetQuotedColumnName(x.Name)))
- : GetQuotedColumnName(index.ColumnName);
-
- var includeColumns = index.IncludeColumns?.Any() ?? false
- ? $" INCLUDE ({string.Join(",", index.IncludeColumns.Select(x => GetQuotedColumnName(x.Name)))})"
- : string.Empty;
-
- return string.Format(CreateIndex, GetIndexType(index.IndexType), " ", GetQuotedName(name),
- GetQuotedTableName(index.TableName), columns, includeColumns);
- }
-
-
- public override Sql InsertForUpdateHint(Sql sql)
- {
- // go find the first FROM clause, and append the lock hint
- Sql s = sql;
- var updated = false;
-
- while (s != null)
- {
- var sqlText = SqlInspector.GetSqlText(s);
- if (sqlText.StartsWith("FROM ", StringComparison.OrdinalIgnoreCase))
- {
- SqlInspector.SetSqlText(s, sqlText + " WITH (UPDLOCK)");
- updated = true;
- break;
- }
-
- s = SqlInspector.GetSqlRhs(sql);
- }
-
- if (updated)
- SqlInspector.Reset(sql);
-
- return sql;
- }
-
- public override Sql AppendForUpdateHint(Sql sql)
- => sql.Append(" WITH (UPDLOCK) ");
-
- public override Sql.SqlJoinClause LeftJoinWithNestedJoin(
- Sql sql,
- Func,
- Sql> nestedJoin,
- string? alias = null)
- {
- Type type = typeof(TDto);
-
- var tableName = GetQuotedTableName(type.GetTableName());
- var join = tableName;
-
- if (alias != null)
- {
- var quotedAlias = GetQuotedTableName(alias);
- join += " " + quotedAlias;
- }
-
- var nestedSql = new Sql(sql.SqlContext);
- nestedSql = nestedJoin(nestedSql);
-
- Sql.SqlJoinClause sqlJoin = sql.LeftJoin(join);
- sql.Append(nestedSql);
- return sqlJoin;
- }
-
- #region Sql Inspection
-
- private static SqlInspectionUtilities? _sqlInspector;
-
- private static SqlInspectionUtilities SqlInspector => _sqlInspector ?? (_sqlInspector = new SqlInspectionUtilities());
-
- private class SqlInspectionUtilities
- {
- private readonly Func _getSqlText;
- private readonly Action _setSqlText;
- private readonly Func _getSqlRhs;
- private readonly Action _setSqlFinal;
-
- public SqlInspectionUtilities()
- {
- (_getSqlText, _setSqlText) = ReflectionUtilities.EmitFieldGetterAndSetter("_sql");
- _getSqlRhs = ReflectionUtilities.EmitFieldGetter("_rhs");
- _setSqlFinal = ReflectionUtilities.EmitFieldSetter("_sqlFinal");
- }
-
- public string GetSqlText(Sql sql) => _getSqlText(sql);
-
- public void SetSqlText(Sql sql, string sqlText) => _setSqlText(sql, sqlText);
-
- public Sql GetSqlRhs(Sql sql) => _getSqlRhs(sql);
-
- public void Reset(Sql sql) => _setSqlFinal(sql, null);
- }
-
- #endregion
+where tbl.[name]=@0 and col.[name]=@1;",
+ tableName,
+ columnName)
+ .FirstOrDefault();
+ return !constraintName.IsNullOrWhiteSpace();
}
+
+ public override bool DoesTableExist(IDatabase db, string tableName)
+ {
+ var result =
+ db.ExecuteScalar(
+ "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @TableName AND TABLE_SCHEMA = (SELECT SCHEMA_NAME())",
+ new { TableName = tableName });
+
+ return result > 0;
+ }
+
+ public override string FormatColumnRename(string? tableName, string? oldName, string? newName) =>
+ string.Format(RenameColumn, tableName, oldName, newName);
+
+ public override string FormatTableRename(string? oldName, string? newName) =>
+ string.Format(RenameTable, oldName, newName);
+
+ protected override string FormatIdentity(ColumnDefinition column) =>
+ column.IsIdentity ? GetIdentityString(column) : string.Empty;
+
+ public override Sql SelectTop(Sql sql, int top) => new Sql(sql.SqlContext, sql.SQL.Insert(sql.SQL.IndexOf(' '), " TOP " + top), sql.Arguments);
+
+ private static string GetIdentityString(ColumnDefinition column) => "IDENTITY(1,1)";
+
+ protected override string? FormatSystemMethods(SystemMethods systemMethod)
+ {
+ switch (systemMethod)
+ {
+ case SystemMethods.NewGuid:
+ return "NEWID()";
+ case SystemMethods.CurrentDateTime:
+ return "GETDATE()";
+
+ // case SystemMethods.NewSequentialId:
+ // return "NEWSEQUENTIALID()";
+ // case SystemMethods.CurrentUTCDateTime:
+ // return "GETUTCDATE()";
+ }
+
+ return null;
+ }
+
+ public override string Format(IndexDefinition index)
+ {
+ var name = string.IsNullOrEmpty(index.Name)
+ ? $"IX_{index.TableName}_{index.ColumnName}"
+ : index.Name;
+
+ var columns = index.Columns.Any()
+ ? string.Join(",", index.Columns.Select(x => GetQuotedColumnName(x.Name)))
+ : GetQuotedColumnName(index.ColumnName);
+
+ var includeColumns = index.IncludeColumns?.Any() ?? false
+ ? $" INCLUDE ({string.Join(",", index.IncludeColumns.Select(x => GetQuotedColumnName(x.Name)))})"
+ : string.Empty;
+
+ return string.Format(CreateIndex, GetIndexType(index.IndexType), " ", GetQuotedName(name), GetQuotedTableName(index.TableName), columns, includeColumns);
+ }
+
+
+ public override Sql InsertForUpdateHint(Sql sql)
+ {
+ // go find the first FROM clause, and append the lock hint
+ Sql s = sql;
+ var updated = false;
+
+ while (s != null)
+ {
+ var sqlText = SqlInspector.GetSqlText(s);
+ if (sqlText.StartsWith("FROM ", StringComparison.OrdinalIgnoreCase))
+ {
+ SqlInspector.SetSqlText(s, sqlText + " WITH (UPDLOCK)");
+ updated = true;
+ break;
+ }
+
+ s = SqlInspector.GetSqlRhs(sql);
+ }
+
+ if (updated)
+ {
+ SqlInspector.Reset(sql);
+ }
+
+ return sql;
+ }
+
+ public override Sql AppendForUpdateHint(Sql sql)
+ => sql.Append(" WITH (UPDLOCK) ");
+
+ public override Sql.SqlJoinClause LeftJoinWithNestedJoin(
+ Sql sql,
+ Func,
+ Sql> nestedJoin,
+ string? alias = null)
+ {
+ Type type = typeof(TDto);
+
+ var tableName = GetQuotedTableName(type.GetTableName());
+ var join = tableName;
+
+ if (alias != null)
+ {
+ var quotedAlias = GetQuotedTableName(alias);
+ join += " " + quotedAlias;
+ }
+
+ var nestedSql = new Sql(sql.SqlContext);
+ nestedSql = nestedJoin(nestedSql);
+
+ Sql.SqlJoinClause sqlJoin = sql.LeftJoin(join);
+ sql.Append(nestedSql);
+ return sqlJoin;
+ }
+
+ public class ServerVersionInfo
+ {
+ public ServerVersionInfo()
+ {
+ ProductVersionName = VersionName.Unknown;
+ EngineEdition = EngineEdition.Unknown;
+ }
+
+ public ServerVersionInfo(string edition, string instanceName, string productVersion, EngineEdition engineEdition, string machineName, string productLevel)
+ {
+ Edition = edition;
+ InstanceName = instanceName;
+ ProductVersion = productVersion;
+ ProductVersionName = MapProductVersion(ProductVersion);
+ EngineEdition = engineEdition;
+ MachineName = machineName;
+ ProductLevel = productLevel;
+ }
+
+ public string? Edition { get; }
+
+ public string? InstanceName { get; }
+
+ public string? ProductVersion { get; }
+
+ public VersionName ProductVersionName { get; }
+
+ public EngineEdition EngineEdition { get; }
+
+ public bool IsAzure => EngineEdition == EngineEdition.Azure;
+
+ public string? MachineName { get; }
+
+ public string? ProductLevel { get; }
+ }
+
+ #region Sql Inspection
+
+ private static SqlInspectionUtilities? _sqlInspector;
+
+ private static SqlInspectionUtilities SqlInspector =>
+_sqlInspector ??= new SqlInspectionUtilities();
+
+ private class SqlInspectionUtilities
+ {
+ private readonly Func _getSqlRhs;
+ private readonly Func _getSqlText;
+ private readonly Action _setSqlFinal;
+ private readonly Action _setSqlText;
+
+ public SqlInspectionUtilities()
+ {
+ (_getSqlText, _setSqlText) = ReflectionUtilities.EmitFieldGetterAndSetter("_sql");
+ _getSqlRhs = ReflectionUtilities.EmitFieldGetter("_rhs");
+ _setSqlFinal = ReflectionUtilities.EmitFieldSetter("_sqlFinal");
+ }
+
+ public string GetSqlText(Sql sql) => _getSqlText(sql);
+
+ public void SetSqlText(Sql sql, string sqlText) => _setSqlText(sql, sqlText);
+
+ public Sql GetSqlRhs(Sql sql) => _getSqlRhs(sql);
+
+ public void Reset(Sql sql) => _setSqlFinal(sql, null);
+ }
+
+ #endregion
}
diff --git a/src/Umbraco.Cms.Persistence.SqlServer/SqlServerComposer.cs b/src/Umbraco.Cms.Persistence.SqlServer/SqlServerComposer.cs
index 60d64c09df..e81a65e74f 100644
--- a/src/Umbraco.Cms.Persistence.SqlServer/SqlServerComposer.cs
+++ b/src/Umbraco.Cms.Persistence.SqlServer/SqlServerComposer.cs
@@ -4,7 +4,7 @@ using Umbraco.Cms.Core.DependencyInjection;
namespace Umbraco.Cms.Persistence.SqlServer;
///
-/// Automatically adds SQL Server support to Umbraco when this project is referenced.
+/// Automatically adds SQL Server support to Umbraco when this project is referenced.
///
public class SqlServerComposer : IComposer
{
diff --git a/src/Umbraco.Cms.Persistence.SqlServer/UmbracoBuilderExtensions.cs b/src/Umbraco.Cms.Persistence.SqlServer/UmbracoBuilderExtensions.cs
index 4b9fb8d11a..a47e92bf2e 100644
--- a/src/Umbraco.Cms.Persistence.SqlServer/UmbracoBuilderExtensions.cs
+++ b/src/Umbraco.Cms.Persistence.SqlServer/UmbracoBuilderExtensions.cs
@@ -13,27 +13,34 @@ using Umbraco.Cms.Persistence.SqlServer.Services;
namespace Umbraco.Cms.Persistence.SqlServer;
///
-/// SQLite support extensions for IUmbracoBuilder.
+/// SQLite support extensions for IUmbracoBuilder.
///
public static class UmbracoBuilderExtensions
{
///
- /// Add required services for SQL Server support.
+ /// Add required services for SQL Server support.
///
public static IUmbracoBuilder AddUmbracoSqlServerSupport(this IUmbracoBuilder builder)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton());
- builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton());
+ builder.Services.TryAddEnumerable(ServiceDescriptor
+ .Singleton());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton());
- builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton());
- builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton());
- builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton());
+ builder.Services.TryAddEnumerable(ServiceDescriptor
+ .Singleton());
+ builder.Services.TryAddEnumerable(ServiceDescriptor
+ .Singleton());
+ builder.Services.TryAddEnumerable(ServiceDescriptor
+ .Singleton());
- builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton());
+ builder.Services.TryAddEnumerable(ServiceDescriptor
+ .Singleton());
- builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton());
- builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton());
+ builder.Services.TryAddEnumerable(ServiceDescriptor
+ .Singleton());
+ builder.Services.TryAddEnumerable(ServiceDescriptor
+ .Singleton());
DbProviderFactories.UnregisterFactory(Constants.ProviderName);
DbProviderFactories.RegisterFactory(Constants.ProviderName, SqlClientFactory.Instance);
diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Constants.cs b/src/Umbraco.Cms.Persistence.Sqlite/Constants.cs
index 76e408423c..5c10f46828 100644
--- a/src/Umbraco.Cms.Persistence.Sqlite/Constants.cs
+++ b/src/Umbraco.Cms.Persistence.Sqlite/Constants.cs
@@ -1,12 +1,15 @@
namespace Umbraco.Cms.Persistence.Sqlite;
///
-/// Constants related to SQLite.
+/// Constants related to SQLite.
///
public static class Constants
{
///
- /// SQLite provider name.
+ /// SQLite provider name.
///
- public const string ProviderName = "Microsoft.Data.SQLite";
+ public const string ProviderName = "Microsoft.Data.Sqlite";
+
+ [Obsolete("This will be removed in Umbraco 12. Use Constants.ProviderName instead")]
+ public const string ProviderNameLegacy = "Microsoft.Data.SQLite";
}
diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Interceptors/SqliteAddMiniProfilerInterceptor.cs b/src/Umbraco.Cms.Persistence.Sqlite/Interceptors/SqliteAddMiniProfilerInterceptor.cs
index eb76319040..9d7f3b29ad 100644
--- a/src/Umbraco.Cms.Persistence.Sqlite/Interceptors/SqliteAddMiniProfilerInterceptor.cs
+++ b/src/Umbraco.Cms.Persistence.Sqlite/Interceptors/SqliteAddMiniProfilerInterceptor.cs
@@ -1,11 +1,12 @@
using System.Data.Common;
using NPoco;
using StackExchange.Profiling;
+using StackExchange.Profiling.Data;
namespace Umbraco.Cms.Persistence.Sqlite.Interceptors;
public class SqliteAddMiniProfilerInterceptor : SqliteConnectionInterceptor
{
public override DbConnection OnConnectionOpened(IDatabase database, DbConnection conn)
- => new StackExchange.Profiling.Data.ProfiledDbConnection(conn, MiniProfiler.Current);
+ => new ProfiledDbConnection(conn, MiniProfiler.Current);
}
diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Interceptors/SqliteAddPreferDeferredInterceptor.cs b/src/Umbraco.Cms.Persistence.Sqlite/Interceptors/SqliteAddPreferDeferredInterceptor.cs
index ef22e9c0b6..5c195291b0 100644
--- a/src/Umbraco.Cms.Persistence.Sqlite/Interceptors/SqliteAddPreferDeferredInterceptor.cs
+++ b/src/Umbraco.Cms.Persistence.Sqlite/Interceptors/SqliteAddPreferDeferredInterceptor.cs
@@ -8,5 +8,6 @@ namespace Umbraco.Cms.Persistence.Sqlite.Interceptors;
public class SqliteAddPreferDeferredInterceptor : SqliteConnectionInterceptor
{
public override DbConnection OnConnectionOpened(IDatabase database, DbConnection conn)
- => new SqlitePreferDeferredTransactionsConnection(conn as SqliteConnection ?? throw new InvalidOperationException());
+ => new SqlitePreferDeferredTransactionsConnection(conn as SqliteConnection ??
+ throw new InvalidOperationException());
}
diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Mappers/SqliteGuidScalarMapper.cs b/src/Umbraco.Cms.Persistence.Sqlite/Mappers/SqliteGuidScalarMapper.cs
index bd9bb1924d..eac4152df4 100644
--- a/src/Umbraco.Cms.Persistence.Sqlite/Mappers/SqliteGuidScalarMapper.cs
+++ b/src/Umbraco.Cms.Persistence.Sqlite/Mappers/SqliteGuidScalarMapper.cs
@@ -14,7 +14,7 @@ public class SqliteNullableGuidScalarMapper : ScalarMapper
{
if (value is null || value == DBNull.Value)
{
- return default(Guid?);
+ return default;
}
return Guid.TryParse($"{value}", out Guid result)
diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Mappers/SqlitePocoGuidMapper.cs b/src/Umbraco.Cms.Persistence.Sqlite/Mappers/SqlitePocoGuidMapper.cs
index f7b2836f1a..930478e453 100644
--- a/src/Umbraco.Cms.Persistence.Sqlite/Mappers/SqlitePocoGuidMapper.cs
+++ b/src/Umbraco.Cms.Persistence.Sqlite/Mappers/SqlitePocoGuidMapper.cs
@@ -8,7 +8,7 @@ public class SqlitePocoGuidMapper : DefaultMapper
{
if (destType == typeof(Guid))
{
- return (value) =>
+ return value =>
{
var result = Guid.Parse($"{value}");
return result;
@@ -17,7 +17,7 @@ public class SqlitePocoGuidMapper : DefaultMapper
if (destType == typeof(Guid?))
{
- return (value) =>
+ return value =>
{
if (Guid.TryParse($"{value}", out Guid result))
{
diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteBulkSqlInsertProvider.cs b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteBulkSqlInsertProvider.cs
index 895ee21ef6..ff0a64a74b 100644
--- a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteBulkSqlInsertProvider.cs
+++ b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteBulkSqlInsertProvider.cs
@@ -4,7 +4,7 @@ using Umbraco.Cms.Infrastructure.Persistence;
namespace Umbraco.Cms.Persistence.Sqlite.Services;
///
-/// Implements for SQLite.
+/// Implements for SQLite.
///
public class SqliteBulkSqlInsertProvider : IBulkSqlInsertProvider
{
@@ -12,17 +12,23 @@ public class SqliteBulkSqlInsertProvider : IBulkSqlInsertProvider
public int BulkInsertRecords(IUmbracoDatabase database, IEnumerable records)
{
- var recordsA = records.ToArray();
- if (recordsA.Length == 0) return 0;
+ T[] recordsA = records.ToArray();
+ if (recordsA.Length == 0)
+ {
+ return 0;
+ }
- var pocoData = database.PocoDataFactory.ForType(typeof(T));
- if (pocoData == null) throw new InvalidOperationException("Could not find PocoData for " + typeof(T));
+ PocoData? pocoData = database.PocoDataFactory.ForType(typeof(T));
+ if (pocoData == null)
+ {
+ throw new InvalidOperationException("Could not find PocoData for " + typeof(T));
+ }
return BulkInsertRecordsSqlite(database, pocoData, recordsA);
}
///
- /// Bulk-insert records using SqlServer BulkCopy method.
+ /// Bulk-insert records using SqlServer BulkCopy method.
///
/// The type of the records.
/// The database.
@@ -39,7 +45,7 @@ public class SqliteBulkSqlInsertProvider : IBulkSqlInsertProvider
database.BeginTransaction();
}
- foreach (var record in records)
+ foreach (T record in records)
{
database.Insert(record);
count++;
diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDatabaseCreator.cs b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDatabaseCreator.cs
index 84c8eea1db..54d6063ad7 100644
--- a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDatabaseCreator.cs
+++ b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDatabaseCreator.cs
@@ -1,4 +1,3 @@
-using System.Diagnostics;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Infrastructure.Persistence;
@@ -6,38 +5,35 @@ using Umbraco.Cms.Infrastructure.Persistence;
namespace Umbraco.Cms.Persistence.Sqlite.Services;
///
-/// Implements for SQLite.
+/// Implements for SQLite.
///
public class SqliteDatabaseCreator : IDatabaseCreator
{
private readonly ILogger _logger;
+ public SqliteDatabaseCreator(ILogger logger) => _logger = logger;
+
///
public string ProviderName => Constants.ProviderName;
- public SqliteDatabaseCreator(ILogger logger)
- {
- _logger = logger;
- }
-
///
- /// Creates a SQLite database file.
+ /// Creates a SQLite database file.
///
///
- ///
- /// With journal_mode = wal we have snapshot isolation.
- ///
- ///
- /// Concurrent read/write can take occur, committing a write transaction will have no impact
- /// on open read transactions as they see only committed data from the point in time that they began reading.
- ///
- ///
- /// A write transaction still requires exclusive access to database files so concurrent writes are not possible.
- ///
- ///
- /// Read more Isolation in SQLite
- /// Read more Write-Ahead Logging
- ///
+ ///
+ /// With journal_mode = wal we have snapshot isolation.
+ ///
+ ///
+ /// Concurrent read/write can take occur, committing a write transaction will have no impact
+ /// on open read transactions as they see only committed data from the point in time that they began reading.
+ ///
+ ///
+ /// A write transaction still requires exclusive access to database files so concurrent writes are not possible.
+ ///
+ ///
+ /// Read more Isolation in SQLite
+ /// Read more Write-Ahead Logging
+ ///
///
public void Create(string connectionString)
{
@@ -88,7 +84,7 @@ public class SqliteDatabaseCreator : IDatabaseCreator
// Copy our blank(ish) wal mode sqlite database to its final location.
try
{
- File.Copy(tempFile, original.DataSource, overwrite: true);
+ File.Copy(tempFile, original.DataSource, true);
}
catch (Exception ex)
{
diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDatabaseProviderMetadata.cs b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDatabaseProviderMetadata.cs
index 0b684551c7..54a7a5cb1d 100644
--- a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDatabaseProviderMetadata.cs
+++ b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDatabaseProviderMetadata.cs
@@ -10,7 +10,7 @@ namespace Umbraco.Cms.Persistence.Sqlite.Services;
public class SqliteDatabaseProviderMetadata : IDatabaseProviderMetadata
{
///
- public Guid Id => new ("530386a2-b219-4d5f-b68c-b965e14c9ac9");
+ public Guid Id => new("530386a2-b219-4d5f-b68c-b965e14c9ac9");
///
public int SortOrder => -1;
@@ -47,12 +47,12 @@ public class SqliteDatabaseProviderMetadata : IDatabaseProviderMetadata
///
///
- ///
- /// Required to ensure database creator is used regardless of configured InstallMissingDatabase value.
- ///
- ///
- /// Ensures database setup with journal_mode = wal;
- ///
+ ///
+ /// Required to ensure database creator is used regardless of configured InstallMissingDatabase value.
+ ///
+ ///
+ /// Ensures database setup with journal_mode = wal;
+ ///
///
public bool ForceCreateDatabase => true;
@@ -64,7 +64,7 @@ public class SqliteDatabaseProviderMetadata : IDatabaseProviderMetadata
DataSource = $"{ConnectionStrings.DataDirectoryPlaceholder}/{databaseModel.DatabaseName}.sqlite.db",
ForeignKeys = true,
Pooling = true,
- Cache = SqliteCacheMode.Shared,
+ Cache = SqliteCacheMode.Shared
};
return builder.ConnectionString;
diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDistributedLockingMechanism.cs b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDistributedLockingMechanism.cs
index 4a47d41846..a4a31416fa 100644
--- a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDistributedLockingMechanism.cs
+++ b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDistributedLockingMechanism.cs
@@ -4,7 +4,6 @@ using Microsoft.Data.SqlClient;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
-using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DistributedLocking;
using Umbraco.Cms.Core.DistributedLocking.Exceptions;
@@ -17,10 +16,10 @@ namespace Umbraco.Cms.Persistence.Sqlite.Services;
public class SqliteDistributedLockingMechanism : IDistributedLockingMechanism
{
- private readonly ILogger _logger;
- private readonly Lazy _scopeAccessor;
private readonly IOptionsMonitor _connectionStrings;
private readonly IOptionsMonitor _globalSettings;
+ private readonly ILogger _logger;
+ private readonly Lazy _scopeAccessor;
public SqliteDistributedLockingMechanism(
ILogger logger,
@@ -36,7 +35,7 @@ public class SqliteDistributedLockingMechanism : IDistributedLockingMechanism
///
public bool Enabled => _connectionStrings.CurrentValue.IsConnectionStringConfigured() &&
- _connectionStrings.CurrentValue.ProviderName == Constants.ProviderName;
+ string.Equals(_connectionStrings.CurrentValue.ProviderName, Constants.ProviderName, StringComparison.InvariantCultureIgnoreCase);
// With journal_mode=wal we can always read a snapshot.
public IDistributedLock ReadLock(int lockId, TimeSpan? obtainLockTimeout = null)
@@ -101,11 +100,9 @@ public class SqliteDistributedLockingMechanism : IDistributedLockingMechanism
public DistributedLockType LockType { get; }
- public void Dispose()
- {
+ public void Dispose() =>
// Mostly no op, cleaned up by completing transaction in scope.
_parent._logger.LogDebug("Dropped {lockType} for id {id}", LockType, LockId);
- }
public override string ToString()
=> $"SqliteDistributedLock({LockId})";
@@ -123,7 +120,8 @@ public class SqliteDistributedLockingMechanism : IDistributedLockingMechanism
if (!db.InTransaction)
{
- throw new InvalidOperationException("SqliteDistributedLockingMechanism requires a transaction to function.");
+ throw new InvalidOperationException(
+ "SqliteDistributedLockingMechanism requires a transaction to function.");
}
}
@@ -140,7 +138,8 @@ public class SqliteDistributedLockingMechanism : IDistributedLockingMechanism
if (!db.InTransaction)
{
- throw new InvalidOperationException("SqliteDistributedLockingMechanism requires a transaction to function.");
+ throw new InvalidOperationException(
+ "SqliteDistributedLockingMechanism requires a transaction to function.");
}
var query = @$"UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id = {LockId}";
diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteExceptionExtensions.cs b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteExceptionExtensions.cs
index 4076718266..acc8afb508 100644
--- a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteExceptionExtensions.cs
+++ b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteExceptionExtensions.cs
@@ -1,4 +1,5 @@
using Microsoft.Data.Sqlite;
+using SQLitePCL;
namespace Umbraco.Cms.Persistence.Sqlite.Services;
@@ -6,7 +7,7 @@ public static class SqliteExceptionExtensions
{
public static bool IsBusyOrLocked(this SqliteException ex) =>
ex.SqliteErrorCode
- is SQLitePCL.raw.SQLITE_BUSY
- or SQLitePCL.raw.SQLITE_LOCKED
- or SQLitePCL.raw.SQLITE_LOCKED_SHAREDCACHE;
+ is raw.SQLITE_BUSY
+ or raw.SQLITE_LOCKED
+ or raw.SQLITE_LOCKED_SHAREDCACHE;
}
diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqlitePreferDeferredTransactionsConnection.cs b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqlitePreferDeferredTransactionsConnection.cs
index 4e126056d6..7c5a9f56f8 100644
--- a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqlitePreferDeferredTransactionsConnection.cs
+++ b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqlitePreferDeferredTransactionsConnection.cs
@@ -9,22 +9,7 @@ public class SqlitePreferDeferredTransactionsConnection : DbConnection
{
private readonly SqliteConnection _inner;
- public SqlitePreferDeferredTransactionsConnection(SqliteConnection inner)
- {
- _inner = inner;
- }
-
- protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
- => _inner.BeginTransaction(isolationLevel, deferred: true); // <-- The important bit
-
- public override void ChangeDatabase(string databaseName)
- => _inner.ChangeDatabase(databaseName);
-
- public override void Close()
- => _inner.Close();
-
- public override void Open()
- => _inner.Open();
+ public SqlitePreferDeferredTransactionsConnection(SqliteConnection inner) => _inner = inner;
public override string Database
=> _inner.Database;
@@ -38,9 +23,6 @@ public class SqlitePreferDeferredTransactionsConnection : DbConnection
public override string ServerVersion
=> _inner.ServerVersion;
- protected override DbCommand CreateDbCommand()
- => new CommandWrapper(_inner.CreateCommand());
-
[AllowNull]
public override string ConnectionString
{
@@ -48,26 +30,26 @@ public class SqlitePreferDeferredTransactionsConnection : DbConnection
set => _inner.ConnectionString = value;
}
+ protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
+ => _inner.BeginTransaction(isolationLevel, true); // <-- The important bit
+
+ public override void ChangeDatabase(string databaseName)
+ => _inner.ChangeDatabase(databaseName);
+
+ public override void Close()
+ => _inner.Close();
+
+ public override void Open()
+ => _inner.Open();
+
+ protected override DbCommand CreateDbCommand()
+ => new CommandWrapper(_inner.CreateCommand());
+
private class CommandWrapper : DbCommand
{
private readonly DbCommand _inner;
- public CommandWrapper(DbCommand inner)
- {
- _inner = inner;
- }
-
- public override void Cancel()
- => _inner.Cancel();
-
- public override int ExecuteNonQuery()
- => _inner.ExecuteNonQuery();
-
- public override object? ExecuteScalar()
- => _inner.ExecuteScalar();
-
- public override void Prepare()
- => _inner.Prepare();
+ public CommandWrapper(DbCommand inner) => _inner = inner;
[AllowNull]
public override string CommandText
@@ -97,10 +79,7 @@ public class SqlitePreferDeferredTransactionsConnection : DbConnection
protected override DbConnection? DbConnection
{
get => _inner.Connection;
- set
- {
- _inner.Connection = (value as SqlitePreferDeferredTransactionsConnection)?._inner;
- }
+ set => _inner.Connection = (value as SqlitePreferDeferredTransactionsConnection)?._inner;
}
protected override DbParameterCollection DbParameterCollection
@@ -118,6 +97,18 @@ public class SqlitePreferDeferredTransactionsConnection : DbConnection
set => _inner.DesignTimeVisible = value;
}
+ public override void Cancel()
+ => _inner.Cancel();
+
+ public override int ExecuteNonQuery()
+ => _inner.ExecuteNonQuery();
+
+ public override object? ExecuteScalar()
+ => _inner.ExecuteScalar();
+
+ public override void Prepare()
+ => _inner.Prepare();
+
protected override DbParameter CreateDbParameter()
=> _inner.CreateParameter();
diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSpecificMapperFactory.cs b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSpecificMapperFactory.cs
index cf1d707d69..66f542712a 100644
--- a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSpecificMapperFactory.cs
+++ b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSpecificMapperFactory.cs
@@ -4,7 +4,7 @@ using Umbraco.Cms.Persistence.Sqlite.Mappers;
namespace Umbraco.Cms.Persistence.Sqlite.Services;
///
-/// Implements for SQLite.
+/// Implements for SQLite.
///
public class SqliteSpecificMapperFactory : IProviderSpecificMapperFactory
{
@@ -12,5 +12,5 @@ public class SqliteSpecificMapperFactory : IProviderSpecificMapperFactory
public string ProviderName => Constants.ProviderName;
///
- public NPocoMapperCollection Mappers => new NPocoMapperCollection(() => new[] { new SqlitePocoGuidMapper() });
+ public NPocoMapperCollection Mappers => new(() => new[] { new SqlitePocoGuidMapper() });
}
diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs
index be6f013f26..fe8bf7b6a1 100644
--- a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs
+++ b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs
@@ -38,6 +38,20 @@ public class SqliteSyntaxProvider : SqlSyntaxProviderBase
[typeof(Guid)] = new SqliteGuidScalarMapper(),
[typeof(Guid?)] = new SqliteNullableGuidScalarMapper(),
};
+
+ IntColumnDefinition = "INTEGER";
+ LongColumnDefinition = "INTEGER";
+ BoolColumnDefinition = "INTEGER";
+
+ GuidColumnDefinition = "TEXT";
+ DateTimeColumnDefinition = "TEXT";
+ DateTimeOffsetColumnDefinition = "TEXT";
+ TimeColumnDefinition = "TEXT";
+ DecimalColumnDefinition = "TEXT"; // REAL would be lossy. - https://docs.microsoft.com/en-us/dotnet/standard/data/sqlite/types
+
+ RealColumnDefinition = "REAL";
+
+ BlobColumnDefinition = "BLOB";
}
///
@@ -54,14 +68,12 @@ public class SqliteSyntaxProvider : SqlSyntaxProviderBase
///