diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..f764ecd --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,275 @@ +.highlight table td { padding: 5px; } + +.highlight table pre { margin: 0; } + +.highlight .cm { color: #777772; font-style: italic; } + +.highlight .cp { color: #797676; font-weight: bold; } + +.highlight .c1 { color: #777772; font-style: italic; } + +.highlight .cs { color: #797676; font-weight: bold; font-style: italic; } + +.highlight .c, .highlight .cd { color: #777772; font-style: italic; } + +.highlight .err { color: #a61717; background-color: #e3d2d2; } + +.highlight .gd { color: #000000; background-color: #ffdddd; } + +.highlight .ge { color: #000000; font-style: italic; } + +.highlight .gr { color: #aa0000; } + +.highlight .gh { color: #797676; } + +.highlight .gi { color: #000000; background-color: #ddffdd; } + +.highlight .go { color: #888888; } + +.highlight .gp { color: #555555; } + +.highlight .gs { font-weight: bold; } + +.highlight .gu { color: #aaaaaa; } + +.highlight .gt { color: #aa0000; } + +.highlight .kc { color: #000000; font-weight: bold; } + +.highlight .kd { color: #000000; font-weight: bold; } + +.highlight .kn { color: #000000; font-weight: bold; } + +.highlight .kp { color: #000000; font-weight: bold; } + +.highlight .kr { color: #000000; font-weight: bold; } + +.highlight .kt { color: #445588; font-weight: bold; } + +.highlight .k, .highlight .kv { color: #000000; font-weight: bold; } + +.highlight .mf { color: #009999; } + +.highlight .mh { color: #009999; } + +.highlight .il { color: #009999; } + +.highlight .mi { color: #009999; } + +.highlight .mo { color: #009999; } + +.highlight .m, .highlight .mb, .highlight .mx { color: #009999; } + +.highlight .sb { color: #d14; } + +.highlight .sc { color: #d14; } + +.highlight .sd { color: #d14; } + +.highlight .s2 { color: #d14; } + +.highlight .se { color: #d14; } + +.highlight .sh { color: #d14; } + +.highlight .si { color: #d14; } + +.highlight .sx { color: #d14; } + +.highlight .sr { color: #009926; } + +.highlight .s1 { color: #d14; } + +.highlight .ss { color: #990073; } + +.highlight .s { color: #d14; } + +.highlight .na { color: #008080; } + +.highlight .bp { color: #797676; } + +.highlight .nb { color: #0086B3; } + +.highlight .nc { color: #445588; font-weight: bold; } + +.highlight .no { color: #008080; } + +.highlight .nd { color: #3c5d5d; font-weight: bold; } + +.highlight .ni { color: #800080; } + +.highlight .ne { color: #990000; font-weight: bold; } + +.highlight .nf { color: #990000; font-weight: bold; } + +.highlight .nl { color: #990000; font-weight: bold; } + +.highlight .nn { color: #555555; } + +.highlight .nt { color: #000080; } + +.highlight .vc { color: #008080; } + +.highlight .vg { color: #008080; } + +.highlight .vi { color: #008080; } + +.highlight .nv { color: #008080; } + +.highlight .ow { color: #000000; font-weight: bold; } + +.highlight .o { color: #000000; font-weight: bold; } + +.highlight .w { color: #bbbbbb; } + +.highlight { background-color: #f8f8f8; } + +/******************************************************************************* +MeyerWeb Reset +*******************************************************************************/ +html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font: inherit; vertical-align: baseline; } + +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } + +ol, ul { list-style: none; } + +table { border-collapse: collapse; border-spacing: 0; } + +/******************************************************************************* +Theme Styles +*******************************************************************************/ +body { box-sizing: border-box; color: #373737; background: #212121; font-size: 16px; font-family: 'Myriad Pro', Calibri, Helvetica, Arial, sans-serif; line-height: 1.5; -webkit-font-smoothing: antialiased; } + +h1, h2, h3, h4, h5, h6 { margin: 10px 0; font-weight: 700; color: #222222; font-family: 'Lucida Grande', 'Calibri', Helvetica, Arial, sans-serif; letter-spacing: -1px; } + +h1 { font-size: 36px; font-weight: 700; } + +h2 { padding-bottom: 10px; font-size: 32px; background: url("../images/bg_hr.png") repeat-x bottom; } + +h3 { font-size: 24px; } + +h4 { font-size: 21px; } + +h5 { font-size: 18px; } + +h6 { font-size: 16px; } + +p { margin: 10px 0 15px 0; } + +footer p { color: #f2f2f2; } + +a { text-decoration: none; color: #0F79D0; text-shadow: none; transition: color 0.5s ease; transition: text-shadow 0.5s ease; -webkit-transition: color 0.5s ease; -webkit-transition: text-shadow 0.5s ease; -moz-transition: color 0.5s ease; -moz-transition: text-shadow 0.5s ease; -o-transition: color 0.5s ease; -o-transition: text-shadow 0.5s ease; -ms-transition: color 0.5s ease; -ms-transition: text-shadow 0.5s ease; } + +a:hover, a:focus { text-decoration: underline; } + +footer a { color: #F2F2F2; text-decoration: underline; } + +em, cite { font-style: italic; } + +strong { font-weight: bold; } + +img { position: relative; margin: 0 auto; max-width: 739px; padding: 5px; margin: 10px 0 10px 0; border: 1px solid #ebebeb; box-shadow: 0 0 5px #ebebeb; -webkit-box-shadow: 0 0 5px #ebebeb; -moz-box-shadow: 0 0 5px #ebebeb; -o-box-shadow: 0 0 5px #ebebeb; -ms-box-shadow: 0 0 5px #ebebeb; } + +p img { display: inline; margin: 0; padding: 0; vertical-align: middle; text-align: center; border: none; } + +pre, code { color: #222; background-color: #fff; font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; font-size: 0.875em; border-radius: 2px; -moz-border-radius: 2px; -webkit-border-radius: 2px; } + +pre { padding: 10px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); overflow: auto; } + +code { padding: 3px; margin: 0 3px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); } + +pre code { display: block; box-shadow: none; } + +blockquote { color: #666; margin-bottom: 20px; padding: 0 0 0 20px; border-left: 3px solid #bbb; } + +ul, ol, dl { margin-bottom: 15px; } + +ul { list-style-position: inside; list-style: disc; padding-left: 20px; } + +ol { list-style-position: inside; list-style: decimal; padding-left: 20px; } + +dl dt { font-weight: bold; } + +dl dd { padding-left: 20px; font-style: italic; } + +dl p { padding-left: 20px; font-style: italic; } + +hr { height: 1px; margin-bottom: 5px; border: none; background: url("../images/bg_hr.png") repeat-x center; } + +table { border: 1px solid #373737; margin-bottom: 20px; text-align: left; } + +th { font-family: 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, sans-serif; padding: 10px; background: #373737; color: #fff; } + +td { padding: 10px; border: 1px solid #373737; } + +form { background: #f2f2f2; padding: 20px; } + +kbd { background-color: #fafbfc; border: 1px solid #c6cbd1; border-bottom-color: #959da5; border-radius: 3px; box-shadow: inset 0 -1px 0 #959da5; color: #444d56; display: inline-block; font-size: 11px; line-height: 11px; padding: 3px 5px; vertical-align: middle; } + +/******************************************************************************* +Full-Width Styles +*******************************************************************************/ +.outer { width: 100%; } + +.inner { position: relative; max-width: 640px; padding: 20px 10px; margin: 0 auto; } + +#forkme_banner { display: block; position: absolute; top: 0; right: 10px; z-index: 10; padding: 10px 50px 10px 10px; color: #fff; background: url("../images/blacktocat.png") #0090ff no-repeat 95% 50%; font-weight: 700; box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); border-bottom-left-radius: 2px; border-bottom-right-radius: 2px; } + +#header_wrap { background: #212121; background: -moz-linear-gradient(top, #373737, #212121); background: -webkit-linear-gradient(top, #373737, #212121); background: -ms-linear-gradient(top, #373737, #212121); background: -o-linear-gradient(top, #373737, #212121); background: linear-gradient(to top, #373737, #212121); } + +#header_wrap .inner { padding: 50px 10px 30px 10px; } + +#project_title { margin: 0; color: #fff; font-size: 42px; font-weight: 700; text-shadow: #111 0px 0px 10px; } + +#project_tagline { color: #fff; font-size: 24px; font-weight: 300; background: none; text-shadow: #111 0px 0px 10px; } + +#downloads { position: absolute; width: 210px; z-index: 10; bottom: -40px; right: 0; height: 70px; background: url("../images/icon_download.png") no-repeat 0% 90%; } + +.zip_download_link { display: block; float: right; width: 90px; height: 70px; text-indent: -5000px; overflow: hidden; background: url(../images/sprite_download.png) no-repeat bottom left; } + +.tar_download_link { display: block; float: right; width: 90px; height: 70px; text-indent: -5000px; overflow: hidden; background: url(../images/sprite_download.png) no-repeat bottom right; margin-left: 10px; } + +.zip_download_link:hover { background: url(../images/sprite_download.png) no-repeat top left; } + +.tar_download_link:hover { background: url(../images/sprite_download.png) no-repeat top right; } + +#main_content_wrap { background: #f2f2f2; border-top: 1px solid #111; border-bottom: 1px solid #111; } + +#main_content { padding-top: 40px; } + +#footer_wrap { background: #212121; } + +/******************************************************************************* +Small Device Styles +*******************************************************************************/ +@media screen and (max-width: 992px) { img { max-width: 100%; } } +@media screen and (max-width: 480px) { body { font-size: 14px; } + #downloads { display: none; } + .inner { min-width: 320px; max-width: 480px; } + #project_title { font-size: 32px; } + h1 { font-size: 28px; } + h2 { font-size: 24px; } + h3 { font-size: 21px; } + h4 { font-size: 18px; } + h5 { font-size: 14px; } + h6 { font-size: 12px; } + code, pre { font-size: 11px; } } +@media screen and (max-width: 320px) { body { font-size: 14px; } + #downloads { display: none; } + .inner { min-width: 240px; max-width: 320px; } + #project_title { font-size: 28px; } + h1 { font-size: 24px; } + h2 { font-size: 21px; } + h3 { font-size: 18px; } + h4 { font-size: 16px; } + h5 { font-size: 14px; } + h6 { font-size: 12px; } + code, pre { min-width: 240px; max-width: 320px; font-size: 11px; } } +.inner { max-width: 960px; } + +pre, code { background-color: unset; font-size: unset; } + +code { font-size: 0.80em; } + +h1 > img { border: unset; box-shadow: unset; vertical-align: middle; -moz-box-shadow: unset; -o-box-shadow: unset; -ms-box-shadow: unset; } diff --git a/assets/images/bg_hr.png b/assets/images/bg_hr.png new file mode 100644 index 0000000..514aee5 Binary files /dev/null and b/assets/images/bg_hr.png differ diff --git a/assets/images/blacktocat.png b/assets/images/blacktocat.png new file mode 100644 index 0000000..e160053 Binary files /dev/null and b/assets/images/blacktocat.png differ diff --git a/assets/images/icon_download.png b/assets/images/icon_download.png new file mode 100644 index 0000000..5a793f1 Binary files /dev/null and b/assets/images/icon_download.png differ diff --git a/assets/images/sprite_download.png b/assets/images/sprite_download.png new file mode 100644 index 0000000..f9f8de2 Binary files /dev/null and b/assets/images/sprite_download.png differ diff --git a/assets/img/document.png b/assets/img/document.png new file mode 100644 index 0000000..fdd59fe Binary files /dev/null and b/assets/img/document.png differ diff --git a/assets/img/entity.png b/assets/img/entity.png new file mode 100644 index 0000000..27c9898 Binary files /dev/null and b/assets/img/entity.png differ diff --git a/assets/img/icon-32.png b/assets/img/icon-32.png new file mode 100644 index 0000000..45e9c03 Binary files /dev/null and b/assets/img/icon-32.png differ diff --git a/assets/img/icon.svg b/assets/img/icon.svg new file mode 100644 index 0000000..50d17ac --- /dev/null +++ b/assets/img/icon.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/img/tablestorage.png b/assets/img/tablestorage.png new file mode 100644 index 0000000..8f86ef7 Binary files /dev/null and b/assets/img/tablestorage.png differ diff --git a/changelog.html b/changelog.html new file mode 100644 index 0000000..adc18a7 --- /dev/null +++ b/changelog.html @@ -0,0 +1,540 @@ + + + + + + + + + + +Changelog | TableStorage + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + View on GitHub + + +

TableStorage

+

Repository pattern with POCO object support for storing to Azure / Cosmos DB Table Storage

+ + +
+
+ + +
+
+

Changelog

+ +

v5.1.2 (2024-01-25)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

v5.1.1 (2023-10-04)

+ +

Full Changelog

+ +

:bug: Fixed bugs:

+ + + +

v5.1.0 (2023-08-11)

+ +

Full Changelog

+ +

:twisted_rightwards_arrows: Merged:

+ + + +

v5.0.2 (2023-08-08)

+ +

Full Changelog

+ +

v5.0.1 (2023-07-25)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

v5.0.0 (2023-07-25)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

v4.3.0 (2023-06-27)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

v4.2.1 (2023-04-17)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

v4.2.0 (2023-03-28)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

:twisted_rightwards_arrows: Merged:

+ + + +

v4.1.3 (2023-01-20)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

:bug: Fixed bugs:

+ + + +

v4.1.2 (2023-01-16)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

:twisted_rightwards_arrows: Merged:

+ + + +

v4.0.0 (2022-08-26)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

v4.0.0-rc.1 (2022-08-15)

+ +

Full Changelog

+ +

v4.0.0-rc (2022-08-15)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

:twisted_rightwards_arrows: Merged:

+ + + +

v4.0.0-beta (2022-05-17)

+ +

Full Changelog

+ +

:bug: Fixed bugs:

+ + + +

:twisted_rightwards_arrows: Merged:

+ + + +

v4.0.0-alpha (2022-05-04)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

:hammer: Other:

+ + + +

:twisted_rightwards_arrows: Merged:

+ + + +

v3.2.0 (2021-12-13)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

v3.1.1 (2021-08-29)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

:twisted_rightwards_arrows: Merged:

+ + + +

v3.1.0 (2021-08-13)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

:twisted_rightwards_arrows: Merged:

+ + + +

v3.0.3 (2021-07-28)

+ +

Full Changelog

+ +

:bug: Fixed bugs:

+ + + +

v3.0.2 (2021-07-01)

+ +

Full Changelog

+ +

:bug: Fixed bugs:

+ + + +

v3.0.1 (2021-07-01)

+ +

Full Changelog

+ +

:bug: Fixed bugs:

+ + + +

v3.0.0 (2021-07-01)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

:hammer: Other:

+ + + +

v2.0.2 (2021-06-23)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

:twisted_rightwards_arrows: Merged:

+ + + +

v2.0.1 (2021-06-17)

+ +

Full Changelog

+ +

v2.0.1-rc (2021-06-17)

+ +

Full Changelog

+ +

v2.0.1-beta (2021-06-17)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

v2.0.0 (2021-06-16)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

:twisted_rightwards_arrows: Merged:

+ + + +

v1.3.0 (2021-05-31)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

:bug: Fixed bugs:

+ + + +

v1.2.1 (2021-05-29)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

v1.2.0 (2021-05-26)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

v1.1.1 (2021-05-26)

+ +

Full Changelog

+ +

:bug: Fixed bugs:

+ + + +

v1.1.0 (2021-05-26)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

v1.0.4 (2021-05-16)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

v1.0.3 (2021-05-15)

+ +

Full Changelog

+ +

v1.0.2 (2021-05-10)

+ +

Full Changelog

+ +

v1.0.1 (2021-05-07)

+ +

Full Changelog

+ +

v1.0.0 (2021-05-07)

+ +

Full Changelog

+ +

v1.0.0-rc (2021-05-07)

+ +

Full Changelog

+ +

:twisted_rightwards_arrows: Merged:

+ + + +

v1.0.0-beta (2021-05-05)

+ +

Full Changelog

+ +

v1.0.0-alpha (2021-05-05)

+ +

Full Changelog

+ +

* This Changelog was automatically generated by github_changelog_generator

+ +
+
+ + + + + diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..e780123 --- /dev/null +++ b/changelog.md @@ -0,0 +1,383 @@ +# Changelog + +## [v5.1.2](https://github.com/devlooped/TableStorage/tree/v5.1.2) (2024-01-25) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v5.1.1...v5.1.2) + +:sparkles: Implemented enhancements: + +- Update to latest protobuf with built-in support for DateOnly and TimeOnly [\#273](https://github.com/devlooped/TableStorage/pull/273) (@kzu) + +## [v5.1.1](https://github.com/devlooped/TableStorage/tree/v5.1.1) (2023-10-04) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v5.1.0...v5.1.1) + +:bug: Fixed bugs: + +- KeyProperties are not being persisted properly [\#252](https://github.com/devlooped/TableStorage/issues/252) +- Properly persist computed colums for same type [\#253](https://github.com/devlooped/TableStorage/pull/253) (@kzu) + +## [v5.1.0](https://github.com/devlooped/TableStorage/tree/v5.1.0) (2023-08-11) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v5.0.2...v5.1.0) + +:twisted_rightwards_arrows: Merged: + +- Remove current implementation of SponsorLink for now [\#244](https://github.com/devlooped/TableStorage/pull/244) (@kzu) + +## [v5.0.2](https://github.com/devlooped/TableStorage/tree/v5.0.2) (2023-08-08) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v5.0.1...v5.0.2) + +## [v5.0.1](https://github.com/devlooped/TableStorage/tree/v5.0.1) (2023-07-25) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v5.0.0...v5.0.1) + +:sparkles: Implemented enhancements: + +- Use OData type annotations to disambiguate property types when persisting [\#235](https://github.com/devlooped/TableStorage/issues/235) +- Empty query results does not throw [\#237](https://github.com/devlooped/TableStorage/pull/237) (@kzu) + +## [v5.0.0](https://github.com/devlooped/TableStorage/tree/v5.0.0) (2023-07-25) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v4.3.0...v5.0.0) + +:sparkles: Implemented enhancements: + +- When hydrating queries for TableEntity, use native .NET types for properties [\#233](https://github.com/devlooped/TableStorage/issues/233) +- Add support for querying over TableEntityRepository [\#231](https://github.com/devlooped/TableStorage/issues/231) +- Improve persistence by annotating supported types with OData Edm [\#236](https://github.com/devlooped/TableStorage/pull/236) (@kzu) +- When hydrating queries for TableEntity, use native .NET types for properties [\#234](https://github.com/devlooped/TableStorage/pull/234) (@kzu) +- Add support for querying over TableEntityRepository [\#232](https://github.com/devlooped/TableStorage/pull/232) (@kzu) + +## [v4.3.0](https://github.com/devlooped/TableStorage/tree/v4.3.0) (2023-06-27) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v4.2.1...v4.3.0) + +:sparkles: Implemented enhancements: + +- Allow entity lookup from entity value [\#215](https://github.com/devlooped/TableStorage/issues/215) +- Allow entity lookup from entity value [\#216](https://github.com/devlooped/TableStorage/pull/216) (@kzu) + +## [v4.2.1](https://github.com/devlooped/TableStorage/tree/v4.2.1) (2023-04-17) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v4.2.0...v4.2.1) + +:sparkles: Implemented enhancements: + +- Allow persisting key properties as columns [\#198](https://github.com/devlooped/TableStorage/pull/198) (@kzu) + +## [v4.2.0](https://github.com/devlooped/TableStorage/tree/v4.2.0) (2023-03-28) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v4.1.3...v4.2.0) + +:sparkles: Implemented enhancements: + +- Add SponsorLink to ensure ongoing development and support [\#193](https://github.com/devlooped/TableStorage/pull/193) (@kzu) + +:twisted_rightwards_arrows: Merged: + +- Switch to Polysharp for the NS2 polyfills [\#194](https://github.com/devlooped/TableStorage/pull/194) (@kzu) + +## [v4.1.3](https://github.com/devlooped/TableStorage/tree/v4.1.3) (2023-01-20) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v4.1.2...v4.1.3) + +:sparkles: Implemented enhancements: + +- Add support for TableConnection overload for DocumentRepository [\#173](https://github.com/devlooped/TableStorage/pull/173) (@kzu) +- As documented \[PartitionKey\], honor it at class level [\#172](https://github.com/devlooped/TableStorage/pull/172) (@kzu) +- Allow persistence of entity properties as columns in document [\#171](https://github.com/devlooped/TableStorage/pull/171) (@kzu) +- Allow retrieving the table client from the connection [\#165](https://github.com/devlooped/TableStorage/pull/165) (@kzu) + +:bug: Fixed bugs: + +- Update mode on table partition should set table repository mode [\#174](https://github.com/devlooped/TableStorage/pull/174) (@kzu) + +## [v4.1.2](https://github.com/devlooped/TableStorage/tree/v4.1.2) (2023-01-16) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v4.0.0...v4.1.2) + +:sparkles: Implemented enhancements: + +- Add TableConnection overloads for TableRepository factory methods [\#161](https://github.com/devlooped/TableStorage/issues/161) +- Allow reusing/caching the combination of CloudStorageAccount and TableClient [\#155](https://github.com/devlooped/TableStorage/issues/155) +- Provide comprehensive readmes for all packages [\#164](https://github.com/devlooped/TableStorage/pull/164) (@kzu) +- Add TableConnection overloads for TableRepository factory methods [\#162](https://github.com/devlooped/TableStorage/pull/162) (@kzu) +- Allow creating partitions from the same table connection [\#156](https://github.com/devlooped/TableStorage/pull/156) (@kzu) + +:twisted_rightwards_arrows: Merged: + +- ⛙ ⬆️ Bump dependencies [\#158](https://github.com/devlooped/TableStorage/pull/158) (@github-actions[bot]) + +## [v4.0.0](https://github.com/devlooped/TableStorage/tree/v4.0.0) (2022-08-26) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v4.0.0-rc.1...v4.0.0) + +:sparkles: Implemented enhancements: + +- Improve dynamic entity support by exposing TableEntity directly [\#127](https://github.com/devlooped/TableStorage/issues/127) + +## [v4.0.0-rc.1](https://github.com/devlooped/TableStorage/tree/v4.0.0-rc.1) (2022-08-15) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v4.0.0-rc...v4.0.0-rc.1) + +## [v4.0.0-rc](https://github.com/devlooped/TableStorage/tree/v4.0.0-rc) (2022-08-15) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v4.0.0-beta...v4.0.0-rc) + +:sparkles: Implemented enhancements: + +- Improve dynamic entity support by exposing TableEntity directly [\#128](https://github.com/devlooped/TableStorage/pull/128) (@kzu) + +:twisted_rightwards_arrows: Merged: + +- +M▼ includes [\#123](https://github.com/devlooped/TableStorage/pull/123) (@github-actions[bot]) +- +M▼ includes [\#117](https://github.com/devlooped/TableStorage/pull/117) (@github-actions[bot]) + +## [v4.0.0-beta](https://github.com/devlooped/TableStorage/tree/v4.0.0-beta) (2022-05-17) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v4.0.0-alpha...v4.0.0-beta) + +:bug: Fixed bugs: + +- If partition or row key expressions have complex lambda, property name should be null [\#100](https://github.com/devlooped/TableStorage/issues/100) + +:twisted_rightwards_arrows: Merged: + +- Reduce scope of key property lookup to direct property lambda [\#101](https://github.com/devlooped/TableStorage/pull/101) (@kzu) + +## [v4.0.0-alpha](https://github.com/devlooped/TableStorage/tree/v4.0.0-alpha) (2022-05-04) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v3.2.0...v4.0.0-alpha) + +:sparkles: Implemented enhancements: + +- Upgrade to latest Azure v12 SDK [\#89](https://github.com/devlooped/TableStorage/pull/89) (@kzu) + +:hammer: Other: + +- Upgrade to latest Azure v12 SDK [\#88](https://github.com/devlooped/TableStorage/issues/88) +- Add support for DateOnly [\#78](https://github.com/devlooped/TableStorage/issues/78) + +:twisted_rightwards_arrows: Merged: + +- Fix test filter for theory tests [\#91](https://github.com/devlooped/TableStorage/pull/91) (@kzu) +- Address warnings [\#90](https://github.com/devlooped/TableStorage/pull/90) (@kzu) + +## [v3.2.0](https://github.com/devlooped/TableStorage/tree/v3.2.0) (2021-12-13) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v3.1.1...v3.2.0) + +:sparkles: Implemented enhancements: + +- Add support for DateOnly [\#79](https://github.com/devlooped/TableStorage/pull/79) (@kzu) + +## [v3.1.1](https://github.com/devlooped/TableStorage/tree/v3.1.1) (2021-08-29) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v3.1.0...v3.1.1) + +:sparkles: Implemented enhancements: + +- Add API docs for all packages [\#64](https://github.com/devlooped/TableStorage/issues/64) + +:twisted_rightwards_arrows: Merged: + +- Add missing API docs and fix all docs warnings [\#65](https://github.com/devlooped/TableStorage/pull/65) (@kzu) + +## [v3.1.0](https://github.com/devlooped/TableStorage/tree/v3.1.0) (2021-08-13) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v3.0.3...v3.1.0) + +:sparkles: Implemented enhancements: + +- Support Timestamp property in POCO entities [\#60](https://github.com/devlooped/TableStorage/issues/60) + +:twisted_rightwards_arrows: Merged: + +- Support Timestamp property in POCO entities [\#61](https://github.com/devlooped/TableStorage/pull/61) (@kzu) + +## [v3.0.3](https://github.com/devlooped/TableStorage/tree/v3.0.3) (2021-07-28) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v3.0.2...v3.0.3) + +:bug: Fixed bugs: + +- Azure Functions fails with SocketException sometimes when accessing table [\#58](https://github.com/devlooped/TableStorage/issues/58) + +## [v3.0.2](https://github.com/devlooped/TableStorage/tree/v3.0.2) (2021-07-01) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v3.0.1...v3.0.2) + +:bug: Fixed bugs: + +- ContinuationToken not properly used for enumerating all entities [\#53](https://github.com/devlooped/TableStorage/issues/53) + +## [v3.0.1](https://github.com/devlooped/TableStorage/tree/v3.0.1) (2021-07-01) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v3.0.0...v3.0.1) + +:bug: Fixed bugs: + +- Remove Create factory methods that cause ambiguous matches [\#52](https://github.com/devlooped/TableStorage/issues/52) + +## [v3.0.0](https://github.com/devlooped/TableStorage/tree/v3.0.0) (2021-07-01) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v2.0.2...v3.0.0) + +:sparkles: Implemented enhancements: + +- Don't fail if deleting non-existent entity, return false instead [\#50](https://github.com/devlooped/TableStorage/issues/50) +- Switch from TableEntity to ITableEntity in the non-generic repository for flexibility [\#48](https://github.com/devlooped/TableStorage/issues/48) +- For PutAsync, allow selecting Replace vs Merge behavior [\#46](https://github.com/devlooped/TableStorage/issues/46) +- When deleting entities, return boolean status for success/failure [\#51](https://github.com/devlooped/TableStorage/pull/51) (@kzu) +- Allow additional properties in table entity repository [\#49](https://github.com/devlooped/TableStorage/pull/49) (@kzu) +- Support merge strategy when updating entities [\#47](https://github.com/devlooped/TableStorage/pull/47) (@kzu) + +:hammer: Other: + +- Document usage of \[Browsable\(false\)\] to skip persistence [\#45](https://github.com/devlooped/TableStorage/issues/45) + +## [v2.0.2](https://github.com/devlooped/TableStorage/tree/v2.0.2) (2021-06-23) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v2.0.1...v2.0.2) + +:sparkles: Implemented enhancements: + +- Allow annotating record constructor parameters with PartitionKey/RowKey [\#43](https://github.com/devlooped/TableStorage/issues/43) + +:twisted_rightwards_arrows: Merged: + +- Add support for record constructor parameter annotations [\#44](https://github.com/devlooped/TableStorage/pull/44) (@kzu) + +## [v2.0.1](https://github.com/devlooped/TableStorage/tree/v2.0.1) (2021-06-17) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v2.0.1-rc...v2.0.1) + +## [v2.0.1-rc](https://github.com/devlooped/TableStorage/tree/v2.0.1-rc) (2021-06-17) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v2.0.1-beta...v2.0.1-rc) + +## [v2.0.1-beta](https://github.com/devlooped/TableStorage/tree/v2.0.1-beta) (2021-06-17) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v2.0.0...v2.0.1-beta) + +:sparkles: Implemented enhancements: + +- Include readme in package [\#39](https://github.com/devlooped/TableStorage/issues/39) + +## [v2.0.0](https://github.com/devlooped/TableStorage/tree/v2.0.0) (2021-06-16) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v1.3.0...v2.0.0) + +:sparkles: Implemented enhancements: + +- Add support for enhanced filtering for document-based repositories [\#37](https://github.com/devlooped/TableStorage/issues/37) +- Add support for filtering by enum property types [\#35](https://github.com/devlooped/TableStorage/issues/35) +- Add support for querying [\#33](https://github.com/devlooped/TableStorage/issues/33) +- Allow passing serialization options for MessagePack serializer [\#32](https://github.com/devlooped/TableStorage/issues/32) +- Switch default built-in serializer to System.Text.Json [\#30](https://github.com/devlooped/TableStorage/issues/30) +- Make Document/Entity default table names plural [\#28](https://github.com/devlooped/TableStorage/issues/28) +- Don't duplicate PartitionKey/RowKey properties in storage [\#26](https://github.com/devlooped/TableStorage/issues/26) + +:twisted_rightwards_arrows: Merged: + +- Add support for enhanced filtering for document-based repo [\#38](https://github.com/devlooped/TableStorage/pull/38) (@kzu) +- Add support for filtering by enum property types [\#36](https://github.com/devlooped/TableStorage/pull/36) (@kzu) +- Add support for querying with LINQ and expressions [\#34](https://github.com/devlooped/TableStorage/pull/34) (@kzu) +- Switch default built-in serializer to System.Text.Json [\#31](https://github.com/devlooped/TableStorage/pull/31) (@kzu) +- Make Document/Entity default table names plural [\#29](https://github.com/devlooped/TableStorage/pull/29) (@kzu) +- Don't duplicate PartitionKey/RowKey properties in storage [\#27](https://github.com/devlooped/TableStorage/pull/27) (@kzu) + +## [v1.3.0](https://github.com/devlooped/TableStorage/tree/v1.3.0) (2021-05-31) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v1.2.1...v1.3.0) + +:sparkles: Implemented enhancements: + +- Allow persisting entities as documents instead of individual columns [\#24](https://github.com/devlooped/TableStorage/issues/24) +- Avoid creating a separate Task for lazily initialization of CloudTable [\#23](https://github.com/devlooped/TableStorage/issues/23) +- No need to use DynamicTableEntity when deleting [\#21](https://github.com/devlooped/TableStorage/issues/21) +- When doing a PutAsync, use InsertOrMerge instead of InsertOrReplace [\#20](https://github.com/devlooped/TableStorage/issues/20) +- Allow persisting entities as documents [\#25](https://github.com/devlooped/TableStorage/pull/25) (@kzu) + +:bug: Fixed bugs: + +- Inconsistent default partition name in TablePartition.Create [\#22](https://github.com/devlooped/TableStorage/issues/22) + +## [v1.2.1](https://github.com/devlooped/TableStorage/tree/v1.2.1) (2021-05-29) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v1.2.0...v1.2.1) + +:sparkles: Implemented enhancements: + +- Add support for TableEntity via ITableRepository and ITablePartition APIs [\#18](https://github.com/devlooped/TableStorage/issues/18) + +## [v1.2.0](https://github.com/devlooped/TableStorage/tree/v1.2.0) (2021-05-26) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v1.1.1...v1.2.0) + +:sparkles: Implemented enhancements: + +- Add an AttributedTableRepository\ for easy consumption in DI scenarios [\#16](https://github.com/devlooped/TableStorage/issues/16) + +## [v1.1.1](https://github.com/devlooped/TableStorage/tree/v1.1.1) (2021-05-26) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v1.1.0...v1.1.1) + +:bug: Fixed bugs: + +- Fix usage in package description [\#15](https://github.com/devlooped/TableStorage/issues/15) + +## [v1.1.0](https://github.com/devlooped/TableStorage/tree/v1.1.0) (2021-05-26) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v1.0.4...v1.1.0) + +:sparkles: Implemented enhancements: + +- Force factory method usage for default implementations [\#14](https://github.com/devlooped/TableStorage/issues/14) +- Apply factory method defaults to TableRepository/TablePartition constructors [\#13](https://github.com/devlooped/TableStorage/issues/13) + +## [v1.0.4](https://github.com/devlooped/TableStorage/tree/v1.0.4) (2021-05-16) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v1.0.3...v1.0.4) + +:sparkles: Implemented enhancements: + +- Make TableRepository\/TablePartition\ public [\#10](https://github.com/devlooped/TableStorage/issues/10) + +## [v1.0.3](https://github.com/devlooped/TableStorage/tree/v1.0.3) (2021-05-15) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v1.0.2...v1.0.3) + +## [v1.0.2](https://github.com/devlooped/TableStorage/tree/v1.0.2) (2021-05-10) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v1.0.1...v1.0.2) + +## [v1.0.1](https://github.com/devlooped/TableStorage/tree/v1.0.1) (2021-05-07) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v1.0.0...v1.0.1) + +## [v1.0.0](https://github.com/devlooped/TableStorage/tree/v1.0.0) (2021-05-07) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v1.0.0-rc...v1.0.0) + +## [v1.0.0-rc](https://github.com/devlooped/TableStorage/tree/v1.0.0-rc) (2021-05-07) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v1.0.0-beta...v1.0.0-rc) + +:twisted_rightwards_arrows: Merged: + +- Add SourceLink to get repo/pdb linking [\#3](https://github.com/devlooped/TableStorage/pull/3) (@kzu) + +## [v1.0.0-beta](https://github.com/devlooped/TableStorage/tree/v1.0.0-beta) (2021-05-05) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/v1.0.0-alpha...v1.0.0-beta) + +## [v1.0.0-alpha](https://github.com/devlooped/TableStorage/tree/v1.0.0-alpha) (2021-05-05) + +[Full Changelog](https://github.com/devlooped/TableStorage/compare/cf1b7f069ac6d68482b498555c8dbdda8e1ae5b4...v1.0.0-alpha) + + + +\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* diff --git a/index.html b/index.html new file mode 100644 index 0000000..07f3c71 --- /dev/null +++ b/index.html @@ -0,0 +1,444 @@ + + + + + + + + + + +TableStorage | Repository pattern with POCO object support for storing to Azure / Cosmos DB Table Storage + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + View on GitHub + + +

TableStorage

+

Repository pattern with POCO object support for storing to Azure / Cosmos DB Table Storage

+ + +
+
+ + +
+
+

Icon TableStorage

+ +

Version +Downloads +License +Build

+ + +

Repository pattern with POCO object support for storing to Azure/CosmosDB Table Storage

+ +

Screenshot of basic usage

+ +
+

NOTE: This library is a thin wrapper around the latest Azure SDK v12+ for Table Storage, +and uses CloudStorageAccount which +is a 100% backwards compatible implementation of the Azure SDK v11 CloudStorageAccount class.

+
+ +

Usage

+ +

Given an entity like:

+ +
public record Product(string Category, string Id) 
+{
+  public required string? Title { get; init; }
+  public double Price { get; init; }
+  public DateOnly CreatedAt { get; init; }
+}
+
+ +
+

NOTE: entity can have custom constructor, key properties can be read-only +(Category and Id in this case for example), and it doesn’t need to inherit +from anything, implement any interfaces or use +any custom attributes (unless you want to). As shown above, it can even be +a simple record type.

+
+ +

The entity can be stored and retrieved with:

+ +
var storageAccount = CloudStorageAccount.DevelopmentStorageAccount; // or production one
+// We lay out the parameter names for clarity only.
+var repo = TableRepository.Create<Product>(storageAccount, 
+    tableName: "Products",
+    partitionKey: p => p.Category, 
+    rowKey: p => p.Id);
+
+var product = new Product("book", "1234") 
+{
+  Title = "Table Storage is Cool",
+  Price = 25.5,
+};
+
+// Insert or Update behavior (aka "upsert")
+await repo.PutAsync(product);
+
+// Enumerate all products in category "book"
+await foreach (var p in repo.EnumerateAsync("book"))
+   Console.WriteLine(p.Price);
+
+// Query books priced in the 20-50 range, 
+// project just title + price
+await foreach (var info in from prod in repo.CreateQuery()
+                           where prod.Price >= 20 and prod.Price <= 50
+                           select new { prod.Title, prod.Price })
+  Console.WriteLine($"{info.Title}: {info.Price}");
+
+// Get previously saved product.
+Product saved = await repo.GetAsync("book", "1234");
+
+// Delete product
+await repo.DeleteAsync("book", "1234");
+
+// Can also delete passing entity
+await repo.DeleteAsync(saved);
+
+ +

Attributes can also be used to eliminate the need for lambdas altogether when +the entity storage layout is known at compile time:

+ +
[Table("Products")]
+public record Product([PartitionKey] string Category, [RowKey] string Id) ... 
+
+var storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
+// Everything discovered from attributes.
+var repo = TableRepository.Create<Product>(storageAccount);
+
+ +

See the Attributes section below for more details on how to use them.

+ +

If the product were books for example, it might make sense to partition by author. +In that case, you could use a TableRepository<Book> when saving:

+ +
public record Book([RowKey] string ISBN, string Title, string Author, BookFormat Format, int Pages);
+
+var repo = TableRepository.Create<Product>(storageAccount, "Books",
+  partitionKey: x => x.Author);
+
+await repo.PutAsync(book);
+
+ +
+

Note how you can mix and match attributes and explicit lambdas as needed. +The latter takes precedence over the former.

+
+ +

And later on when listing/filtering books by a particular author, you can use +a TablePartition<Product> so all querying is automatically scoped to that author:

+ +
var partition = TablePartition.Create<Book>(storageAccount, "Books", 
+  partitionKey: "Rick Riordan");
+
+// Get Rick Riordan books, only from Disney/Hyperion, with over 1000 pages
+var query = from book in repo.CreateQuery()
+            where 
+                book.ISBN.CompareTo("97814231") >= 0 &&
+                book.ISBN.CompareTo("97814232") < 0 && 
+                book.Pages >= 1000
+            select new { book.ISBN, book.Title };
+
+ +

Using table partitions is quite convenient for handling reference data too, for example. +Enumerating all entries in the partition wouldn’t be something you’d typically do for +your “real” data, but for reference data, it could come in handy.

+ +
+

NOTE: if access to the Timestamp managed by Azure Table Storage for the entity is needed, +just declare a property with that name with either DateTimeOffset, DateTime or string type +to read it.

+
+ +

Stored entities using TableRepository and TablePartition use individual columns for +properties, which makes it easy to browse the data (and query, as shown above!).

+ +
+

NOTE: partition and row keys can also be typed as Guid

+
+ +

Document-based storage is also available via DocumentRepository and DocumentPartition if +you don’t need the individual columns.

+ + +

Document Storage

+ +

The DocumentRepository.Create and DocumentPartition.Create factory methods provide access +to document-based storage, exposing the a similar API as column-based storage.

+ +

Document repositories cause entities to be persisted as a single document column, alongside type and version +information to handle versioning a the app level as needed.

+ +

The API is mostly the same as for column-based repositories (document repositories implement +the same underlying ITableStorage interface):

+ +
public record Product(string Category, string Id) 
+{
+  public string? Title { get; init; }
+  public double Price { get; init; }
+  public DateOnly CreatedAt { get; init; }
+}
+
+var book = new Product("book", "9781473217386")
+{
+    Title = "Neuromancer",
+    Price = 7.32
+};
+
+// Column-based storage
+var repo = TableRepository.Create<Product>(
+    CloudStorageAccount.DevelopmentStorageAccount,
+    tableName: "Products",
+    partitionKey: p => p.Category,
+    rowKey: p => p.Id);
+
+await repo.PutAsync(book);
+
+// Document-based storage
+var docs = DocumentRepository.Create<Product>(
+    CloudStorageAccount.DevelopmentStorageAccount,
+    tableName: "Documents",
+    partitionKey: p => p.Category,
+    rowKey: p => p.Id
+    serializer: [SERIALIZER]);
+
+await docs.PutAsync(book);
+
+ +
+

If not provided, the serializer defaults to the System.Text.Json-based DocumentSerializer.Default.

+
+ +

The resulting differences in storage can be seen in the following screenshots of the +Azure Storage Explorer:

+ +

Screenshot of entity persisted with separate columns for properties

+ +

Screenshot of entity persisted as a document

+ +

The Type column persisted in the documents table is the Type.FullName of the persisted entity, and the +Version is the [Major].[Minor] of its assembly, which could be used for advanced data migration scenarios. +The major and minor version components are also provided as individual columns for easier querying +by various version ranges, using IDocumentRepository.EnumerateAsync(predicate).

+ + + +

In addition to the default built-in JSON plain-text based serializer (which uses the +System.Text.Json package), there are other +alternative serializers for the document-based repository, including various binary serializers +which will instead persist the document as a byte array:

+ +

Json.NET Bson MessagePack Protobuf

+ +

You can pass the serializer to use to the factory method as follows:

+ +
var repo = TableRepository.Create<Product>(...,
+    serializer: [JsonDocumentSerializer|BsonDocumentSerializer|MessagePackDocumentSerializer|ProtobufDocumentSerializer].Default);
+
+ +
+

NOTE: when using alternative serializers, entities might need to be annotated with whatever +attributes are required by the underlying libraries.

+
+ +

Attributes

+ +

If you want to avoid using strings with the factory methods, you can also annotate the +entity type to modify the default values used:

+ +
    +
  • [Table("tableName")]: class-level attribute to change the default when no value is provided
  • +
  • [PartitionKey]: annotates the property that should be used as the partition key
  • +
  • [RowKey]: annotates the property that should be used as the row key.
  • +
+ +

Values passed to the factory methods override declarative attributes.

+ +

For the products example above, your record entity could be:

+ +
[Table("Products")]
+public record Product([PartitionKey] string Category, [RowKey] string Id) 
+{
+  public string? Title { get; init; }
+  public double Price { get; init; }
+}
+
+ +

And creating the repository wouldn’t require any arguments besides the storage account:

+ +
var repo = TableRepository.Create<Product>(CloudStorageAccount.DevelopmentStorageAccount);
+
+ +

In addition, if you want to omit a particular property from persistence, you can annotate +it with [Browsable(false)] and it will be skipped when persisting and reading the entity.

+ +

TableEntity Support

+ +

Since these repository APIs are quite a bit more intuitive than working directly against a
+TableClient, you might want to retrieve/enumerate entities just by their built-in TableEntity +properties, like PartitionKey, RowKey, Timestamp and ETag. For this scenario, we +also support creating ITableRepository<TableEntity> and ITablePartition<TableEntity> +by using the factory methods TableRepository.Create(...) and TablePartition.Create(...) +without a (generic) entity type argument.

+ +

For example, given you know all Region entities saved in the example above, use the region Code +as the RowKey, you could simply enumerate all regions without using the Region type at all:

+ +
var account = CloudStorageAccount.DevelopmentStorageAccount; // or production one
+var repo = TablePartition.Create(storageAccount, 
+  tableName: "Reference",
+  partitionKey: "Region");
+
+// Enumerate all regions within the partition as plain TableEntities
+await foreach (TableEntity region in repo.EnumerateAsync())
+   Console.WriteLine(region.RowKey);
+
+ +

You can access and add additional properties by just using the entity indexer, which you can +later persist by calling PutAsync:

+ +
await repo.PutAsync(
+    new TableEntity("Book", "9781473217386") 
+    {
+        ["Title"] = "Neuromancer",
+        ["Price"] = 7.32
+    });
+
+var entity = await repo.GetAsync("Book", "9781473217386");
+
+Assert.Equal("Neuromancer", entity["Title"]);
+Assert.Equal(7.32, (double)entity["Price"]);
+
+ + + +

Installation

+ +
> Install-Package Devlooped.TableStorage
+
+ +

All packages also come in source-only versions, if you want to avoid an additional assembly dependency:

+ +
> Install-Package Devlooped.TableStorage.Source
+
+ +

The source-only packages includes all types with the default visibility (internal), so you can decide +what types to make public by declaring a partial class with the desired visibility. To make them all +public, for example, you can include the same Visibility.cs +that the compiled version uses.

+ +

Dogfooding

+ +

CI Version +Build

+ +

We also produce CI packages from branches and pull requests so you can dogfood builds as quickly as they are produced.

+ +

The CI feed is https://pkg.kzu.io/index.json.

+ +

The versioning scheme for packages is:

+ +
    +
  • PR builds: 42.42.42-pr[NUMBER]
  • +
  • Branch builds: 42.42.42-[BRANCH].[COMMITS]
  • +
+ + + +

Sponsors

+ + +

Clarius Org +Kirill Osenkov +MFB Technologies, Inc. +Stephen Shaw +Torutek +DRIVE.NET, Inc. +Ashley Medway +Keith Pickford +Thomas Bolon +Kori Francis +Toni Wenzel +Giorgi Dalakishvili +Uno Platform +Dan Siegel +Reuben Swartz +Jacob Foshee + +Ix Technologies B.V. +David JENNI +Jonathan +Oleg Kyrylchuk +Charley Wu +Jakob Tikjøb Andersen +Seann Alexander +Tino Hager +Mark Seemann +Ken Bonny +Simon Cropp +agileworks-eu +sorahex +Zheyu Shen +Vezel +ChilliCream +4OTC

+ + + +

Sponsor this project

+ +

Learn more about GitHub Sponsors

+ + + +
+
+ + + + + diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..83969dc --- /dev/null +++ b/license.txt @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) Daniel Cazzulino and Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, 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. + diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..2a6d695 --- /dev/null +++ b/readme.md @@ -0,0 +1,374 @@ +![Icon](https://raw.githubusercontent.com/devlooped/TableStorage/main/assets/img/icon-32.png) TableStorage +============ + +[![Version](https://img.shields.io/nuget/v/Devlooped.TableStorage.svg?color=royalblue)](https://www.nuget.org/packages/Devlooped.TableStorage) +[![Downloads](https://img.shields.io/nuget/dt/Devlooped.TableStorage.svg?color=green)](https://www.nuget.org/packages/Devlooped.TableStorage) +[![License](https://img.shields.io/github/license/devlooped/TableStorage.svg?color=blue)](https://github.com/devlooped/TableStorage/blob/main/license.txt) +[![Build](https://github.com/devlooped/TableStorage/workflows/build/badge.svg?branch=main)](https://github.com/devlooped/TableStorage/actions) + + +Repository pattern with POCO object support for storing to Azure/CosmosDB Table Storage + +![Screenshot of basic usage](https://raw.githubusercontent.com/devlooped/TableStorage/main/assets/img/tablestorage.png) + +> NOTE: This library is a thin wrapper around the latest Azure SDK v12+ for Table Storage, +> and uses [CloudStorageAccount](https://www.nuget.org/packages/Devlooped.CloudStorageAccount) which +> is a 100% backwards compatible implementation of the Azure SDK v11 `CloudStorageAccount` class. + +## Usage + +Given an entity like: + +```csharp +public record Product(string Category, string Id) +{ + public required string? Title { get; init; } + public double Price { get; init; } + public DateOnly CreatedAt { get; init; } +} +``` + +> NOTE: entity can have custom constructor, key properties can be read-only +> (Category and Id in this case for example), and it doesn't need to inherit +> from anything, implement any interfaces or use +> any custom attributes (unless you want to). As shown above, it can even be +> a simple record type. + +The entity can be stored and retrieved with: + +```csharp +var storageAccount = CloudStorageAccount.DevelopmentStorageAccount; // or production one +// We lay out the parameter names for clarity only. +var repo = TableRepository.Create(storageAccount, + tableName: "Products", + partitionKey: p => p.Category, + rowKey: p => p.Id); + +var product = new Product("book", "1234") +{ + Title = "Table Storage is Cool", + Price = 25.5, +}; + +// Insert or Update behavior (aka "upsert") +await repo.PutAsync(product); + +// Enumerate all products in category "book" +await foreach (var p in repo.EnumerateAsync("book")) + Console.WriteLine(p.Price); + +// Query books priced in the 20-50 range, +// project just title + price +await foreach (var info in from prod in repo.CreateQuery() + where prod.Price >= 20 and prod.Price <= 50 + select new { prod.Title, prod.Price }) + Console.WriteLine($"{info.Title}: {info.Price}"); + +// Get previously saved product. +Product saved = await repo.GetAsync("book", "1234"); + +// Delete product +await repo.DeleteAsync("book", "1234"); + +// Can also delete passing entity +await repo.DeleteAsync(saved); +``` + +Attributes can also be used to eliminate the need for lambdas altogether when +the entity storage layout is known at compile time: + +```csharp +[Table("Products")] +public record Product([PartitionKey] string Category, [RowKey] string Id) ... + +var storageAccount = CloudStorageAccount.DevelopmentStorageAccount; +// Everything discovered from attributes. +var repo = TableRepository.Create(storageAccount); +``` + +See the [Attributes](#attributes) section below for more details on how to use them. + +If the product were books for example, it might make sense to partition by author. +In that case, you could use a `TableRepository` when saving: + +```csharp +public record Book([RowKey] string ISBN, string Title, string Author, BookFormat Format, int Pages); + +var repo = TableRepository.Create(storageAccount, "Books", + partitionKey: x => x.Author); + +await repo.PutAsync(book); +``` + +> Note how you can mix and match attributes and explicit lambdas as needed. +> The latter takes precedence over the former. + +And later on when listing/filtering books by a particular author, you can use +a `TablePartition` so all querying is automatically scoped to that author: + +```csharp +var partition = TablePartition.Create(storageAccount, "Books", + partitionKey: "Rick Riordan"); + +// Get Rick Riordan books, only from Disney/Hyperion, with over 1000 pages +var query = from book in repo.CreateQuery() + where + book.ISBN.CompareTo("97814231") >= 0 && + book.ISBN.CompareTo("97814232") < 0 && + book.Pages >= 1000 + select new { book.ISBN, book.Title }; +``` + +Using table partitions is quite convenient for handling reference data too, for example. +Enumerating all entries in the partition wouldn't be something you'd typically do for +your "real" data, but for reference data, it could come in handy. + +> NOTE: if access to the `Timestamp` managed by Azure Table Storage for the entity is needed, +> just declare a property with that name with either `DateTimeOffset`, `DateTime` or `string` type +> to read it. + +Stored entities using `TableRepository` and `TablePartition` use individual columns for +properties, which makes it easy to browse the data (and query, as shown above!). + +> NOTE: partition and row keys can also be typed as `Guid` + +Document-based storage is also available via `DocumentRepository` and `DocumentPartition` if +you don't need the individual columns. + + +## Document Storage + +The `DocumentRepository.Create` and `DocumentPartition.Create` factory methods provide access +to document-based storage, exposing the a similar API as column-based storage. + +Document repositories cause entities to be persisted as a single document column, alongside type and version +information to handle versioning a the app level as needed. + +The API is mostly the same as for column-based repositories (document repositories implement +the same underlying `ITableStorage` interface): + +```csharp +public record Product(string Category, string Id) +{ + public string? Title { get; init; } + public double Price { get; init; } + public DateOnly CreatedAt { get; init; } +} + +var book = new Product("book", "9781473217386") +{ + Title = "Neuromancer", + Price = 7.32 +}; + +// Column-based storage +var repo = TableRepository.Create( + CloudStorageAccount.DevelopmentStorageAccount, + tableName: "Products", + partitionKey: p => p.Category, + rowKey: p => p.Id); + +await repo.PutAsync(book); + +// Document-based storage +var docs = DocumentRepository.Create( + CloudStorageAccount.DevelopmentStorageAccount, + tableName: "Documents", + partitionKey: p => p.Category, + rowKey: p => p.Id + serializer: [SERIALIZER]); + +await docs.PutAsync(book); +``` + +> If not provided, the serializer defaults to the `System.Text.Json`-based `DocumentSerializer.Default`. + +The resulting differences in storage can be seen in the following screenshots of the +[Azure Storage Explorer](https://azure.microsoft.com/en-us/features/storage-explorer/): + +![Screenshot of entity persisted with separate columns for properties](https://raw.githubusercontent.com/devlooped/TableStorage/main/assets/img/entity.png) + +![Screenshot of entity persisted as a document](https://raw.githubusercontent.com/devlooped/TableStorage/main/assets/img/document.png) + + +The `Type` column persisted in the documents table is the `Type.FullName` of the persisted entity, and the +`Version` is the `[Major].[Minor]` of its assembly, which could be used for advanced data migration scenarios. +The major and minor version components are also provided as individual columns for easier querying +by various version ranges, using `IDocumentRepository.EnumerateAsync(predicate)`. + + + +In addition to the default built-in JSON plain-text based serializer (which uses the +[System.Text.Json](https://www.nuget.org/packages/system.text.json) package), there are other +alternative serializers for the document-based repository, including various binary serializers +which will instead persist the document as a byte array: + +[![Json.NET](https://img.shields.io/nuget/v/Devlooped.TableStorage.Newtonsoft.svg?color=royalblue&label=Newtonsoft)](https://www.nuget.org/packages/Devlooped.TableStorage.Newtonsoft) [![Bson](https://img.shields.io/nuget/v/Devlooped.TableStorage.Bson.svg?color=royalblue&label=Bson)](https://www.nuget.org/packages/Devlooped.TableStorage.Bson) [![MessagePack](https://img.shields.io/nuget/v/Devlooped.TableStorage.MessagePack.svg?color=royalblue&label=MessagePack)](https://www.nuget.org/packages/Devlooped.TableStorage.MessagePack) [![Protobuf](https://img.shields.io/nuget/v/Devlooped.TableStorage.Protobuf.svg?color=royalblue&label=Protobuf)](https://www.nuget.org/packages/Devlooped.TableStorage.Protobuf) + +You can pass the serializer to use to the factory method as follows: + +```csharp +var repo = TableRepository.Create(..., + serializer: [JsonDocumentSerializer|BsonDocumentSerializer|MessagePackDocumentSerializer|ProtobufDocumentSerializer].Default); +``` + +> NOTE: when using alternative serializers, entities might need to be annotated with whatever +> attributes are required by the underlying libraries. + + +## Attributes + +If you want to avoid using strings with the factory methods, you can also annotate the +entity type to modify the default values used: + +* `[Table("tableName")]`: class-level attribute to change the default when no value is provided +* `[PartitionKey]`: annotates the property that should be used as the partition key +* `[RowKey]`: annotates the property that should be used as the row key. + +Values passed to the factory methods override declarative attributes. + +For the products example above, your record entity could be: + +```csharp +[Table("Products")] +public record Product([PartitionKey] string Category, [RowKey] string Id) +{ + public string? Title { get; init; } + public double Price { get; init; } +} +``` + +And creating the repository wouldn't require any arguments besides the storage account: + +```csharp +var repo = TableRepository.Create(CloudStorageAccount.DevelopmentStorageAccount); +``` + +In addition, if you want to omit a particular property from persistence, you can annotate +it with `[Browsable(false)]` and it will be skipped when persisting and reading the entity. + + +## TableEntity Support + +Since these repository APIs are quite a bit more intuitive than working directly against a +`TableClient`, you might want to retrieve/enumerate entities just by their built-in `TableEntity` +properties, like `PartitionKey`, `RowKey`, `Timestamp` and `ETag`. For this scenario, we +also support creating `ITableRepository` and `ITablePartition` +by using the factory methods `TableRepository.Create(...)` and `TablePartition.Create(...)` +without a (generic) entity type argument. + +For example, given you know all `Region` entities saved in the example above, use the region `Code` +as the `RowKey`, you could simply enumerate all regions without using the `Region` type at all: + +```csharp +var account = CloudStorageAccount.DevelopmentStorageAccount; // or production one +var repo = TablePartition.Create(storageAccount, + tableName: "Reference", + partitionKey: "Region"); + +// Enumerate all regions within the partition as plain TableEntities +await foreach (TableEntity region in repo.EnumerateAsync()) + Console.WriteLine(region.RowKey); +``` + +You can access and add additional properties by just using the entity indexer, which you can +later persist by calling `PutAsync`: + +```csharp +await repo.PutAsync( + new TableEntity("Book", "9781473217386") + { + ["Title"] = "Neuromancer", + ["Price"] = 7.32 + }); + +var entity = await repo.GetAsync("Book", "9781473217386"); + +Assert.Equal("Neuromancer", entity["Title"]); +Assert.Equal(7.32, (double)entity["Price"]); +``` + + + +## Installation + +``` +> Install-Package Devlooped.TableStorage +``` + +All packages also come in source-only versions, if you want to avoid an additional assembly dependency: + +``` +> Install-Package Devlooped.TableStorage.Source +``` + +The source-only packages includes all types with the default visibility (internal), so you can decide +what types to make public by declaring a partial class with the desired visibility. To make them all +public, for example, you can include the same [Visibility.cs](https://github.com/devlooped/TableStorage/blob/main/src/TableStorage/Visibility.cs) +that the compiled version uses. + + +## Dogfooding + +[![CI Version](https://img.shields.io/endpoint?url=https://shields.kzu.io/vpre/Devlooped.TableStorage/main&label=nuget.ci&color=brightgreen)](https://pkg.kzu.io/index.json) +[![Build](https://github.com/devlooped/TableStorage/workflows/build/badge.svg?branch=main)](https://github.com/devlooped/TableStorage/actions) + +We also produce CI packages from branches and pull requests so you can dogfood builds as quickly as they are produced. + +The CI feed is `https://pkg.kzu.io/index.json`. + +The versioning scheme for packages is: + +- PR builds: *42.42.42-pr*`[NUMBER]` +- Branch builds: *42.42.42-*`[BRANCH]`.`[COMMITS]` + + + +# Sponsors + + +[![Clarius Org](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/clarius.png "Clarius Org")](https://github.com/clarius) +[![Kirill Osenkov](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/KirillOsenkov.png "Kirill Osenkov")](https://github.com/KirillOsenkov) +[![MFB Technologies, Inc.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/MFB-Technologies-Inc.png "MFB Technologies, Inc.")](https://github.com/MFB-Technologies-Inc) +[![Stephen Shaw](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/decriptor.png "Stephen Shaw")](https://github.com/decriptor) +[![Torutek](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/torutek-gh.png "Torutek")](https://github.com/torutek-gh) +[![DRIVE.NET, Inc.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/drivenet.png "DRIVE.NET, Inc.")](https://github.com/drivenet) +[![Ashley Medway](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/AshleyMedway.png "Ashley Medway")](https://github.com/AshleyMedway) +[![Keith Pickford](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Keflon.png "Keith Pickford")](https://github.com/Keflon) +[![Thomas Bolon](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/tbolon.png "Thomas Bolon")](https://github.com/tbolon) +[![Kori Francis](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/kfrancis.png "Kori Francis")](https://github.com/kfrancis) +[![Toni Wenzel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/twenzel.png "Toni Wenzel")](https://github.com/twenzel) +[![Giorgi Dalakishvili](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Giorgi.png "Giorgi Dalakishvili")](https://github.com/Giorgi) +[![Uno Platform](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/unoplatform.png "Uno Platform")](https://github.com/unoplatform) +[![Dan Siegel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/dansiegel.png "Dan Siegel")](https://github.com/dansiegel) +[![Reuben Swartz](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/rbnswartz.png "Reuben Swartz")](https://github.com/rbnswartz) +[![Jacob Foshee](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jfoshee.png "Jacob Foshee")](https://github.com/jfoshee) +[![](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Mrxx99.png "")](https://github.com/Mrxx99) +[![Eric Johnson](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/eajhnsn1.png "Eric Johnson")](https://github.com/eajhnsn1) +[![Ix Technologies B.V.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/IxTechnologies.png "Ix Technologies B.V.")](https://github.com/IxTechnologies) +[![David JENNI](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/davidjenni.png "David JENNI")](https://github.com/davidjenni) +[![Jonathan ](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Jonathan-Hickey.png "Jonathan ")](https://github.com/Jonathan-Hickey) +[![Oleg Kyrylchuk](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/okyrylchuk.png "Oleg Kyrylchuk")](https://github.com/okyrylchuk) +[![Charley Wu](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/akunzai.png "Charley Wu")](https://github.com/akunzai) +[![Jakob Tikjøb Andersen](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jakobt.png "Jakob Tikjøb Andersen")](https://github.com/jakobt) +[![Seann Alexander](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/seanalexander.png "Seann Alexander")](https://github.com/seanalexander) +[![Tino Hager](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/tinohager.png "Tino Hager")](https://github.com/tinohager) +[![Mark Seemann](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/ploeh.png "Mark Seemann")](https://github.com/ploeh) +[![Ken Bonny](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/KenBonny.png "Ken Bonny")](https://github.com/KenBonny) +[![Simon Cropp](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/SimonCropp.png "Simon Cropp")](https://github.com/SimonCropp) +[![agileworks-eu](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/agileworks-eu.png "agileworks-eu")](https://github.com/agileworks-eu) +[![sorahex](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/sorahex.png "sorahex")](https://github.com/sorahex) +[![Zheyu Shen](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/arsdragonfly.png "Zheyu Shen")](https://github.com/arsdragonfly) +[![Vezel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/vezel-dev.png "Vezel")](https://github.com/vezel-dev) +[![ChilliCream](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/ChilliCream.png "ChilliCream")](https://github.com/ChilliCream) +[![4OTC](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/4OTC.png "4OTC")](https://github.com/4OTC) + + + + +[![Sponsor this project](https://raw.githubusercontent.com/devlooped/sponsors/main/sponsor.png "Sponsor this project")](https://github.com/sponsors/devlooped) +  + +[Learn more about GitHub Sponsors](https://github.com/sponsors) + +