diff --git a/docs/ai-integration/vector-search/content/_indexing-attachments-for-vector-search-csharp.mdx b/docs/ai-integration/vector-search/content/_indexing-attachments-for-vector-search-csharp.mdx index 86b063916a..7372db0032 100644 --- a/docs/ai-integration/vector-search/content/_indexing-attachments-for-vector-search-csharp.mdx +++ b/docs/ai-integration/vector-search/content/_indexing-attachments-for-vector-search-csharp.mdx @@ -34,7 +34,7 @@ import CodeBlock from '@theme/CodeBlock'; They are stored as **binary data**, regardless of content type. * Attachments are handled as streams, allowing efficient upload and retrieval. - Learn more in: [What are attachments](../../../document-extensions/attachments/what-are-attachments.mdx). + Learn more in: [Attachments overview](../../../document-extensions/attachments/overview.mdx). diff --git a/docs/client-api/operations/_what-are-operations-csharp.mdx b/docs/client-api/operations/_what-are-operations-csharp.mdx index e37c702e25..0ba2266108 100644 --- a/docs/client-api/operations/_what-are-operations-csharp.mdx +++ b/docs/client-api/operations/_what-are-operations-csharp.mdx @@ -144,8 +144,8 @@ Task> SendAsync(PatchOperation #### The following common operations are available: * **Attachments**: -        [PutAttachmentOperation](../../client-api/operations/attachments/put-attachment.mdx) -        [GetAttachmentOperation](../../client-api/operations/attachments/get-attachment.mdx) +        [PutAttachmentOperation](../../document-extensions/attachments/store-attachments/store-attachments-local#store-attachments-via-an-operation) +        [GetAttachmentOperation](../../document-extensions/attachments/get-attachments#get-attachment---using-an-operation)        [DeleteAttachmentOperation](../../client-api/operations/attachments/delete-attachment.mdx) * **Counters**: @@ -356,6 +356,10 @@ Task SendAsync(IMaintenanceOperation operation,        [DeleteRevisionsOperation](../../document-extensions/revisions/client-api/operations/delete-revisions.mdx)        [ConfigureRevisionsBinCleanerOperation](../../document-extensions/revisions/revisions-bin-cleaner.mdx#setting-the-revisions-bin-cleaner---from-the-client-api) +* **Remote attachments**: +        [ConfigureRemoteAttachmentsOperation](../../document-extensions/attachments/configure-remote-attachments.mdx#configure-remote-attachments-settings) +        [GetRemoteAttachmentsConfigurationOperation](../../document-extensions/attachments/configure-remote-attachments.mdx#get-remote-attachments-settings) + * **Sorters**:        [PutSortersOperation](../../client-api/operations/maintenance/sorters/put-sorter.mdx)        DeleteSorterOperation diff --git a/docs/client-api/operations/_what-are-operations-java.mdx b/docs/client-api/operations/_what-are-operations-java.mdx index e73c3f9ba8..26c6bbc04c 100644 --- a/docs/client-api/operations/_what-are-operations-java.mdx +++ b/docs/client-api/operations/_what-are-operations-java.mdx @@ -54,8 +54,8 @@ public Operation sendAsync(IOperation operation, SessionInfo #### Attachments -* [GetAttachmentOperation](../../client-api/operations/attachments/get-attachment.mdx) -* [PutAttachmentOperation](../../client-api/operations/attachments/put-attachment.mdx) +* [GetAttachmentOperation](../../document-extensions/attachments/get-attachments#get-attachment-via-an-operation) +* [PutAttachmentOperation](../../document-extensions/attachments/store-attachments/store-attachments-local#store-attachment-via-an-operation) * [DeleteAttachmentOperation](../../client-api/operations/attachments/delete-attachment.mdx) #### Patching diff --git a/docs/client-api/operations/_what-are-operations-nodejs.mdx b/docs/client-api/operations/_what-are-operations-nodejs.mdx index 875d0f0fa4..f4689796e2 100644 --- a/docs/client-api/operations/_what-are-operations-nodejs.mdx +++ b/docs/client-api/operations/_what-are-operations-nodejs.mdx @@ -118,8 +118,8 @@ await send(patchOperation, sessionInfo, resultType); #### The following common operations are available: * __Attachments__: -        [PutAttachmentOperation](../../client-api/operations/attachments/put-attachment.mdx) -        [GetAttachmentOperation](../../client-api/operations/attachments/get-attachment.mdx) +        [PutAttachmentOperation](../../document-extensions/attachments/store-attachments/store-attachments-local#store-attachment-via-an-operation) +        [GetAttachmentOperation](../../document-extensions/attachments/get-attachments#get-attachment-via-an-operation)        [DeleteAttachmentOperation](../../client-api/operations/attachments/delete-attachment.mdx) * __Counters__: @@ -297,6 +297,10 @@ __Send syntax__: * __Revisions__:        [ConfigureRevisionsOperation](../../document-extensions/revisions/client-api/operations/configure-revisions.mdx) +* __Remote attachments__: +        ConfigureRemoteAttachmentsOperation +        GetRemoteAttachmentsConfigurationOperation + * __Sorters__:        [PutSortersOperation](../../client-api/operations/maintenance/sorters/put-sorter.mdx)        DeleteSorterOperation diff --git a/docs/client-api/operations/_what-are-operations-php.mdx b/docs/client-api/operations/_what-are-operations-php.mdx index 71f6abebc2..405f92b5e7 100644 --- a/docs/client-api/operations/_what-are-operations-php.mdx +++ b/docs/client-api/operations/_what-are-operations-php.mdx @@ -114,8 +114,8 @@ public function send(...$parameters); #### The following common operations are available: * **Attachments**: -        [PutAttachmentOperation](../../client-api/operations/attachments/put-attachment.mdx) -        [GetAttachmentOperation](../../client-api/operations/attachments/get-attachment.mdx) +        PutAttachmentOperation +        GetAttachmentOperation        [DeleteAttachmentOperation](../../client-api/operations/attachments/delete-attachment.mdx) * **Counters**: @@ -287,6 +287,10 @@ $status = $indexStats->getStatus(); // will be "Paused" * **Revisions**:        [ConfigureRevisionsOperation](../../document-extensions/revisions/client-api/operations/configure-revisions.mdx) + +* **Remote attachments**: +        ConfigureRemoteAttachmentsOperation +        GetRemoteAttachmentsConfigurationOperation * **Sorters**:        [PutSortersOperation](../../client-api/operations/maintenance/sorters/put-sorter.mdx) diff --git a/docs/client-api/operations/_what-are-operations-python.mdx b/docs/client-api/operations/_what-are-operations-python.mdx index f01ea43d95..61955440f0 100644 --- a/docs/client-api/operations/_what-are-operations-python.mdx +++ b/docs/client-api/operations/_what-are-operations-python.mdx @@ -114,8 +114,8 @@ def send_patch_operation_with_entity_class( #### The following common operations are available: * **Attachments**: -        [PutAttachmentOperation](../../client-api/operations/attachments/put-attachment.mdx) -        [GetAttachmentOperation](../../client-api/operations/attachments/get-attachment.mdx) +        PutAttachmentOperation +        GetAttachmentOperation        [DeleteAttachmentOperation](../../client-api/operations/attachments/delete-attachment.mdx) * **Counters**: @@ -288,6 +288,10 @@ def send_async(self, operation: MaintenanceOperation[OperationIdResult]) -> Oper * **Revisions**:        [ConfigureRevisionsOperation](../../document-extensions/revisions/client-api/operations/configure-revisions.mdx) + +* **Remote attachments**: +        ConfigureRemoteAttachmentsOperation +        GetRemoteAttachmentsConfigurationOperation * **Sorters**:        [PutSortersOperation](../../client-api/operations/maintenance/sorters/put-sorter.mdx) diff --git a/docs/client-api/operations/attachments/_category_.json b/docs/client-api/operations/attachments/_category_.json deleted file mode 100644 index b2b7ed7266..0000000000 --- a/docs/client-api/operations/attachments/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "position": 5, - "label": Attachments, -} \ No newline at end of file diff --git a/docs/client-api/operations/attachments/_delete-attachment-csharp.mdx b/docs/client-api/operations/attachments/_delete-attachment-csharp.mdx deleted file mode 100644 index b22232e875..0000000000 --- a/docs/client-api/operations/attachments/_delete-attachment-csharp.mdx +++ /dev/null @@ -1,32 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - -This operation is used to delete an attachment from a document. - -## Syntax - - - -{`public DeleteAttachmentOperation(string documentId, string name, string changeVector = null) -`} - - - -| Parameter | | | -|------------------|--------|-------------------------------------------------------------------------| -| **documentId** | string | ID of a document containing an attachment | -| **name** | string | Name of an attachment | -| **changeVector** | string | Entity changeVector, used for concurrency checks (`null` to skip check) | - -## Example - - - -{`store.Operations.Send(new DeleteAttachmentOperation("orders/1-A", "invoice.pdf")); -`} - - - - diff --git a/docs/client-api/operations/attachments/_delete-attachment-java.mdx b/docs/client-api/operations/attachments/_delete-attachment-java.mdx deleted file mode 100644 index eac3cba4bb..0000000000 --- a/docs/client-api/operations/attachments/_delete-attachment-java.mdx +++ /dev/null @@ -1,35 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - -This operation is used to delete an attachment from a document. - -## Syntax - - - -{`DeleteAttachmentOperation(String documentId, String name) - -DeleteAttachmentOperation(String documentId, String name, String changeVector) -`} - - - -| Parameter | | | -|------------------|--------|-------------------------------------------------------------------------| -| **documentId** | String | ID of a document containing an attachment | -| **name** | String | Name of an attachment | -| **changeVector** | String | Entity changeVector, used for concurrency checks (`null` to skip check) | - -## Example - - - -{`store.operations().send( - new DeleteAttachmentOperation("orders/1-A", "invoice.pdf")); -`} - - - - diff --git a/docs/client-api/operations/attachments/_delete-attachment-nodejs.mdx b/docs/client-api/operations/attachments/_delete-attachment-nodejs.mdx deleted file mode 100644 index 05e8c3117d..0000000000 --- a/docs/client-api/operations/attachments/_delete-attachment-nodejs.mdx +++ /dev/null @@ -1,50 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* Use the `DeleteAttachmentOperation` to delete an attachment from a document. - -* In this page: - - * [Delete attachment example](../../../client-api/operations/attachments/delete-attachment.mdx#delete-attachment-example) - * [Syntax](../../../client-api/operations/attachments/delete-attachment.mdx#syntax) - - -## Delete attachment example - - - -{`// Define the delete attachment operation -const deleteAttachmentOp = new DeleteAttachmentOperation("employees/1-A", "photo.jpg"); - -// Execute the operation by passing it to operations.send -await documentStore.operations.send(deleteAttachmentOp); -`} - - - - - -## Syntax - - - -{`// Available overloads: -const deleteAttachmentOp = new DeleteAttachmentOperation(documentId, name); -const deleteAttachmentOp = new DeleteAttachmentOperation(documentId, name, changeVector); -`} - - - -| Parameter | Type | Description | -|------------------|----------|-----------------------------------------------------------------------------------| -| __documentId__ | `string` | ID of document from which attachment will be removed | -| __name__ | `string` | Name of attachment to delete | -| __changeVector__ | `string` | ChangeVector of attachment,
used for concurrency checks (`null` to skip check) | - - - - diff --git a/docs/client-api/operations/attachments/_get-attachment-csharp.mdx b/docs/client-api/operations/attachments/_get-attachment-csharp.mdx deleted file mode 100644 index 47d869ffda..0000000000 --- a/docs/client-api/operations/attachments/_get-attachment-csharp.mdx +++ /dev/null @@ -1,71 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - -This operation is used to get an attachment from a document. - -## Syntax - - - -{`public GetAttachmentOperation(string documentId, string name, AttachmentType type, string changeVector) -`} - - - - - -{`public class AttachmentResult -\{ - public Stream Stream; - public AttachmentDetails Details; -\} - -public class AttachmentDetails : AttachmentName -\{ - public string ChangeVector; - public string DocumentId; -\} - -public class AttachmentName -\{ - public string Name; - public string Hash; - public string ContentType; - public long Size; -\} -`} - - - -| Parameter | | | -|------------------|----------------| ----- | -| **documentId** | string | ID of a document which will contain an attachment | -| **name** | string | Name of an attachment | -| **type** | AttachmentType | Specify whether getting an attachment from a document or from a revision.
(`Document` or `Revision`). | -| **changeVector** | string | The ChangeVector of the document or the revision to which the attachment belongs.
Mandatory when getting an attachment from a revision.
Used for concurrency checks (use `null` to skip the check). | - -| Return Value | | -| ------------- | ----- | -| **Stream** | Stream containing an attachment | -| **ChangeVector** | Change vector of document | -| **DocumentId** | ID of document | -| **Name** | Name of attachment | -| **Hash** | Hash of attachment | -| **ContentType** | MIME content type of an attachment | -| **Size** | Size of attachment | - -## Example - - - -{`store.Operations.Send(new GetAttachmentOperation("orders/1-A", - "invoice.pdf", - AttachmentType.Document, - changeVector: null)); -`} - - - - diff --git a/docs/client-api/operations/attachments/_put-attachment-csharp.mdx b/docs/client-api/operations/attachments/_put-attachment-csharp.mdx deleted file mode 100644 index 8e86ea993d..0000000000 --- a/docs/client-api/operations/attachments/_put-attachment-csharp.mdx +++ /dev/null @@ -1,71 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - -This operation is used to put an attachment to a document. - -## Syntax - - - -{`public PutAttachmentOperation(string documentId, - string name, - Stream stream, - string contentType = null, - string changeVector = null) -`} - - - - - -{`public class AttachmentDetails : AttachmentName -\{ - public string ChangeVector; - public string DocumentId; -\} - -public class AttachmentName -\{ - public string Name; - public string Hash; - public string ContentType; - public long Size; -\} -`} - - - -| Parameter | | | -|------------------|--------|-------------------------------------------------------------------------| -| **documentId** | string | ID of a document which will contain an attachment | -| **name** | string | Name of an attachment | -| **stream** | Stream | Stream contains attachment raw bytes | -| **contentType** | string | MIME type of attachment | -| **changeVector** | string | Entity changeVector, used for concurrency checks (`null` to skip check) | - -| Return Value | | -|------------------|-------------------------------------| -| **ChangeVector** | Change vector of created attachment | -| **DocumentId** | ID of document | -| **Name** | Name of created attachment | -| **Hash** | Hash of created attachment | -| **ContentType** | MIME content type of attachment | -| **Size** | Size of attachment | - -## Example - - - -{`AttachmentDetails attachmentDetails = - store.Operations.Send( - new PutAttachmentOperation("orders/1-A", - "invoice.pdf", - stream, - "application/pdf")); -`} - - - - diff --git a/docs/client-api/operations/attachments/_put-attachment-java.mdx b/docs/client-api/operations/attachments/_put-attachment-java.mdx deleted file mode 100644 index 4bfe0db7f3..0000000000 --- a/docs/client-api/operations/attachments/_put-attachment-java.mdx +++ /dev/null @@ -1,116 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - -This operation is used to put an attachment to a document. - -## Syntax - - - -{`PutAttachmentOperation(String documentId, String name, InputStream stream) - -PutAttachmentOperation(String documentId, String name, InputStream stream, String contentType) - -PutAttachmentOperation(String documentId, String name, InputStream stream, String contentType, String changeVector) -`} - - - - - -{`public class AttachmentDetails extends AttachmentName \{ - private String changeVector; - private String documentId; - - public String getChangeVector() \{ - return changeVector; - \} - - public void setChangeVector(String changeVector) \{ - this.changeVector = changeVector; - \} - - public String getDocumentId() \{ - return documentId; - \} - - public void setDocumentId(String documentId) \{ - this.documentId = documentId; - \} -\} - -public class AttachmentName \{ - private String name; - private String hash; - private String contentType; - private long size; - - public String getName() \{ - return name; - \} - - public void setName(String name) \{ - this.name = name; - \} - - public String getHash() \{ - return hash; - \} - - public void setHash(String hash) \{ - this.hash = hash; - \} - - public String getContentType() \{ - return contentType; - \} - - public void setContentType(String contentType) \{ - this.contentType = contentType; - \} - - public long getSize() \{ - return size; - \} - - public void setSize(long size) \{ - this.size = size; - \} -\} -`} - - - -| Parameter | | | -|------------------| ------------- | ----- | -| **documentId** | String | ID of a document which will contain an attachment | -| **name** | String | Name of an attachment | -| **stream** | InputStream | Stream contains attachment raw bytes | -| **contentType** | String | MIME type of attachment | -| **changeVector** | String | Entity changeVector, used for concurrency checks (`null` to skip check) | - -| Return Value | | -| ------------- | ----- | -| **ChangeVector** | Change vector of created attachment | -| **DocumentId** | ID of document | -| **Name** | Name of created attachment | -| **Hash** | Hash of created attachment | -| **ContentType** | MIME content type of attachment | -| **Size** | Size of attachment | - -## Example - - - -{`AttachmentDetails attachmentDetails = store - .operations().send(new PutAttachmentOperation("orders/1-A", - "invoice.pdf", - stream, - "application/pdf")); -`} - - - - diff --git a/docs/client-api/operations/attachments/_put-attachment-nodejs.mdx b/docs/client-api/operations/attachments/_put-attachment-nodejs.mdx deleted file mode 100644 index b81b0f1ab2..0000000000 --- a/docs/client-api/operations/attachments/_put-attachment-nodejs.mdx +++ /dev/null @@ -1,89 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* Use the `PutAttachmentOperation` to add an attachment to a document. - -* In this page: - - * [Put attachment example](../../../client-api/operations/attachments/put-attachment.mdx#put-attachment-example) - * [Syntax](../../../client-api/operations/attachments/put-attachment.mdx#syntax) - - -## Put attachment example - - - -{`// Prepare content to attach -const text = "Some content..."; -const byteArray = Buffer.from(text); - -// Define the put attachment operation -const putAttachmentOp = new PutAttachmentOperation( - "employees/1-A", "attachmentName.txt", byteArray, "text/plain"); - -// Execute the operation by passing it to operations.send -const attachmentDetails = await documentStore.operations.send(putAttachmentOp); -`} - - - - - -## Syntax - - - -{`// Available overloads: -const putAttachmentOp = new PutAttachmentOperation(documentId, name, stream); -const putAttachmentOp = new PutAttachmentOperation(documentId, name, stream, contentType); -const putAttachmentOp = new PutAttachmentOperation(documentId, name, stream, contentType, changeVector); -`} - - - -| Parameter | Type | Description | -|------------------|------------------------------|-----------------------------------------------------------------------------------| -| __documentId__ | `string` | Document ID to which the attachment will be added | -| __name__ | `string` | Name of attachment to put | -| __stream__ | `stream.Readable` / `Buffer` | A stream that contains the raw bytes of the attachment | -| __contentType__ | `string` | Content type of attachment | -| __changeVector__ | `string` | ChangeVector of attachment,
used for concurrency checks (`null` to skip check) | - -| Return Value of `store.operations.send(putAttachmentOp)` | | -|----------------------------------------------------------|---------------------------------------------| -| `object` | An object with the new attachment's details | - - - -{`// The AttachmentDetails object: -// ============================= -\{ - // Change vector of attachment - changeVector; // string - - // ID of the document that contains the attachment - documentId?; // string - - // Name of attachment - name; // string; - - // Hash of attachment - hash; // string; - - // Content type of attachment - contentType; // string - - // Size of attachment - size; // number -\} -`} - - - - - - diff --git a/docs/client-api/operations/attachments/delete-attachment.mdx b/docs/client-api/operations/attachments/delete-attachment.mdx deleted file mode 100644 index 19b3c51b9c..0000000000 --- a/docs/client-api/operations/attachments/delete-attachment.mdx +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: "Delete Attachment Operation" -sidebar_label: Delete Attachment -sidebar_position: 2 ---- - -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -import DeleteAttachmentCsharp from './_delete-attachment-csharp.mdx'; -import DeleteAttachmentJava from './_delete-attachment-java.mdx'; -import DeleteAttachmentNodejs from './_delete-attachment-nodejs.mdx'; - -export const supportedLanguages = ["csharp", "java", "nodejs"]; - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/client-api/operations/attachments/get-attachment.mdx b/docs/client-api/operations/attachments/get-attachment.mdx deleted file mode 100644 index 692ccb77ad..0000000000 --- a/docs/client-api/operations/attachments/get-attachment.mdx +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: "Get Attachment Operation" -sidebar_label: Get Attachment -sidebar_position: 1 ---- - -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -import GetAttachmentCsharp from './_get-attachment-csharp.mdx'; -import GetAttachmentJava from './_get-attachment-java.mdx'; -import GetAttachmentNodejs from './_get-attachment-nodejs.mdx'; - -export const supportedLanguages = ["csharp", "java", "nodejs"]; - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/client-api/operations/attachments/put-attachment.mdx b/docs/client-api/operations/attachments/put-attachment.mdx deleted file mode 100644 index 91924a00a4..0000000000 --- a/docs/client-api/operations/attachments/put-attachment.mdx +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: "Put Attachment Operation" -sidebar_label: Put Attachment -sidebar_position: 0 ---- - -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -import PutAttachmentCsharp from './_put-attachment-csharp.mdx'; -import PutAttachmentJava from './_put-attachment-java.mdx'; -import PutAttachmentNodejs from './_put-attachment-nodejs.mdx'; - -export const supportedLanguages = ["csharp", "java", "nodejs"]; - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/client-api/operations/counters/_category_.json b/docs/client-api/operations/counters/_category_.json index 1c2a845242..feae47df60 100644 --- a/docs/client-api/operations/counters/_category_.json +++ b/docs/client-api/operations/counters/_category_.json @@ -1,4 +1,4 @@ { - "position": 6, - "label": Counters, + "position": 5, + "label": "Counters" } \ No newline at end of file diff --git a/docs/client-api/operations/maintenance/_get-stats-csharp.mdx b/docs/client-api/operations/maintenance/_get-stats-csharp.mdx index 9c5e83cea3..4dd0fd8ada 100644 --- a/docs/client-api/operations/maintenance/_get-stats-csharp.mdx +++ b/docs/client-api/operations/maintenance/_get-stats-csharp.mdx @@ -16,10 +16,13 @@ import CodeBlock from '@theme/CodeBlock'; * [Get database statistics](../../../client-api/operations/maintenance/get-stats.mdx#get-database-statistics) * [Get detailed database statistics](../../../client-api/operations/maintenance/get-stats.mdx#get-detailed-database-statistics) * [Get statistics for another database](../../../client-api/operations/maintenance/get-stats.mdx#get-statistics-for-another-database) + + ## Get collection statistics To get **collection statistics**, use `GetCollectionStatisticsOperation`: + {`// Pass an instance of class \`GetCollectionStatisticsOperation\` to the store @@ -28,6 +31,7 @@ CollectionStatistics stats = `} + Statistics are returned in the `CollectionStatistics` object. @@ -45,8 +49,6 @@ public class CollectionStatistics - - ## Get detailed collection statistics To get **detailed collection statistics**, use `GetDetailedCollectionStatisticsOperation`: @@ -58,7 +60,9 @@ DetailedCollectionStatistics stats = `} + Statistics are returned in the `DetailedCollectionStatistics` object. + {`// Detailed collection stats results: @@ -86,11 +90,10 @@ public class CollectionDetails - - ## Get database statistics To get **database statistics**, use `GetStatisticsOperation`: + {`// Pass an instance of class \`GetStatisticsOperation\` to the store @@ -99,7 +102,9 @@ DatabaseStatistics stats = `} + Statistics are returned in the `DatabaseStatistics` object. + {`// Database stats results: @@ -114,11 +119,14 @@ public class DatabaseStatistics public long CountOfDocumentsConflicts \{ get; set; \} // Total # of documents conflicts in database public long CountOfTombstones \{ get; set; \} // Total # of tombstones in database public long CountOfConflicts \{ get; set; \} // Total # of conflicts in database - public long CountOfAttachments \{ get; set; \} // Total # of attachments in database - public long CountOfUniqueAttachments \{ get; set; \} // Total # of unique attachments in database public long CountOfCounterEntries \{ get; set; \} // Total # of counter-group entries in database public long CountOfTimeSeriesSegments \{ get; set; \} // Total # of time-series segments in database + // Total # of attachments in database (local & remote) + public long CountOfAttachments \{ get; set; \} + // Total # of unique attachments in database (local only) + public long CountOfUniqueAttachments \{ get; set; \} + // List of stale index names in database public string[] StaleIndexes => Indexes?.Where(x => x.IsStale).Select(x => x.Name).ToArray(); // Statistics for each index in database @@ -137,8 +145,6 @@ public class DatabaseStatistics - - ## Get detailed database statistics To get **detailed database statistics**, use `GetDetailedStatisticsOperation`: @@ -150,27 +156,29 @@ DetailedDatabaseStatistics stats = `} + Statistics are returned in the `DetailedDatabaseStatistics` object. + {`// Detailed database stats results: public class DetailedDatabaseStatistics : DatabaseStatistics \{ - // Total # of identities in database + // Total # of identities in the database public long CountOfIdentities \{ get; set; \} - // Total # of compare-exchange items in database + // Total # of compare-exchange items in the database public long CountOfCompareExchange \{ get; set; \} - // Total # of cmpXchg tombstones in database + // Total # of cmpXchg tombstones in the database public long CountOfCompareExchangeTombstones \{ get; set; \} - // Total # of TS deleted ranges values in database + // Total # of TS deleted ranges values in the database public long CountOfTimeSeriesDeletedRanges \{ get; set; \} + // Total # of remote attachments in the database + public long CountOfRemoteAttachments \{ get; set; \} \} `} - - ## Get statistics for another database * By default, you get statistics for the database defined in your Document Store. @@ -186,8 +194,4 @@ DatabaseStatistics stats = -* Learn more about switching operations to another database [here](../../../client-api/operations/how-to/switch-operations-to-a-different-database.mdx). - - - - +* Learn more about switching operations to another database [here](../../../client-api/operations/how-to/switch-operations-to-a-different-database.mdx). \ No newline at end of file diff --git a/docs/client-api/operations/patching/_category_.json b/docs/client-api/operations/patching/_category_.json index f0be7deba4..a49858f51f 100644 --- a/docs/client-api/operations/patching/_category_.json +++ b/docs/client-api/operations/patching/_category_.json @@ -1,4 +1,4 @@ { - "position": 7, - "label": Patching, + "position": 6, + "label": "Patching" } diff --git a/docs/document-extensions/attachments/_bulk-insert-csharp.mdx b/docs/document-extensions/attachments/_bulk-insert-csharp.mdx deleted file mode 100644 index 9ca3ffbcbe..0000000000 --- a/docs/document-extensions/attachments/_bulk-insert-csharp.mdx +++ /dev/null @@ -1,119 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* [BulkInsert](../../client-api/bulk-insert/how-to-work-with-bulk-insert-operation.mdx) is RavenDB's high-performance data insertion operation. - Use its `AttachmentsFor` interface to add attachments to documents with great speed. - -* In this page: - * [Usage flow](../../document-extensions/attachments/bulk-insert.mdx#usage-flow) - * [Usage example](../../document-extensions/attachments/bulk-insert.mdx#usage-example) - * [Syntax](../../document-extensions/attachments/bulk-insert.mdx#syntax) - - - -## Usage flow - -* Create a `BulkInsert` instance. - -* Pass the Document ID to the instance's `AttachmentsFor` method. - -* To add an attachment, call `Store`. - Pass it the attachment's name, stream, and type (optional). - The `Store` function can be called repeatedly as necessary. - -* Note: - If an attachment with the specified name already exists on the document, - the bulk insert operation will overwrite it. - - - -## Usage example - -In this example, we attach a file to all User documents that match a query. - - - - -{`List users; - -// Choose user profiles for which to attach a file -using (var session = store.OpenSession()) -{ - users = session.Query() - .Where(u => u.Age < 30) - .ToList(); -} - -// Prepare content to attach -byte[] byteArray = Encoding.UTF8.GetBytes("some contents here"); -var stream = new MemoryStream(byteArray); - -// Create a BulkInsert instance -using (var bulkInsert = store.BulkInsert()) -{ - for (var i = 0; i < users.Count; i++) - { - string userId = users[i].Id; - - // Call 'AttachmentsFor', pass the document ID for which to attach the file - var attachmentsBulkInsert = bulkInsert.AttachmentsFor(userId); - - // Call 'Store' to add the file to the BulkInsert instance - // The data stored in bulkInsert will be streamed to the server in batches - attachmentsBulkInsert.Store("AttachmentName", stream); - } -} -`} - - - - -{`public class User -{ - public string Id { get; set; } - public string Name { get; set; } - public string LastName { get; set; } - public string AddressId { get; set; } - public int Count { get; set; } - public int Age { get; set; } -} -`} - - - - - - -## Syntax - - - -{`public AttachmentsBulkInsert AttachmentsFor(string id) -`} - - - -| Parameter | Type | Description | -|------------|----------|----------------------------------------------------------| -| `id` | `string` | The document ID to which the attachment should be added. | - - - -{`public void Store(string name, Stream stream, string contentType = null) -`} - - - -| Parameter | Type | Description | -|---------------|----------|------------------------------------| -| `name` | `string` | Name of attachment | -| `stream` | `Stream` | The attachment's stream | -| `contentType` | `string` | Type of attachment (default: null) | - - - - diff --git a/docs/document-extensions/attachments/_category_.json b/docs/document-extensions/attachments/_category_.json index fc6c0b1e6e..0197c84b56 100644 --- a/docs/document-extensions/attachments/_category_.json +++ b/docs/document-extensions/attachments/_category_.json @@ -1,4 +1,4 @@ { "position": 3, - "label": Attachments, + "label": "Attachments" } \ No newline at end of file diff --git a/docs/document-extensions/attachments/_copying-moving-renaming-csharp.mdx b/docs/document-extensions/attachments/_copying-moving-renaming-csharp.mdx deleted file mode 100644 index 1537f1a14a..0000000000 --- a/docs/document-extensions/attachments/_copying-moving-renaming-csharp.mdx +++ /dev/null @@ -1,148 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - -Attachments can be copied, moved, or renamed using built-in session methods. -All of those actions are executed when `SaveChanges` is called and take place on the server-side, -removing the need to transfer the entire attachment binary data over the network in order to perform the action. - -## Copy attachment - -Attachment can be copied using one of the `session.Advanced.Attachments.Copy` methods: - -### Syntax - - - -{`void Copy( - object sourceEntity, - string sourceName, - object destinationEntity, - string destinationName); - -void Copy( - string sourceDocumentId, - string sourceName, - string destinationDocumentId, - string destinationName); -`} - - - -### Example - - - - -{`var employee1 = session.Load("employees/1-A"); -var employee2 = session.Load("employees/2-A"); - -session.Advanced.Attachments.Copy(employee1, "photo.jpg", employee2, "photo-copy.jpg"); - -session.SaveChanges(); -`} - - - - -{`var employee1 = await asyncSession.LoadAsync("employees/1-A"); -var employee2 = await asyncSession.LoadAsync("employees/2-A"); - -asyncSession.Advanced.Attachments.Copy(employee1, "photo.jpg", employee2, "photo-copy.jpg"); - -await asyncSession.SaveChangesAsync(); -`} - - - - - - -## Move attachment - -Attachment can be moved using one of the `session.Advanced.Attachments.Move` methods: - -### Syntax - - - -{`void Move(object sourceEntity, string sourceName, object destinationEntity, string destinationName); - -void Move(string sourceDocumentId, string sourceName, string destinationDocumentId, string destinationName); -`} - - - -### Example - - - - -{`var employee1 = session.Load("employees/1-A"); -var employee2 = session.Load("employees/2-A"); - -session.Advanced.Attachments.Move(employee1, "photo.jpg", employee2, "photo.jpg"); - -session.SaveChanges(); -`} - - - - -{`var employee1 = await asyncSession.LoadAsync("employees/1-A"); -var employee2 = await asyncSession.LoadAsync("employees/2-A"); - -asyncSession.Advanced.Attachments.Move(employee1, "photo.jpg", employee2, "photo.jpg"); - -await asyncSession.SaveChangesAsync(); -`} - - - - - - -## Rename attachment - -Attachment can be renamed using one of the `session.Advanced.Attachments.Rename` methods: - -### Syntax - - - -{`void Rename(object entity, string name, string newName); - -void Rename(string documentId, string name, string newName); -`} - - - -### Example - - - - -{`var employee = session.Load("employees/1-A"); - -session.Advanced.Attachments.Rename(employee, "photo.jpg", "photo-new.jpg"); - -session.SaveChanges(); -`} - - - - -{`var employee = await asyncSession.LoadAsync("employees/1-A"); - -asyncSession.Advanced.Attachments.Rename(employee, "photo.jpg", "photo-new.jpg"); - -await asyncSession.SaveChangesAsync(); -`} - - - - - - - diff --git a/docs/document-extensions/attachments/_deleting-csharp.mdx b/docs/document-extensions/attachments/_deleting-csharp.mdx deleted file mode 100644 index de50ebeb51..0000000000 --- a/docs/document-extensions/attachments/_deleting-csharp.mdx +++ /dev/null @@ -1,49 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - -**Delete** from `session.Advanced.Attachments` is used to remove an attachment from a document. - -## Syntax - - - -{`void Delete(string documentId, string name); -void Delete(object entity, string name); -`} - - - -## Example - - - - -{`using (var session = store.OpenSession()) -{ - Album album = session.Load("albums/1"); - session.Advanced.Attachments.Delete(album, "001.jpg"); - session.Advanced.Attachments.Delete("albums/1", "002.jpg"); - - session.SaveChanges(); -} -`} - - - - -{`using (var asyncSession = store.OpenAsyncSession()) -{ - Album album = await asyncSession.LoadAsync("albums/1"); - asyncSession.Advanced.Attachments.Delete(album, "001.jpg"); - asyncSession.Advanced.Attachments.Delete("albums/1", "002.jpg"); - - await asyncSession.SaveChangesAsync(); -} -`} - - - - - diff --git a/docs/document-extensions/attachments/_deleting-java.mdx b/docs/document-extensions/attachments/_deleting-java.mdx deleted file mode 100644 index 57b740bfda..0000000000 --- a/docs/document-extensions/attachments/_deleting-java.mdx +++ /dev/null @@ -1,34 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - -**Delete** from `session.advanced().attachments()` is used to remove an attachment from a document. - -## Syntax - - - -{`void delete(String documentId, String name); - -void delete(Object entity, String name); -`} - - - -## Example - - - -{`try (IDocumentSession session = store.openSession()) \{ - Album album = session.load(Album.class, "albums/1"); - session.advanced().attachments().delete(album, "001.jpg"); - session.advanced().attachments().delete("albums/1", "002.jpg"); - - session.saveChanges(); -\} -`} - - - - diff --git a/docs/document-extensions/attachments/_deleting-nodejs.mdx b/docs/document-extensions/attachments/_deleting-nodejs.mdx deleted file mode 100644 index 9ab7f434d5..0000000000 --- a/docs/document-extensions/attachments/_deleting-nodejs.mdx +++ /dev/null @@ -1,34 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - -The method `session.advanced.attachments.delete()` is used to remove an attachment from a document. - -## Syntax - - - -{`session.advanced.attachments.delete(documentId, name); - -session.advanced.attachments.delete(entity, name); -`} - - - -## Example - - - -{`const session = store.openSession(); -const album = await session.load("albums/1"); -session.advanced.attachments.delete(album, "001.jpg"); - -session.advanced.attachments.delete("albums/1", "002.jpg"); - -await session.saveChanges(); -`} - - - - diff --git a/docs/document-extensions/attachments/_loading-csharp.mdx b/docs/document-extensions/attachments/_loading-csharp.mdx deleted file mode 100644 index cf088ddae7..0000000000 --- a/docs/document-extensions/attachments/_loading-csharp.mdx +++ /dev/null @@ -1,231 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - -Learn in this page how to load a part of an attachment, an entire attachment, -or multiple attachments. - -* In this page: - * [Load attachments](../../document-extensions/attachments/loading.mdx#load-attachments) - * [Load a part of an attachment](../../document-extensions/attachments/loading.mdx#load-a-part-of-an-attachment) - - -## Load attachments - -* Use these methods to load attachments from the database. - * **session.Advanced.Attachments.Get** - Can be used to download an attachment or multiple attachments. - * **session.Advanced.Attachments.GetNames** - Can be used to download all attachment names that are attached to a document. - * **session.Advanced.Attachments.GetRevision** - Can be used to download an attachment of a revision document. - -* Use this method to verify that an attachment exists. - * **session.Advanced.Attachments.Exists** - -## Syntax - - - - -{`AttachmentResult Get(string documentId, string name); -AttachmentResult Get(object entity, string name); -IEnumerator Get(IEnumerable attachments); -AttachmentName[] GetNames(object entity); -AttachmentResult GetRevision(string documentId, string name, string changeVector); -bool Exists(string documentId, string name); -`} - - - - -{`Task GetAsync(string documentId, string name, CancellationToken token = default); -Task GetAsync(object entity, string name, CancellationToken token = default); -Task> GetAsync(IEnumerable attachments, CancellationToken token = default); -Task GetRevisionAsync(string documentId, string name, string changeVector, CancellationToken token = default); -Task ExistsAsync(string documentId, string name, CancellationToken token = default); -`} - - - - -## Example I - - - - -{`using (var session = store.OpenSession()) -{ - Album album = session.Load("albums/1"); - - using (AttachmentResult file1 = session.Advanced.Attachments.Get(album, "001.jpg")) - using (AttachmentResult file2 = session.Advanced.Attachments.Get("albums/1", "002.jpg")) - { - Stream stream = file1.Stream; - - AttachmentDetails attachmentDetails = file1.Details; - string name = attachmentDetails.Name; - string contentType = attachmentDetails.ContentType; - string hash = attachmentDetails.Hash; - long size = attachmentDetails.Size; - string documentId = attachmentDetails.DocumentId; - string changeVector = attachmentDetails.ChangeVector; - } - - AttachmentName[] attachmentNames = session.Advanced.Attachments.GetNames(album); - foreach (AttachmentName attachmentName in attachmentNames) - { - string name = attachmentName.Name; - string contentType = attachmentName.ContentType; - string hash = attachmentName.Hash; - long size = attachmentName.Size; - } - - bool exists = session.Advanced.Attachments.Exists("albums/1", "003.jpg"); -} -`} - - - - -{`using (var asyncSession = store.OpenAsyncSession()) -{ - Album album = await asyncSession.LoadAsync("albums/1"); - - using (AttachmentResult file1 = await asyncSession.Advanced.Attachments.GetAsync(album, "001.jpg")) - using (AttachmentResult file2 = await asyncSession.Advanced.Attachments.GetAsync("albums/1", "002.jpg")) - { - Stream stream = file1.Stream; - - AttachmentDetails attachmentDetails = file1.Details; - string name = attachmentDetails.Name; - string contentType = attachmentDetails.ContentType; - string hash = attachmentDetails.Hash; - long size = attachmentDetails.Size; - string documentId = attachmentDetails.DocumentId; - string changeVector = attachmentDetails.ChangeVector; - } - - AttachmentName[] attachmentNames = asyncSession.Advanced.Attachments.GetNames(album); - foreach (AttachmentName attachmentName in attachmentNames) - { - string name = attachmentName.Name; - string contentType = attachmentName.ContentType; - string hash = attachmentName.Hash; - long size = attachmentName.Size; - } - - bool exists = await asyncSession.Advanced.Attachments.ExistsAsync("albums/1", "003.jpg"); -} -`} - - - - -## Example II -Here, we load multiple string attachments we previously created for a document. We then -go through them, and decode each attachment to its original text. - - - -{`// Load a user profile -var user = session.Load(userId); - -// Get the names of files attached to this document -IEnumerable attachmentNames = session.Advanced.Attachments.GetNames(user).Select(x => new AttachmentRequest(userId, x.Name)); - -// Get the attached files -IEnumerator attachmentsEnumerator = session.Advanced.Attachments.Get(attachmentNames); - -// Go through the document's attachments -while (attachmentsEnumerator.MoveNext()) -{ - AttachmentEnumeratorResult res = attachmentsEnumerator.Current; - - AttachmentDetails attachmentDetails = res.Details; // attachment details - - Stream attachmentStream = res.Stream; // attachment contents - - // In this case it is a string attachment, that can be decoded back to text - var ms = new MemoryStream(); - attachmentStream.CopyTo(ms); - string decodedStream = Encoding.UTF8.GetString(ms.ToArray()); -} -`} - - - - -{`// Load a user profile -var user = await session.LoadAsync(userId); - -// Get the names of files attached to this document -IEnumerable attachmentNames = session.Advanced.Attachments.GetNames(user).Select(x => new AttachmentRequest(userId, x.Name)); - -// Get the attached files -IEnumerator attachmentsEnumerator = await session.Advanced.Attachments.GetAsync(attachmentNames); - -// Go through the document's attachments -while (attachmentsEnumerator.MoveNext()) -{ - AttachmentEnumeratorResult res = attachmentsEnumerator.Current; - - AttachmentDetails attachmentDetails = res.Details; // attachment details - - Stream attachmentStream = res.Stream; // attachment contents - - // In this case it is a string attachment, that can be decoded back to text - var ms = new MemoryStream(); - attachmentStream.CopyTo(ms); - string decodedStream = Encoding.UTF8.GetString(ms.ToArray()); -} -`} - - - - - - -## Load a part of an attachment - -Use `GetRange` to load a part of an attachment by document ID and the attachment name. - -## Syntax - - -{`// Returns a range of the attachment by the document id and attachment name. -AttachmentResult GetRange(string documentId, string name, long? from, long? to); - -// Returns a range of the attachment by the document id and attachment name. -AttachmentResult GetRange(object entity, string name, long? from, long? to); -`} - - - -## Sample - - - -{`Album album = session.Load("albums/1"); - -AttachmentResult attachmentPart = session.Advanced.Attachments.GetRange( - album, "track1.mp3", 101, 200); -`} - - - - -{`Album album = await asyncSession.LoadAsync("albums/1"); - -AttachmentResult file1 = await asyncSession.Advanced.Attachments.GetRangeAsync( - album, "track1.mp3", 101, 200); -`} - - - - - - - diff --git a/docs/document-extensions/attachments/_loading-java.mdx b/docs/document-extensions/attachments/_loading-java.mdx deleted file mode 100644 index 60560f3ea0..0000000000 --- a/docs/document-extensions/attachments/_loading-java.mdx +++ /dev/null @@ -1,69 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - -There are a few methods that allow you to download attachments from a database: - -**session.advanced().attachments().get** can be used to download an attachment. -**session.advanced().attachments().getNames** can be used to download all attachment names that are attached to a document. -**session.advanced().attachments().getRevision** can be used to download an attachment of a revision document. -**session.advanced().attachments().exists** can be used to determine if an attachment exists on a document. - -## Syntax - - - -{`AttachmentName[] getNames(Object entity); - -boolean exists(String documentId, String name); - -CloseableAttachmentResult get(String documentId, String name); - -CloseableAttachmentResult get(Object entity, String name); - -CloseableAttachmentResult getRevision(String documentId, String name, String changeVector); -`} - - - -## Example - - - -{`try (IDocumentSession session = store.openSession()) \{ - Album album = session.load(Album.class, "albums/1"); - - try (CloseableAttachmentResult file1 = session - .advanced().attachments().get(album, "001.jpg"); - CloseableAttachmentResult file2 = session - .advanced().attachments().get("albums/1", "002.jpg")) \{ - - InputStream inputStream = file1 - .getData(); - - AttachmentDetails attachmentDetails = file1.getDetails(); - String name = attachmentDetails.getName(); - String contentType = attachmentDetails.getContentType(); - String hash = attachmentDetails.getHash(); - long size = attachmentDetails.getSize(); - String documentId = attachmentDetails.getDocumentId(); - String changeVector = attachmentDetails.getChangeVector(); - \} - - AttachmentName[] attachmentNames = session.advanced().attachments().getNames(album); - for (AttachmentName attachmentName : attachmentNames) \{ - - String name = attachmentName.getName(); - String contentType = attachmentName.getContentType(); - String hash = attachmentName.getHash(); - long size = attachmentName.getSize(); - \} - - boolean exists = session.advanced().attachments().exists("albums/1", "003.jpg"); -\} -`} - - - - diff --git a/docs/document-extensions/attachments/_loading-nodejs.mdx b/docs/document-extensions/attachments/_loading-nodejs.mdx deleted file mode 100644 index 8df5bc3ea3..0000000000 --- a/docs/document-extensions/attachments/_loading-nodejs.mdx +++ /dev/null @@ -1,76 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - -There are a few methods that allow you to download attachments from a database: - -**session.advanced.attachments.get()** can be used to download an attachment. -**session.advanced.attachments.getNames()** can be used to download all attachment names that are attached to a document. -**session.advanced.attachments.getRevision()** can be used to download an attachment of a revision document. -**session.advanced.attachments.exists()** can be used to determine if an attachment exists on a document. - -## Syntax - - - -{`session.advanced.attachments.getNames(entity); - -session.advanced.attachments.exists(documentId, name); - -session.advanced.attachments.get(documentId, name); - -session.advanced.attachments.get(entity, name); - -session.advanced.attachments.getRevision(documentId, name, changeVector); -`} - - - -| Parameters | | | -| ------------- | ------------- | ----- | -| **entity** or **documentId** | object or string | instance of the entity or the entity ID | -| **name** | string | attachment name | -| **changeVector** | string | change vector for revision identification | - -| Return Value | | -| ------------- | ------------- | -| `Promise` | Promise resolving to a Readable for attachment content | - -## Example - - - -{`const album = await session.load("albums/1"); - -const file1 = await session.advanced.attachments.get(album, "001.jpg"); -const file2 = await session.advanced.attachments.get("albums/1", "002.jpg"); - -const inputStream = file1.data; - -const attachmentDetails = file1.details; -// \{ -// name: '001.jpg', -// documentId: 'albums/1', -// contentType: 'image/jpeg', -// hash: 'MvUEcrFHSVDts5ZQv2bQ3r9RwtynqnyJzIbNYzu1ZXk=', -// changeVector: '"A:3-K5TR36dafUC98AItzIa6ow"', -// size: 25793 -// \} - -const attachmentNames = await session.advanced.attachments.getNames(album); -for (const attachmentName of attachmentNames) \{ - const name = attachmentName.name; - const contentType = attachmentName.contentType; - const hash = attachmentName.hash; - const size = attachmentName.size; -\} - -const exists = session.advanced.attachments.exists("albums/1", "003.jpg"); -// true - -`} - - - - diff --git a/docs/document-extensions/attachments/_storing-csharp.mdx b/docs/document-extensions/attachments/_storing-csharp.mdx deleted file mode 100644 index 4562e5b827..0000000000 --- a/docs/document-extensions/attachments/_storing-csharp.mdx +++ /dev/null @@ -1,79 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - -In order to store an attachment in RavenDB you need to create a document. Then you can attach an attachment to the document using the `session.Advanced.Attachments.Store` method. - -Attachments, just like documents, are a part of the session and will only be saved on the Server when `DocumentSession.SaveChanges` is executed (you can read more about saving changes in session [here](../../client-api/session/saving-changes.mdx)). - -## Syntax - -Attachments can be stored using one of the following `session.Advanced.Attachments.Store` methods: - - - -{`void Store(string documentId, string name, Stream stream, string contentType = null); -void Store(object entity, string name, Stream stream, string contentType = null); -`} - - - -## Example - - - - -{`using (var session = store.OpenSession()) -using (var file1 = File.Open("001.jpg", FileMode.Open)) -using (var file2 = File.Open("002.jpg", FileMode.Open)) -using (var file3 = File.Open("003.jpg", FileMode.Open)) -using (var file4 = File.Open("004.mp4", FileMode.Open)) -{ - var album = new Album - { - Name = "Holidays", - Description = "Holidays travel pictures of the all family", - Tags = new[] { "Holidays Travel", "All Family" }, - }; - session.Store(album, "albums/1"); - - session.Advanced.Attachments.Store("albums/1", "001.jpg", file1, "image/jpeg"); - session.Advanced.Attachments.Store("albums/1", "002.jpg", file2, "image/jpeg"); - session.Advanced.Attachments.Store("albums/1", "003.jpg", file3, "image/jpeg"); - session.Advanced.Attachments.Store("albums/1", "004.mp4", file4, "video/mp4"); - - session.SaveChanges(); -} -`} - - - - -{`using (var asyncSession = store.OpenAsyncSession()) -using (var file1 = File.Open("001.jpg", FileMode.Open)) -using (var file2 = File.Open("002.jpg", FileMode.Open)) -using (var file3 = File.Open("003.jpg", FileMode.Open)) -using (var file4 = File.Open("004.mp4", FileMode.Open)) -{ - var album = new Album - { - Name = "Holidays", - Description = "Holidays travel pictures of the all family", - Tags = new[] { "Holidays Travel", "All Family" }, - }; - await asyncSession.StoreAsync(album, "albums/1"); - - asyncSession.Advanced.Attachments.Store("albums/1", "001.jpg", file1, "image/jpeg"); - asyncSession.Advanced.Attachments.Store("albums/1", "002.jpg", file2, "image/jpeg"); - asyncSession.Advanced.Attachments.Store("albums/1", "003.jpg", file3, "image/jpeg"); - asyncSession.Advanced.Attachments.Store("albums/1", "004.mp4", file4, "video/mp4"); - - await asyncSession.SaveChangesAsync(); -} -`} - - - - - diff --git a/docs/document-extensions/attachments/_storing-java.mdx b/docs/document-extensions/attachments/_storing-java.mdx deleted file mode 100644 index bfa024b483..0000000000 --- a/docs/document-extensions/attachments/_storing-java.mdx +++ /dev/null @@ -1,60 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - -In order to store an attachment in RavenDB you need to create a document. Then you can attach an attachment to the document using the `session.advanced().attachments().store` method. - -Attachments, just like documents, are a part of the session and will only be saved on the Server when `DocumentSession.saveChanges` is executed (you can read more about saving changes in session [here](../../client-api/session/saving-changes.mdx)). - -## Syntax - -Attachments can be stored using one of the following `session.advanced().attachments().store` methods: - - - -{`void store(String documentId, String name, InputStream stream); - -void store(String documentId, String name, InputStream stream, String contentType); - -void store(Object entity, String name, InputStream stream); - -void store(Object entity, String name, InputStream stream, String contentType); -`} - - - -## Example - - - -{`try (IDocumentSession session = store.openSession()) \{ - try ( - FileInputStream file1 = new FileInputStream("001.jpg"); - FileInputStream file2 = new FileInputStream("002.jpg"); - FileInputStream file3 = new FileInputStream("003.jpg"); - FileInputStream file4 = new FileInputStream("004.mp4") - ) \{ - Album album = new Album(); - album.setName("Holidays"); - album.setDescription("Holidays travel pictures of the all family"); - album.setTags(new String[] \{ "Holidays Travel", "All Family" \}); - session.store(album, "albums/1"); - - session.advanced().attachments() - .store("albums/1", "001.jpg", file1, "image/jpeg"); - session.advanced().attachments() - .store("albums/1", "002.jpg", file2, "image/jpeg"); - session.advanced().attachments() - .store("albums/1", "003.jpg", file3, "image/jpeg"); - session.advanced().attachments() - .store("albums/1", "004.mp4", file4, "video/mp4"); - - session.saveChanges(); - \} -\} -`} - - - - diff --git a/docs/document-extensions/attachments/_storing-nodejs.mdx b/docs/document-extensions/attachments/_storing-nodejs.mdx deleted file mode 100644 index 65910b461a..0000000000 --- a/docs/document-extensions/attachments/_storing-nodejs.mdx +++ /dev/null @@ -1,61 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - -In order to store an attachment in RavenDB you need to create a document. Then you can attach an attachment to the document using the `session.advanced.attachments.store()` method. - -Attachments, just like documents, are a part of the session and will only be saved on the Server when `DocumentSession.saveChanges()` is executed. - -## Syntax - -Attachments can be stored using one of the following `session.advanced.attachments.store()` methods: - - - -{`session.advanced.attachments.store(documentId, name, stream, [contentType]); - -session.advanced.attachments.store(entity, name, stream, [contentType]); -`} - - - -| Parameters | | | -| ------------- | ------------- | ----- | -| **entity** or **documentId** | object or string | instance of the entity or the entity ID | -| **name** | string | attachment name | -| **stream** | `Readable` or `Buffer` | attachment content | -| **contentType** | string | attachment content type | - -## Example - - - -{`const session = store.openSession(); - -const file1 = fs.createReadStream("001.jpg"); -const file2 = fs.createReadStream("002.jpg"); -const file3 = fs.createReadStream("003.jpg"); -const file4 = fs.createReadStream("004.mp4"); - -const album = new Album(); -album.name = "Holidays"; -album.description = "Holidays travel pictures of the all family"; -album.tags = [ "Holidays Travel", "All Family" ]; -await session.store(album, "albums/1"); - -session.advanced.attachments - .store("albums/1", "001.jpg", file1, "image/jpeg"); -session.advanced.attachments - .store("albums/1", "002.jpg", file2, "image/jpeg"); -session.advanced.attachments - .store("albums/1", "003.jpg", file3, "image/jpeg"); -session.advanced.attachments - .store("albums/1", "004.mp4", file4, "video/mp4"); - -await session.saveChanges(); -`} - - - - diff --git a/docs/document-extensions/attachments/_what-are-attachments-java.mdx b/docs/document-extensions/attachments/_what-are-attachments-java.mdx deleted file mode 100644 index 3b5015f6a0..0000000000 --- a/docs/document-extensions/attachments/_what-are-attachments-java.mdx +++ /dev/null @@ -1,88 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - -In RavenDB, attachments are binary streams which can be bound to an existing document. -Each attachment has a name, and you can specify the content type (`image/png` or `application/pdf` for example). - -A document can have any number of attachments. - -Each attachment is bound to an existing document. In order to get a document, you'll need to specify the document ID and the attachment name. -What's great in this approach is that you can specify the attachment's metadata in the document itself, and this document can be queried as any other document. - -## Example I - -In order to store an album of pictures in RavenDB, you can create the following "albums/1" document: - - - -{`\{ - "UserId": "users/1", - "Name": "Holidays", - "Description": "Holidays travel pictures of the all family", - "Tags": ["Holidays Travel", "All Family"], - "@metadata": \{ - "@collection": "Albums" - \} -\} -`} - - - -This document can have the following attachments: - -| Name | Content type | -| - | - | -| `001.jpg` | `image/jpeg` | -| `002.jpg` | `image/jpeg` | -| `003.jpg` | `image/jpeg` | -| `004.mp4` | `video/mp4` | - -## Example II - -You can store a `users/1` document and attach to it to a profile picture. -When requesting the document from the server the results would be: - - - -{`\{ - "Name": "Hibernating Rhinos", - "@metadata": \{ - "@attachments": [ - \{ - "ContentType": "image/png", - "Hash": "iFg0o6D38pUcWGVlP71ddDp8SCcoEal47kG3LtWx0+Y=", - "Name": "profile.png", - "Size": 33241 - \} - ], - "@collection": "Users", - "@change-vector": "A:1061-D11EJRPTVEGKpMaH2BUl9Q", - "@flags": "HasAttachments", - "@id": "users/1", - "@last-modified": "2017-12-05T12:36:24.0504021Z" - \} -\} -`} - - - -Note that this document has an HasAttachments flag and an @attachments array with the attachment's info. - -You can see the attachment's name, content type, hash and size. - - -We would store the attachment streams by the hash, so if many attachments have the same hash, their streams would be stored just once. - - -## Transaction Support - -In RavenDB, attachment and documents are stored as ACID transaction: You either get all of them saved to disk or none. - -## Revisions and Attachments - -When the revisions feature is turned on in your database, each attachment addition to a document (or deletion from a document) will create a new revision of the document, -as there will be a change to the document's metadata, as shown in example #2. - - diff --git a/docs/document-extensions/attachments/assets/attachments-stats-1.png b/docs/document-extensions/attachments/assets/attachments-stats-1.png new file mode 100644 index 0000000000..09a6fc1ca7 Binary files /dev/null and b/docs/document-extensions/attachments/assets/attachments-stats-1.png differ diff --git a/docs/document-extensions/attachments/assets/attachments-stats-2.png b/docs/document-extensions/attachments/assets/attachments-stats-2.png new file mode 100644 index 0000000000..02ea118969 Binary files /dev/null and b/docs/document-extensions/attachments/assets/attachments-stats-2.png differ diff --git a/docs/document-extensions/attachments/assets/configure-remote-attachments-1.png b/docs/document-extensions/attachments/assets/configure-remote-attachments-1.png new file mode 100644 index 0000000000..09e698d551 Binary files /dev/null and b/docs/document-extensions/attachments/assets/configure-remote-attachments-1.png differ diff --git a/docs/document-extensions/attachments/assets/configure-remote-attachments-2.png b/docs/document-extensions/attachments/assets/configure-remote-attachments-2.png new file mode 100644 index 0000000000..5b89b6c587 Binary files /dev/null and b/docs/document-extensions/attachments/assets/configure-remote-attachments-2.png differ diff --git a/docs/document-extensions/attachments/assets/configure-remote-attachments-3.png b/docs/document-extensions/attachments/assets/configure-remote-attachments-3.png new file mode 100644 index 0000000000..2a5aa67757 Binary files /dev/null and b/docs/document-extensions/attachments/assets/configure-remote-attachments-3.png differ diff --git a/docs/document-extensions/attachments/assets/configure-remote-attachments-4.png b/docs/document-extensions/attachments/assets/configure-remote-attachments-4.png new file mode 100644 index 0000000000..f968f9cfe4 Binary files /dev/null and b/docs/document-extensions/attachments/assets/configure-remote-attachments-4.png differ diff --git a/docs/document-extensions/attachments/assets/delete-attachment.png b/docs/document-extensions/attachments/assets/delete-attachment.png new file mode 100644 index 0000000000..2c5fb35fb6 Binary files /dev/null and b/docs/document-extensions/attachments/assets/delete-attachment.png differ diff --git a/docs/document-extensions/attachments/assets/document-with-attachments-1.png b/docs/document-extensions/attachments/assets/document-with-attachments-1.png new file mode 100644 index 0000000000..522e85a1fd Binary files /dev/null and b/docs/document-extensions/attachments/assets/document-with-attachments-1.png differ diff --git a/docs/document-extensions/attachments/assets/document-with-attachments-2.png b/docs/document-extensions/attachments/assets/document-with-attachments-2.png new file mode 100644 index 0000000000..b8fe192161 Binary files /dev/null and b/docs/document-extensions/attachments/assets/document-with-attachments-2.png differ diff --git a/docs/document-extensions/attachments/assets/get-attachment.png b/docs/document-extensions/attachments/assets/get-attachment.png new file mode 100644 index 0000000000..bc73a985ee Binary files /dev/null and b/docs/document-extensions/attachments/assets/get-attachment.png differ diff --git a/docs/document-extensions/attachments/attachments-and-other-features.mdx b/docs/document-extensions/attachments/attachments-and-other-features.mdx new file mode 100644 index 0000000000..354ed4d668 --- /dev/null +++ b/docs/document-extensions/attachments/attachments-and-other-features.mdx @@ -0,0 +1,145 @@ +--- +title: "Attachments and Other Features" +sidebar_label: "Attachments and Other Features" +sidebar_position: 7 +--- + +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; + + + +* Once an attachment is added to a document, the `@attachments` property is added to the document’s metadata with details about the attachment. + +* This metadata update triggers any features that respond to document changes - + such as [Indexing](../../studio/database/indexes/indexes-overview), + any defined [ongoing tasks](../../studio/database/tasks/ongoing-tasks/general-info), + [Subscriptions](../../client-api/data-subscriptions/what-are-data-subscriptions), etc. + +* In this article: + * [Attachments & Replication](../../document-extensions/attachments/attachments-and-other-features#attachments-replication) + * [Attachments & RavenDB ETL](../../document-extensions/attachments/attachments-and-other-features#attachments-ravendb-etl) + * [Attachments & Export/import/backup/restore](../../document-extensions/attachments/attachments-and-other-features#attachments-export-import-backup-restore) + * [Attachments & Revisions](../../document-extensions/attachments/attachments-and-other-features#attachments-revisions) + + + + + +Replication is triggered by document changes, including when attachments are added or modified. +* **Internal replication** occurs automatically between nodes in the database group. +* **External replication** is a defined ongoing task between two RavenDB databases (typically across clusters). + +Learn more about replication in [Replication overview](../../server/clustering/replication/replication-overview). + +--- + +For both internal and external replication: + +**Local attachments**: +The document is replicated along with the attachment's binary content, which will be stored on the target node. + +**Remote attachments**: +The document is replicated without the binary content, only the metadata is transferred. +The attachment's content resides in shared remote storage (e.g., S3, Azure Blob) and does not need to be copied between nodes. + + * **Target server compatibility**: + If the target node is running a RavenDB server version earlier than `7.2`, which does not support remote attachments, + replicating a document that references a remote attachment will throw a `LegacyReplicationViolationException`. + + * **Remote destination configuration for external replication**: + When using external replication, the remote storage destinations must also be configured on the target database. + This is your responsibility, RavenDB does not automatically replicate the remote attachment configurations. + If the destination is not defined on the target, the remote attachment will be inaccessible after replication. + + + + + +A [RavenDB ETL task](../../server/ongoing-tasks/etl/raven) sends documents to another RavenDB database and allows you to transform documents using a script. +The ETL task is triggered by document changes in the source database, including when attachments are added or modified. + +--- + +**Local attachments**: +The document is sent to the target database along with the binary attachment stream. + +**Remote attachments**: +The document is sent without the binary stream, only the metadata is transferred, since the attachment content resides in remote storage. + + * **Remote destination configuration**: + When documents that reference remote attachments are sent via RavenDB ETL, you must ensure that the same remote attachment configuration is defined on the target database. + This is your responsibility, as the ETL process does not automatically configure remote destinations on the target side. + + * **Target server compatibility**: + If the target node is running a RavenDB server version earlier than `7.2`, which does not support remote attachments, + attempting to send a document that references a remote attachment will result in an excetion. + + + + + +The following behavior applies to [Export](../../studio/database/tasks/export-database), +[Import](../../studio/database/tasks/import-data/import-data-file), +[Backup](../../studio/database/tasks/backup-task), +and [Restore from backup](../../studio/database/create-new-database/from-backup) operations. + +--- + +**Local attachments**: +The attachment metadata and the binary content are included in the operation: + * During export/backup, the binary stream is written to the dump file. + * During import/restore, the binary stream is loaded from the dump into the database. + +**Remote attachments**: + + * **Attachments metadata**: + * During **export/backup**: + only the metadata is written to the dump, + the binary content remains in remote storage. + * During **import/restore**: + the metadata is loaded from the dump into the database, + the content is expected to remain accessible in its original remote location. + + * **Remote attachments configuration**: + By default, RavenDB includes the remote attachments configuration, including the list of defined remote destinations, in both export/backup and import/restore operations. + + However, during **import or restore**, RavenDB does not verify the actual remote storage itself. + You must ensure that: + * The destination identifiers still point to valid and accessible remote storage. + * The external storage contains all the remote attachment files referenced in the imported documents. + If the files are missing, the attachments will not be accessible. + + + + + +Revisions are immutable snapshots of a document's state at a given point in time. +If [Document revisions](../../document-extensions/revisions/overview) are enabled, any addition or removal of attachments will create a new revision of the document. + +--- + +When a revision is created, it records the attachment's state exactly as it was at that moment. +The storage location of the attachment is bound to the revision’s creation context, not to the current state of the document: +* If the attachment was **local**, the revision stores it as local. +* If the attachment was **remote**, the revision stores only the remote parameters (e.g., destination identifier, upload time). + +The background process that uploads attachments to remote storage only processes current documents. +It does not process or modify revisions. + * Revisions that reference an attachment as local will remain local forever, even if the attachment in the current (parent) document is + [scheduled for remote uploading](../../document-extensions/attachments/store-attachments/store-attachments-remote#schedule-existing-attachments-for-remote-upload) and later uploaded to remote storage. + * The binary content will be retained on disk until all references to the attachments are removed - + both from the parent document and from any revision that still reference it. + +**Restoring from a Revision**: +Reverting a document to a previous revision also restores the attachment state stored in that revision: + * Reverting to a revision that references a local attachmet restores the attachment as local. + * Reverting to a revision that references a remote attachment results in a document with a remote attachment. + + \ No newline at end of file diff --git a/docs/document-extensions/attachments/configure-remote-attachments.mdx b/docs/document-extensions/attachments/configure-remote-attachments.mdx new file mode 100644 index 0000000000..63962cf9d2 --- /dev/null +++ b/docs/document-extensions/attachments/configure-remote-attachments.mdx @@ -0,0 +1,18 @@ +--- +title: "Configure remote attachments" +sidebar_label: "Configure remote attachments" +sidebar_position: 1 +--- + +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + +import ConfigureRemoteAttachmentsCsharp from './content/_configure-remote-attachments-csharp.mdx'; + +export const supportedLanguages = ["csharp"]; + + + + + + \ No newline at end of file diff --git a/docs/document-extensions/attachments/content/_configure-remote-attachments-csharp.mdx b/docs/document-extensions/attachments/content/_configure-remote-attachments-csharp.mdx new file mode 100644 index 0000000000..ff9392f470 --- /dev/null +++ b/docs/document-extensions/attachments/content/_configure-remote-attachments-csharp.mdx @@ -0,0 +1,564 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; + + + +* Storing attachments in a remote storage destination is an optional feature that helps reduce the amount of data stored on the local disk + by moving the attachments content to external storage of your choice. + +* To store an attachment in a remote storage destination, you must: + * Enable the feature. + * Configure the remote destinations. + (Currently, Azure Blob Storage and Amazon S3 or any S3-compatible storage service are supported). + * Mark the attachment for remote storage, either when [storing](../../document-extensions/attachments/store-attachments/store-attachments-remote) + it or later using a [patch](../../document-extensions/attachments/store-attachments/store-attachments-remote#schedule-existing-attachments-for-remote-upload) operation. + +* When the Remote Attachments feature is enabled: + * RavenDB runs a **background task** that periodically scans the database for attachments marked for remote upload. + If the upload time for an attachment has passed when the task runs, + the task sends the attachment to the configured remote storage destinations. + * Uploading attachments to remote storage runs in the background and does not block other client operations. + +* Remote attachments are currently supported only in **non-sharded databases**. + Attempts to configure remote attachments on sharded databases will throw an exception. + +* In this article: + * [Configuring remote attachments settings - from the Studio](../../document-extensions/attachments/configure-remote-attachments#configuring-remote-attachments-settings-from-the-studio) + * [Configuring remote attachments settings - using the Client API](../../document-extensions/attachments/configure-remote-attachments#configuring-remote-attachments-settings-using-the-client-api) + * [Configure remote attachments settings](../../document-extensions/attachments/configure-remote-attachments#configure-remote-attachments-settings) + * [Get remote attachments settings](../../document-extensions/attachments/configure-remote-attachments#get-remote-attachments-settings) + * [Syntax](../../document-extensions/attachments/configure-remote-attachments#syntax) + + + + + +### Remote attachments settings + +![Remote attachments settings](../assets/configure-remote-attachments-1.png) + +1. Go to **"Settings > Remote Attachments"** + +2. **Enable remote attachments** + Toggle ON to enable storing attachments in a remote storage destination. + +3. **Set interval between remote attachments task runs** + Toggle ON to specify the delay (in seconds) between background task runs after the task completes the upload of attachments from the previous batch. + Default: `60` seconds. + +4. **Set max number of attachments to process in a single run** + Toggle ON to specify the maximum number of attachments processed in a single background task run. + Default: `unlimited`. + +5. **Set max number of concurrent uploads** + Toggle ON to define the maximum number of attachments that can be uploaded to remote storage concurrently. + Default: `8` attachments. + +6. **Add new** + Click the _Add New_ button to define a new remote storage destination. + +7. **Destinations** + All defined destinations will be listed in this section. + There is no limit to the number of destinations you can define. + +8. **Save** + Click the _Save_ button to save all your changes and apply the configuration. + +--- + +### Destinations + +![Remote attachments settings](../assets/configure-remote-attachments-2.png) + +1. **Destination Identifier** + The unique identifier string (_case-insensitive_) that you have assigned to this remote storage destination. +2. **Unsaved Changes (yellow star)** + A yellow star next to the destination's identifier indicates unsaved changes in its configuration. +3. **Remote Storage** + The remote storage provider configured for this destination. +4. **Destination Status** + When set to _Disabled_, the destination is ignored by the background task, + and no attachments are uploaded to it, even if their scheduled upload time has passed. +5. **Enable/Disable** + Click to toggle the destination's status between _Enabled_ and _Disabled_. +6. **Edit** + Click to modify the configuration settings of this destination. +7. **Delete** + Click to remove this destination from the configuration list. + +--- + +### Define destination - Amazon S3 storage + +![Remote attachments settings](../assets/configure-remote-attachments-3.png) + +1. **Provider** + Select **Amazon S3** as the remote storage provider. +2. **Use a custom S3 host** + Toggle ON to specify a custom S3-compatible host other than Amazon's official service. +3. **Force path style** + Toggle ON if your custom S3 host requires path-style access instead of virtual-hosted-style URLs. + For example: `https://{Server-URL}/{Bucket-Name}` instead of `https://{Bucket-Name}.{Server-URL}`. +4. **Custom server URL** + Enter the URL of the custom S3-compatible server (only required if using a custom S3 host). + The URL should include the protocol (`http://` or `https://`) and may include a port number. +5. **Destination identifier** + Enter a unique custom identifier (_case-insensitive_) for this Amazon S3 destination. + The background task will use this identifier to reference this destination configuration. +6. **Bucket name** + Specify the name of the S3 bucket where attachments will be stored. + Bucket names must follow AWS S3 naming rules: + * Must be globally unique across all AWS accounts. + * Must be between 3 and 63 characters long. + * Must consist only of lowercase letters, numbers, dots, and hyphens. + * Must begin and end with a letter or number. +7. **Remote folder name** (optional) + Define a folder within the bucket to organize uploaded attachments. + Leave blank to use the root of the bucket. +8. **Region** (optional) + Specify the AWS region where the S3 bucket is located (e.g., `us-east-1`). +9. **Access key** + Provide the AWS access key ID for this S3 bucket. +10. **Secret key** + Provide the corresponding AWS secret key for this S3 bucket. +11. **Storage class** + Choose an Amazon S3 storage class based on how often you access your data and its retention requirements. + For available options, see [S3StorageClass](../../document-extensions/attachments/configure-remote-attachments#s3storageclass). Default: `STANDARD`. +12. **Test credentials** + Click this option to verify whether the connection to the S3 storage works successfully. +13. **Apply configuration** + Click to add this destination to the destination list. + Note: Remember to click the _Save_ button to save all changes. + +--- + +### Define destination - Azure storage + +![Remote attachments settings](../assets/configure-remote-attachments-4.png) + +1. **Provider** + Select **Azure** as the remote storage provider. +2. **Destination identifier** + Enter a unique custom identifier (_case-insensitive_) for this Azure destination. + The background task will use this identifier to reference this destination configuration. +3. **Storage container** + Specify the name of the Azure Storage container where attachments will be stored. +4. **Remote folder name** (optional) + Define a folder within the storage container to organize uploaded attachments. + Leave blank to use the root of the container. +5. **Account name** + Enter the Azure Storage account name. +6. **Account key** + Provide the corresponding access key for the Azure Storage account. +7. **Test credentials** + Click to verify whether the connection to the Azure storage works successfully. +8. **Apply configuration** + Click to add this destination to the destination list. + Note: remember to click the _Save_ button to save all changes. + + + + + +### Configure remote attachments settings + +* Use the `ConfigureRemoteAttachmentsOperation` to configure remote attachment settings. + +* You can add multiple destinations as needed. + Ensure that each destination has a unique identifier for accurate reference. + +* Note: + Configuring remote attachment settings will **override** any existing configurations on the server. + If you need to add new destinations without losing existing ones, + make sure to [Get the current remote attachmets settings](../../document-extensions/attachments/configure-remote-attachments#get-remote-attachments-settings) first, + update it, and then send it back to the server. + +* The following example demonstrates how to configure both Amazon S3 and Azure destinations: + + + +```csharp +// Define remote storage settings: +// =============================== + +// Provide all necessary credentials and configuration for accessing your AMAZON S3 storage +var s3Settings = new RemoteAttachmentsS3Settings +{ + BucketName = "your-bucket-name", + AwsAccessKey = "your-amazon-access-key", + AwsSecretKey = "your-amazon-secret-key", + AwsRegionName = "your-region-name" // For example, "us-east-1" + RemoteFolderName = "your-s3-folder", // Optional +}; + +// Provide the necessary details for your AZURE Storage container +var azureSettings = new RemoteAttachmentsAzureSettings +{ + StorageContainer = "your-azure-container-name", + AccountName = "your-azure-account-name", + AccountKey = "your-azure-account-key", + RemoteFolderName = "your-azure-folder" // Optional +}; + +// Define the remote attachments configuration: +// ============================================ + +var configuration = new RemoteAttachmentsConfiguration +{ + // Add the remote storage destinations to the configuration + Destinations = new Dictionary + { + { + // Provide a custom identifier for this remote destination (case-insensitive) + "my-amazon-storage", + + new RemoteAttachmentsDestinationConfiguration + { + S3Settings = s3Settings, // The Amazon S3 settings + Disabled = false // Set to 'true' to disable this destination only + } + }, + { + // Provide a custom identifier for this remote destination + "my-azure-storage", + + new RemoteAttachmentsDestinationConfiguration + { + AzureSettings = azureSettings, // The Azure Storage settings + Disabled = false // Set to 'true' to disable this destination only + } + } + }, + + // OPTIONAL settings to control the remote attachments feature and its background task: + + // Set to 'true' to disable the entire remote attachments feature + Disabled = false, + // The time interval (in seconds) between background task runs + CheckFrequencyInSec = 600, + // The maximum number of attachments processed in a single background task run + MaxItemsToProcess = 25, + // The maximum number of attachments that can be uploaded to remote storage concurrently + ConcurrentUploads = 6 +}; + +// Apply the remote attachments configuration to the server: +// ========================================================= + +// This operation will override any existing remote attachment configurations on the server. +// Ensure all necessary destinations and settings are included in this configuration. +store.Maintenance.Send(new ConfigureRemoteAttachmentsOperation(configuration)); +``` + + +```csharp +// Define remote storage settings: +// =============================== + +// Provide all necessary credentials and configuration for accessing your AMAZON S3 storage +var s3Settings = new RemoteAttachmentsS3Settings +{ + BucketName = "your-bucket-name", + AwsAccessKey = "your-amazon-access-key", + AwsSecretKey = "your-amazon-secret-key", + AwsRegionName = "your-region-name" // For example, "us-east-1" + RemoteFolderName = "your-s3-folder", // Optional +}; + +// Provide the necessary details for your AZURE Storage container +var azureSettings = new RemoteAttachmentsAzureSettings +{ + StorageContainer = "your-azure-container-name", + AccountName = "your-azure-account-name", + AccountKey = "your-azure-account-key", + RemoteFolderName = "your-azure-folder" // Optional +}; + +// Define the remote attachments configuration: +// ============================================ + +var configuration = new RemoteAttachmentsConfiguration +{ + // Add the remote storage destinations to the configuration + Destinations = new Dictionary + { + { + // Provide a custom identifier for this remote destination (case-insensitive) + "my-amazon-storage", + + new RemoteAttachmentsDestinationConfiguration + { + S3Settings = s3Settings, // The Amazon S3 settings + Disabled = false // Set to 'true' to disable this destination only + } + }, + { + // Provide a custom identifier for this remote destination + "my-azure-storage", + + new RemoteAttachmentsDestinationConfiguration + { + AzureSettings = azureSettings, // The Azure Storage settings + Disabled = false // Set to 'true' to disable this destination only + } + } + }, + + // OPTIONAL settings to control the remote attachments feature and its background task: + + // Set to 'true' to disable the entire remote attachments feature + Disabled = false, + // The time interval (in seconds) between background task runs + CheckFrequencyInSec = 600, + // The maximum number of attachments processed in a single background task run + MaxItemsToProcess = 25, + // The maximum number of attachments that can be uploaded to remote storage concurrently + ConcurrentUploads = 6 +}; + +// Apply the remote attachments configuration to the server: +// ========================================================= + +// This operation will override any existing remote attachment configurations on the server. +// Ensure all necessary destinations and settings are included in this configuration. +await store.Maintenance.SendAsync(new ConfigureRemoteAttachmentsOperation(configuration)); +``` + + + +--- + +### Get remote attachments settings + +Use the `GetRemoteAttachmentsConfigurationOperation` to retrieve the current remote attachment settings. + + + +```csharp +// Get the current remote attachments settings +RemoteAttachmentsConfiguration configuration = store.Maintenance.Send( + new GetRemoteAttachmentsConfigurationOperation()); + +// Display current settings +if (configuration != null) +{ + // Time interval between task runs (seconds) + var checkFreqInSec = configuration000.CheckFrequencyInSec; + + // Number of concurrent uploads + var concurrentUploads = configuration000.ConcurrentUploads; + + foreach (var destination in configuration.Destinations) + { + // Destination identifier + var destinationId000 = destination.Key; + + if (destination.Value.S3Settings != null) + { + // S3 bucket name + var s3BucketName = destination.Value.S3Settings.BucketName; + } + if (destination.Value.AzureSettings != null) + { + // Azure container name + var azureContainer = destination.Value.AzureSettings.StorageContainer; + } + } +} +else +{ + // No remote attachments configuration found +} +``` + + +```csharp +// Get the current remote attachments settings asynchronously +RemoteAttachmentsConfiguration configuration = await store.Maintenance.SendAsync( + new GetRemoteAttachmentsConfigurationOperation()); + +// Display current settings +if (configuration != null) +{ + // Time interval between task runs (seconds) + var checkFreqInSec = configuration000.CheckFrequencyInSec; + + // Number of concurrent uploads + var concurrentUploads = configuration000.ConcurrentUploads; + + foreach (var destination in configuration.Destinations) + { + // Destination identifier + var destinationId000 = destination.Key; + + if (destination.Value.S3Settings != null) + { + // S3 bucket name + var s3BucketName = destination.Value.S3Settings.BucketName; + } + if (destination.Value.AzureSettings != null) + { + // Azure container name + var azureContainer = destination.Value.AzureSettings.StorageContainer; + } + } +} +else +{ + // No remote attachments configuration found +} +``` + + + + + + + +### `ConfigureRemoteAttachmentsOperation` + + +```csharp +// Configure the remote attachments settings using a store operation +public ConfigureRemoteAttachmentsOperation(RemoteAttachmentsConfiguration configuration) +``` + + +### `GetRemoteAttachmentsConfigurationOperation` + + +```csharp +// Get the current remote attachments settings using a store operation +public GetRemoteAttachmentsConfigurationOperation +``` + + +| Parameter | Type | Description | +|-------------------|----------------------------------|---------------------------------------------| +| **configuration** | `RemoteAttachmentsConfiguration` | The remote attachments configuration class. | + +--- + +#### `RemoteAttachmentsConfiguration` + + +```csharp +public class RemoteAttachmentsConfiguration +{ + // A dictionary of remote storage destinations, where each key is the destination identifier + public Dictionary Destinations { get; set; } + + // The time interval (in seconds) between background task runs + // Default: 60 seconds + public long? CheckFrequencyInSec { get; set; } + + // The maximum number of attachments processed in a single background task run + // Default: int.MaxValue (unlimited) + public long? MaxItemsToProcess { get; set; } + + // The maximum number of attachments that can be uploaded to remote storage concurrently + // Default: 8 attachments + public int? ConcurrentUploads { get; set; } + + // Set to 'true' to disable the entire remote attachments feature + // Default: false + public bool Disabled { get; set; } +} +``` + + +#### `RemoteAttachmentsDestinationConfiguration` + + +```csharp +public class RemoteAttachmentsDestinationConfiguration +{ + // Set to 'true' to disable this destination only + public bool Disabled { get; set; } + + // Amazon S3 settings for the remote storage destination + public RemoteAttachmentsS3Settings S3Settings { get; set; } + + // Azure Blob Storage settings for the remote storage destination + public RemoteAttachmentsAzureSettings AzureSettings { get; set; } +} +``` + + +#### `RemoteAttachmentsS3Settings` + + +```csharp +public class RemoteAttachmentsS3Settings +{ + public string AwsAccessKey { get; set; } + public string AwsSecretKey { get; set; } + public string AwsSessionToken { get; set; } + public string AwsRegionName { get; set; } + public string BucketName { get; set; } + public string RemoteFolderName { get; set; } + public string CustomServerUrl { get; set; } + public bool ForcePathStyle { get; set; } + public S3StorageClass? StorageClass { get; set; } +} +``` + + +#### `RemoteAttachmentsAzureSettings` + + +```csharp +public class RemoteAttachmentsAzureSettings +{ + public string StorageContainer { get; set; } + public string RemoteFolderName { get; set; } + public string AccountName { get; set; } + public string AccountKey { get; set; } + public string SasToken { get; set; } +} +``` + + +#### `S3StorageClass` + + +```csharp +public class S3StorageClass +{ + // Default class for frequently accessed data with high durability and low latency. + public string Standard { get; set; } + + For infrequently accessed, long-lived data; cheaper storage, retrieval fees apply. + public string StandardInfrequentAccess { get; set; } + + // Similar to Standard-IA but stored in one availability zone; + // less resilient to failures but more cost-efficient. + public string OneZoneInfrequentAccess { get; set; } + + // Automatically optimizes costs by moving data between access tiers. + public string IntelligentTiering { get; set; } + + // Low-cost storage for archives; fast retrieval with retrieval fees. + public string GlacierInstantRetrieval { get; set; } + + // Cheaper archival class; retrieval takes minutes to hours, + // suitable for data that doesn’t need immediate access. + public string Glacier { get; set; } + + // Lowest-cost storage for rarely accessed data; retrieval may take hours. + public string DeepArchive { get; set; } + + // Cheaper storage with lower durability, suitable for non-critical data. + public string ReducedRedundancy { get; set; } + + // Faster access to objects stored in only one availability zone. + public string ExpressOneZone { get; set; } +} +``` + + + \ No newline at end of file diff --git a/docs/document-extensions/attachments/content/_copy-move-rename-csharp.mdx b/docs/document-extensions/attachments/content/_copy-move-rename-csharp.mdx new file mode 100644 index 0000000000..55d2c3fcb9 --- /dev/null +++ b/docs/document-extensions/attachments/content/_copy-move-rename-csharp.mdx @@ -0,0 +1,264 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; + + + +* You can copy, move, or rename attachments using the _Session_'s methods. + These actions are executed on the server when calling `SaveChanges()`. + +* In this article: + * [Copy attachment to another document](../../document-extensions/attachments/copy-move-rename#copy-attachment-to-another-document) + * [Move attachment to another document](../../document-extensions/attachments/copy-move-rename#move-attachment-to-another-document) + * [Rename attachment](../../document-extensions/attachments/copy-move-rename#rename-attachment) + * [Syntax](../../document-extensions/attachments/copy-move-rename#syntax) + + + + + +* Use the session's `Advanced.Attachments.Copy` method to copy an attachment from one document to another. + You can assign any name to the copied attachment in the target document, it doesn't have to match the source name. + +* **Copying LOCAL attachments**: + The target document's metadata is updated to reference the same attachment. + The binary content is Not duplicated, it remains stored once in the database, + even if the copied attachment has a different name in the target document. + +* **Copying REMOTE attachments**: + The target document's metadata is updated to reference the existing attachment in remote storage. + The attachment is Not re-uploaded or duplicated in the remote destination, + even if the copied attachment has a different name in the target document. + +* An exception is thrown in any of the following cases: + * The source document does not exist. + * The destination document does not exist. + * The specified attachment is not found in the source document. + * An attachment with the same name already exists in the target document. + + + +```csharp +using (var session = store.OpenSession()) +{ + // Load source document + var employee1 = session.Load("employees/1"); + // Load target document + var employee2 = session.Load("employees/2"); + + // Copy the attachment from employee1 to employee2 + session.Advanced.Attachments.Copy(employee1, "notes.txt", employee2, "notes-copy.txt"); + + // Alternatively, use document IDs: + // session.Advanced.Attachments.Copy( + // "employees/1", "notes.txt", "employees/2", "notes-copy.txt"); + + // The attachment will be copied only when 'SaveChanges' is called + session.SaveChanges(); +} +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession()) +{ + // Load source document + var employee1 = await asyncSession.LoadAsync("employees/1"); + // Load target document + var employee2 = await asyncSession.LoadAsync("employees/2"); + + // Copy the attachment from employee1 to employee2 + asyncSession.Advanced.Attachments.Copy(employee1, "notes.txt", employee2, "notes-copy.txt"); + + // Alternatively, use document IDs: + // asyncSession.Advanced.Attachments.Copy( + // "employees/1", "notes.txt", "employees/2", "notes-copy.txt"); + + // The attachment will be copied only when 'SaveChangesAsync' is called + await asyncSession.SaveChangesAsync(); +} +``` + + + + + + + +* Use the session's `Advanced.Attachments.Move` method to move an attachment from one document to another. + You can assign a new name to the moved attachment in the target document, it doesn't have to match the source name. + +* Whether the attachment is stored locally or remotely, the reference is removed from the source document, + and the target document’s metadata is updated to reference it. + +* An exception is thrown in any of the following cases: + * The source document does not exist. + * The destination document does not exist. + * The specified attachment is not found in the source document. + * An attachment with the same name already exists in the target document. + + + +```csharp +using (var session = store.OpenSession()) +{ + // Load source document + var employee1 = session.Load("employees/1"); + // Load target document + var employee2 = session.Load("employees/2"); + + // Move the attachment from employee1 to employee2 + session.Advanced.Attachments.Move(employee1, "notes.txt", employee2, "notes.txt"); + + // Alternatively, use document IDs: + // session.Advanced.Attachments.Move( + // "employees/1", "notes.txt", "employees/2", "notes.txt"); + + // The attachment will be moved only when 'SaveChanges' is called + session.SaveChanges(); +} +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession()) +{ + // Load source document + var employee1 = await asyncSession.LoadAsync("employees/1"); + // Load target document + var employee2 = await asyncSession.LoadAsync("employees/2"); + + // Move the attachment from employee1 to employee2 + asyncSession.Advanced.Attachments.Move(employee1, "notes.txt", employee2, "notes.txt"); + + // Alternatively, use document IDs: + // asyncSession.Advanced.Attachments.Move( + // "employees/1", "notes.txt", "employees/2", "notes.txt"); + + // The attachment will be moved only when 'SaveChangesAsync' is called + await asyncSession.SaveChangesAsync(); +} +``` + + + + + + + +* Use the session's `Advanced.Attachments.Rename` method to rename an existing attachment in a document. + You can assign any new name - the attachment content remains unchanged. + +* Renaming an attachment only affects the document’s metadata. + The actual content is stored once using the hash of its binary data, + so neither local nor remote storage is affected by the name change. + +* An exception is thrown in any of the following cases: + * The document does not exist. + * The specified attachment is not found in the document. + * An attachment with the new name already exists in the document. + + + +```csharp +using (var session = store.OpenSession()) +{ + // Load a document + var employee1 = session.Load("employees/1"); + + // Rename the attachment + session.Advanced.Attachments.Rename( + employee1, name: "notes.txt", newName: "notes-new-name.txt"); + + // Alternatively, use document ID: + // session.Advanced.Attachments.Rename( + // "employees/1", name: "notes.txt", newName: "notes-new-name.txt"); + + // The attachment will be renamed only when 'SaveChanges' is called + session.SaveChanges(); +} +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession()) +{ + // Load a document + var employee1 = await asyncSession.LoadAsync("employees/1"); + + // Rename the attachment + asyncSession.Advanced.Attachments.Rename( + employee1, name: "notes.txt", newName: "notes-new-name.txt"); + + // Alternatively, use document ID: + // asyncSession.Advanced.Attachments.Rename( + // "employees/1", name: "notes.txt", newName: "notes-new-name.txt"); + + // The attachment will be renamed only when 'SaveChangesAsync' is called + await asyncSession.SaveChangesAsync(); +} +``` + + + + + + + +#### Copy attachment + + +```csharp +// Available overloads: +// ==================== +void Copy( + object sourceEntity, string sourceName, object destinationEntity, string destinationName); +void Copy( + string sourceDocumentId, string sourceName, string destinationDocumentId, string destinationName); +``` + + +#### Move attachment + + +```csharp +// Available overloads: +// ==================== +void Move( + object sourceEntity, string sourceName, object destinationEntity, string destinationName); +void Move( + string sourceDocumentId, string sourceName, string destinationDocumentId, string destinationName); +``` + + +| Parameter | Type | Description | +|---------------------------|----------|-------------| +| **sourceEntity** | `object` | The source document entity that holds the attachment. | +| **sourceDocumentId** | `string` | The source document ID that holds the attachment. | +| **sourceName** | `string` | The attachment name in the source document. | +| **destinationEntity** | `object` | The target document entity. | +| **destinationDocumentId** | `string` | The target document ID. | +| **destinationName** | `string` | The name to assign to the attachment in the target document. | + +--- + +#### Rename attachment + + +```csharp +void Rename(object entity, string name, string newName); +void Rename(string documentId, string name, string newName); +``` + + +| Parameter | Type | Description | +|----------------|----------|-------------| +| **entity** | `object` | The document entity that holds the attachment. | +| **documentId** | `string` | The document ID that holds the attachment. | +| **name** | `string` | The current name of the attachment. | +| **newName** | `string` | The new name to assign to the attachment. | + + \ No newline at end of file diff --git a/docs/document-extensions/attachments/_copying-moving-renaming-java.mdx b/docs/document-extensions/attachments/content/_copy-move-rename-java.mdx similarity index 96% rename from docs/document-extensions/attachments/_copying-moving-renaming-java.mdx rename to docs/document-extensions/attachments/content/_copy-move-rename-java.mdx index bb9db7b11d..246f259103 100644 --- a/docs/document-extensions/attachments/_copying-moving-renaming-java.mdx +++ b/docs/document-extensions/attachments/content/_copy-move-rename-java.mdx @@ -2,6 +2,8 @@ import Admonition from '@theme/Admonition'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; Attachments can be copied, moved, or renamed using built-in session methods. All of those actions are executed when `saveChanges` is called and take place on the server-side, diff --git a/docs/document-extensions/attachments/_copying-moving-renaming-nodejs.mdx b/docs/document-extensions/attachments/content/_copy-move-rename-nodejs.mdx similarity index 94% rename from docs/document-extensions/attachments/_copying-moving-renaming-nodejs.mdx rename to docs/document-extensions/attachments/content/_copy-move-rename-nodejs.mdx index b3b0605659..65e669dabf 100644 --- a/docs/document-extensions/attachments/_copying-moving-renaming-nodejs.mdx +++ b/docs/document-extensions/attachments/content/_copy-move-rename-nodejs.mdx @@ -2,6 +2,8 @@ import Admonition from '@theme/Admonition'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; @@ -11,12 +13,13 @@ import CodeBlock from '@theme/CodeBlock'; removing the need to transfer the entire attachment binary data over the network in order to perform the action. * In this page: - * [Copy attachment](../../document-extensions/attachments/copying-moving-renaming.mdx#copy-attachment) - * [Move attachment](../../document-extensions/attachments/copying-moving-renaming.mdx#move-attachment) - * [Rename attachment](../../document-extensions/attachments/copying-moving-renaming.mdx#rename-attachment) - * [Syntax](../../document-extensions/attachments/copying-moving-renaming.mdx#syntax) + * [Copy attachment](../../document-extensions/attachments/copy-move-rename#copy-attachment) + * [Move attachment](../../document-extensions/attachments/copy-move-rename#move-attachment) + * [Rename attachment](../../document-extensions/attachments/copy-move-rename#rename-attachment) + * [Syntax](../../document-extensions/attachments/copy-move-rename#syntax) + ## Copy attachment Use `session.advanced.attachments.copy` to copy an attachment from one document to another. @@ -50,8 +53,6 @@ await session.saveChanges(); - - ## Move attachment Use `session.advanced.attachments.move` to move an attachment from one document to another. @@ -85,8 +86,6 @@ await session.saveChanges(); - - ## Rename attachment Use `session.advanced.attachments.rename` to rename an attachment. @@ -119,8 +118,6 @@ await session.saveChanges(); - - ## Syntax @@ -135,8 +132,8 @@ copy(sourceDocumentId, sourceName, destinationDocumentId, destinationName); -{`// Move - a vailable overloads: -// ============================ +{`// Move - available overloads: +// ============================= move(sourceEntity, sourceName, destinationEntity, destinationName); move(sourceDocumentId, sourceName, destinationDocumentId, destinationName); `} @@ -156,7 +153,7 @@ move(sourceDocumentId, sourceName, destinationDocumentId, destinationName); {`// Rename - available overloads: -// ============================= +// ============================== rename(entity, name, newName); rename(documentId, name, newName); `} diff --git a/docs/document-extensions/attachments/_copying-moving-renaming-php.mdx b/docs/document-extensions/attachments/content/_copy-move-rename-php.mdx similarity index 95% rename from docs/document-extensions/attachments/_copying-moving-renaming-php.mdx rename to docs/document-extensions/attachments/content/_copy-move-rename-php.mdx index 5003ed3b6b..71a7295a2c 100644 --- a/docs/document-extensions/attachments/_copying-moving-renaming-php.mdx +++ b/docs/document-extensions/attachments/content/_copy-move-rename-php.mdx @@ -2,6 +2,8 @@ import Admonition from '@theme/Admonition'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; Attachments can be copied, moved, or renamed using built-in session methods. All of those actions are executed when `saveChanges` is called and take place on the server-side, diff --git a/docs/document-extensions/attachments/_copying-moving-renaming-python.mdx b/docs/document-extensions/attachments/content/_copy-move-rename-python.mdx similarity index 95% rename from docs/document-extensions/attachments/_copying-moving-renaming-python.mdx rename to docs/document-extensions/attachments/content/_copy-move-rename-python.mdx index c73d0c43e4..207788ea63 100644 --- a/docs/document-extensions/attachments/_copying-moving-renaming-python.mdx +++ b/docs/document-extensions/attachments/content/_copy-move-rename-python.mdx @@ -2,6 +2,8 @@ import Admonition from '@theme/Admonition'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; Attachments can be copied, moved, or renamed using built-in session methods. All of those actions are executed when `save_changes` is called and take place on the server-side, diff --git a/docs/document-extensions/attachments/content/_delete-attachment-csharp.mdx b/docs/document-extensions/attachments/content/_delete-attachment-csharp.mdx new file mode 100644 index 0000000000..ca8492fef7 --- /dev/null +++ b/docs/document-extensions/attachments/content/_delete-attachment-csharp.mdx @@ -0,0 +1,152 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; + + + +* This article explains how to delete attachments from documents in RavenDB. + +* **Deleting a LOCAL attachment**: + * The reference to the attachment is removed from the document’s metadata. + * The binary content is deleted from local storage only if no other documents in the database reference the exact same content. + +* **Deleting a REMOTE attachment**: + * Only the reference to the remote attachment is removed from the document’s metadata. + * RavenDB does not delete the binary data from the remote storage. + It is your responsibility to remove the file from the cloud provider manually. + +* **When deleting a DOCUMENT**: + * All local attachments referenced by the document are also deleted, + (as long as no other document references the same binary content). + * Remote attachments are not deleted from the cloud provider - you must remove them manually. + +* In this article: + * [Delete attachment using the **Studio**](../../document-extensions/attachments/delete-attachment#delete-attachment-using-the-studio) + * [Delete attachment using the **Client API**](../../document-extensions/attachments/delete-attachment#delete-attachment-using-the-client-api) + * [Delete attachment - via the session](../../document-extensions/attachments/delete-attachment#delete-attachment---via-the-session) + * [Delete attachment - via an operation](../../document-extensions/attachments/delete-attachment#delete-attachment---via-an-operation) + * [Syntax](../../document-extensions/attachments/delete-attachment#syntax) + + + + + +In the Studio, open the document view. +To delete an attachment from the document, click the trash can icon next to the attachment name. + +![Delete attachment](../assets/delete-attachment.png) + + + + + +### Delete attachment - via the session + + + +```csharp +using (var session = store.OpenSession()) +{ + Employee employee = session.Load("employees/1"); + + // Mark the attachment for deletion: + // Provide the document entity and the attachment name + session.Advanced.Attachments.Delete(employee, "image1.png"); + + // Or - provide the document ID and the attachment name + // session.Advanced.Attachments.Delete("employees/1", "image1.png"); + + // The attachment is deleted when 'SaveChanges" is called + session.SaveChanges(); +} +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession()) +{ + Employee employee = await asyncSession.LoadAsync("employees/1"); + + // Mark the attachment for deletion: + // Provide the document entity and the attachment name + asyncSession.Advanced.Attachments.Delete(employee, "image1.png"); + + // Or - provide the document ID and the attachment name + // asyncSession.Advanced.Attachments.Delete("employees/1", "image1.png"); + + // The attachment is deleted when 'SaveChangesAsync' is called + await asyncSession.SaveChangesAsync(); +} +``` + + + +--- + +### Delete attachment - via an operation + + + +```csharp +// Define the delete attachment operation +// Specify the document ID and the attachment name +var deleteAttachmentOp = new DeleteAttachmentOperation("employees/1", "image1.png", null); + +// Execute the operation by passing it to 'Operations.Send' +store.Operations.Send(deleteAttachmentOp); +``` + + +```csharp +// Define the delete attachment operation +// Specify the document ID and the attachment name +var deleteAttachmentOp = new DeleteAttachmentOperation("employees/1", "image1.png", null); + +// Execute the operation asynchronously by passing it to 'Operations.SendAsync' +await store.Operations.SendAsync(deleteAttachmentOp); +``` + + + + + + + +#### Delete attachment via operation + + +```csharp +public DeleteAttachmentOperation(string documentId, string name, string changeVector = null) +``` + + +| Parameter | Type | Description | +|------------------|----------|-------------| +| **documentId** | `string` | The ID of the document from which to delete the attachment. | +| **name** | `string` | The name of the attachment to delete. | +| **changeVector** | `string` | The document's change vector used for concurrency control.
If set to `null`, no concurrency check will be performed and the attachment will be deleted regardless of the document's current state. | + +--- + +#### Delete attachment via session + + +```csharp +// Avialable overloads: +// ==================== + +void Delete(object entity, string name); +void Delete(string documentId, string name); +``` + + +| Parameter | Type | Description | +|----------------|----------|-------------| +| **documentId** | `string` | The ID of the document from which to delete the attachment. | +| **entity** | `string` | The document entity from which to delete the attachment. | +| **name** | `string` | The name of the attachment to delete. | + +
\ No newline at end of file diff --git a/docs/document-extensions/attachments/content/_delete-attachment-java.mdx b/docs/document-extensions/attachments/content/_delete-attachment-java.mdx new file mode 100644 index 0000000000..85d6e6e870 --- /dev/null +++ b/docs/document-extensions/attachments/content/_delete-attachment-java.mdx @@ -0,0 +1,83 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; + + + +* This article explains how to delete attachments from a document. + +* In this article + * [Delete attachment via the session](../../document-extensions/attachments/delete-attachment#get-attachment-via-the-session) + * [Delete attachment via an operation](../../document-extensions/attachments/delete-attachment#get-attachment-via-an-operation) + + + + + +**Delete** from `session.advanced().attachments()` is used to remove an attachment from a document. + +### Syntax + + + +{`void delete(String documentId, String name); + +void delete(Object entity, String name); +`} + + + +### Example + + + +{`try (IDocumentSession session = store.openSession()) \{ + Album album = session.load(Album.class, "albums/1"); + session.advanced().attachments().delete(album, "001.jpg"); + session.advanced().attachments().delete("albums/1", "002.jpg"); + + session.saveChanges(); +\} +`} + + + + + + + +This operation is used to delete an attachment from a document. + +### Syntax + + + +{`DeleteAttachmentOperation(String documentId, String name) + +DeleteAttachmentOperation(String documentId, String name, String changeVector) +`} + + + +| Parameter | | | +|------------------|--------|-------------------------------------------------------------------------| +| **documentId** | String | ID of a document containing an attachment | +| **name** | String | Name of an attachment | +| **changeVector** | String | Entity changeVector, used for concurrency checks (`null` to skip check) | + +### Example + + + +{`store.operations().send( + new DeleteAttachmentOperation("orders/1-A", "invoice.pdf")); +`} + + + + + + diff --git a/docs/document-extensions/attachments/content/_delete-attachment-nodejs.mdx b/docs/document-extensions/attachments/content/_delete-attachment-nodejs.mdx new file mode 100644 index 0000000000..0fe21ab908 --- /dev/null +++ b/docs/document-extensions/attachments/content/_delete-attachment-nodejs.mdx @@ -0,0 +1,84 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; + + + +* This article explains how to delete attachments from a document. + +* In this article + * [Delete attachment via the session](../../document-extensions/attachments/delete-attachment#get-attachment-via-the-session) + * [Delete attachment via an operation](../../document-extensions/attachments/delete-attachment#get-attachment-via-an-operation) + + + + + +The method `session.advanced.attachments.delete()` is used to remove an attachment from a document. + +### Syntax + + + +{`session.advanced.attachments.delete(documentId, name); + +session.advanced.attachments.delete(entity, name); +`} + + + +### Example + + + +{`const session = store.openSession(); +const album = await session.load("albums/1"); +session.advanced.attachments.delete(album, "001.jpg"); + +session.advanced.attachments.delete("albums/1", "002.jpg"); + +await session.saveChanges(); +`} + + + + + + + +### Delete attachment example + + + +{`// Define the delete attachment operation +const deleteAttachmentOp = new DeleteAttachmentOperation("employees/1-A", "photo.jpg"); + +// Execute the operation by passing it to operations.send +await documentStore.operations.send(deleteAttachmentOp); +`} + + + +### Syntax + + + +{`// Available overloads: +const deleteAttachmentOp = new DeleteAttachmentOperation(documentId, name); +const deleteAttachmentOp = new DeleteAttachmentOperation(documentId, name, changeVector); +`} + + + +| Parameter | Type | Description | +|------------------|----------|------------------------------------------------------------------------------------| +| **documentId** | `string` | ID of document from which attachment will be removed | +| **name** | `string` | Name of attachment to delete | +| **changeVector** | `string` | ChangeVector of attachment,
used for concurrency checks (`null` to skip check) | + +
+ + diff --git a/docs/document-extensions/attachments/_deleting-php.mdx b/docs/document-extensions/attachments/content/_delete-attachment-php.mdx similarity index 100% rename from docs/document-extensions/attachments/_deleting-php.mdx rename to docs/document-extensions/attachments/content/_delete-attachment-php.mdx diff --git a/docs/document-extensions/attachments/_deleting-python.mdx b/docs/document-extensions/attachments/content/_delete-attachment-python.mdx similarity index 100% rename from docs/document-extensions/attachments/_deleting-python.mdx rename to docs/document-extensions/attachments/content/_delete-attachment-python.mdx diff --git a/docs/document-extensions/attachments/content/_get-attachments-csharp.mdx b/docs/document-extensions/attachments/content/_get-attachments-csharp.mdx new file mode 100644 index 0000000000..a1bdcb5f58 --- /dev/null +++ b/docs/document-extensions/attachments/content/_get-attachments-csharp.mdx @@ -0,0 +1,722 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; + + + +* This article explains how to retrieve attachments that are already stored in your database, + whether they are **local** or **remote**. + Retrieval is transparent, the same code works for both storage types. + +* You can: + * Get a single attachment or multiple attachments. + * Get part of an attachment (local attachments only). + * Retrieve only the attachment names, or check whether a specific attachment exists. + +* If a document's metadata references an attachment, either in local or remote storage, but the file no longer exists + (e.g., was deleted manually), then attempting to retrieve the attachment will throw a _FileNotFoundException_. + +* Note: + **Remote attachments** are Not cached locally after they are downloaded. + Each retrieval will fetch the data again from the remote storage. + +* In this article: + * [Get attachments using the **Studio**](../../document-extensions/attachments/get-attachments#get-attachments-using-the-studio) + * [Get attachments using the **Client API**](../../document-extensions/attachments/get-attachments#get-attachments-using-the-client-api) + * [Get attachment details (excluding content)](../../document-extensions/attachments/get-attachments#get-attachment-details-excluding-content) + * [Get single attachment from document](../../document-extensions/attachments/get-attachments#get-single-attachment-from-document) + * [Get single attachment from revision](../../document-extensions/attachments/get-attachments#get-single-attachment-from-revision) + * [Get multiple attachments](../../document-extensions/attachments/get-attachments#get-multiple-attachments) + * [Get partial content](../../document-extensions/attachments/get-attachments#get-partial-content) + * [Check if an attachment exists](../../document-extensions/attachments/get-attachments#check-if-an-attachment-exists) + * [Syntax](../../document-extensions/attachments/get-attachments#syntax) + + + + + +In the Studio, open the document view. To download an attachment, simply click its name. +The file, whether stored locally or in remote storage, will be downloaded to your browser’s default download location. + +![Download attachments](../assets/get-attachment.png) + + + +## Get attachments using the Client API + + + +* Use the session's `GetNames()` method to retrieve the attachment names and related details for a given document, excluding the attachment content. + Returned entries include both **local** and **remote** attachments. + +* To get the attachment content stream as well, see the other sections below, + e.g. [Get single attachment](../../document-extensions/attachments/get-attachments#get-single-attachment-from-document). + + + +```csharp +using (var session = store.OpenSession()) +{ + // Load the document entity + Employee employee = session.Load("employees/1"); + + // Call 'GetNames', pass the document entity + AttachmentName[] attachmentNames = session.Advanced.Attachments.GetNames(employee); + + // Iterate over results + foreach (AttachmentName attachmentName in attachmentNames) + { + // The attachment name: + string name = attachmentName.Name; + + // Access details: + string contentType = attachmentName.ContentType; + string hash = attachmentName.Hash; + long size = attachmentName.Size; + var remoteParams = attachmentName.RemoteParameters; + + // If the attachment is stored locally, RemoteParameters will be null + if (remoteParams != null) + { + // Remote attachment info: + string identifier = remoteParams.Identifier; + DateTime at = remoteParams.At; + } + } +} +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession()) +{ + // Load the document entity + Employee employee = await asyncSession.LoadAsync("employees/1"); + + // Call 'GetNames', pass the document entity + AttachmentName[] attachmentNames = session.Advanced.Attachments.GetNames(employee); + + // Iterate over results + foreach (AttachmentName attachmentName in attachmentNames) + { + // The attachment name: + string name = attachmentName.Name; + + // Access details: + string contentType = attachmentName.ContentType; + string hash = attachmentName.Hash; + long size = attachmentName.Size; + var remoteParams = attachmentName.RemoteParameters; + + // If the attachment is stored locally, RemoteParameters will be null + if (remoteParams != null) + { + // Remote attachment info: + string identifier = remoteParams.Identifier; + DateTime at = remoteParams.At; + } + } +} +``` + + + + + + + +### Get attachment - using the session + +* Use the session's `Get()` method to retrieve a single attachment by name, along with its content stream and related details. + You can pass either the document entity or the document ID. + +* The method seamlessly retrieves attachments from both **local** and **remote** storage. + + + +```csharp +using (var session = store.OpenSession()) +{ + // Option 1: Load the document entity + Employee employee = session.Load("employees/1"); + // Call 'Get', pass the document entity and the name of the attachment to retrieve + AttachmentResult attachment = session.Advanced.Attachments.Get(employee, "notes.txt"); + + // Option 2: Call 'Get' and pass the document ID directly + // AttachmentResult attachment = + // session.Advanced.Attachments.Get("employees/1", "notes.txt"); + + if (attachment != null) + { + // Access the content stream: + var stream = attachment.Stream; + + // Optionally, decode the stream to a string (for text-based attachments) + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms); + string decodedContent = Encoding.UTF8.GetString(ms.ToArray()); + } + + // Access attachment details: + var details = attachment.Details; + + string name = details.Name; + string hash = details.Hash; + long size = details.Size; + string contentType = details.ContentType; + string documentId = details.DocumentId; + string changeVector = details.ChangeVector; + var remoteParams = details.RemoteParameters; + + // If the attachment is stored locally, remoteParams will be null + if (remoteParams != null) + { + // Remote attachment info: + string identifier = remoteParams.Identifier; + DateTime at = remoteParams.At; + } + } +} +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession()) +{ + // Option 1: Load the document entity + Employee employee = await asyncSession.LoadAsync("employees/1"); + // Call 'GetAsync', pass the document entity and the name of the attachment to retrieve + AttachmentResult attachment = + await asyncSession.Advanced.Attachments.GetAsync(employee, "notes.txt"); + + // Option 2: Pass the document ID directly + // AttachmentResult attachment = + // await asyncSession.Advanced.Attachments.GetAsync("employees/1", "notes.txt"); + + if (attachment != null) + { + // Access the content stream: + var stream = attachment.Stream; + + // Optionally, decode the stream to a string (for text-based attachments) + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms); + string decodedContent = Encoding.UTF8.GetString(ms.ToArray()); + } + + // Access attachment details: + var details = attachment.Details; + + string name = details.Name; + string hash = details.Hash; + long size = details.Size; + string contentType = details.ContentType; + string documentId = details.DocumentId; + string changeVector = details.ChangeVector; + var remoteParams = details.RemoteParameters; + + // If the attachment is stored locally, remoteParams will be null + if (remoteParams != null) + { + // Remote attachment info: + string identifier = remoteParams.Identifier; + DateTime at = remoteParams.At; + } + } +} +``` + + + +--- + +### Get attachment - using an operation + +* Use `GetAttachmentOperation` to retrieve a single attachment outside of the session context. + The result includes both the content stream and related attachment details. + +* The operation seamlessly retrieves attachments from both **local** and **remote** storage. + + + +```csharp +// Define the get attachment operation +// Specify the document ID and the attachment name. +// Use 'AttachmentType.Document' to indicate that the attachment is from the document itself +// (not from a revision). +var getAttachmentOp = new GetAttachmentOperation("employees/1", "notes.txt", AttachmentType.Document, null); + +// Execute the operation by passing it to 'Operations.Send' +AttachmentResult attachment = store.Operations.Send(getAttachmentOp); + +if (attachment != null) +{ + // The result includes both the content stream and attachment details + var stream = attachment.Stream; + var details = attachment.Details; +} +``` + + +```csharp +// Define the get attachment operation +// Specify the document ID and the attachment name. +// Use 'AttachmentType.Document' to indicate that the attachment is from the document itself +// (not from a revision). +var getAttachmentOp = new GetAttachmentOperation("employees/1", "notes.txt", AttachmentType.Document, null); + +// Execute the operation by passing it to 'Operations.SendAsync' +AttachmentResult attachment = await store.Operations.SendAsync(getAttachmentOp); + +if (attachment != null) +{ + // The result includes both the content stream and attachment details + var stream = attachment.Stream; + var details = attachment.Details; +} +``` + + + + + + + +### Get attachment from a revision - using the session + +* Use the session’s `GetRevision()` method to retrieve a single attachment from a specific document revision. + The result includes both the content stream and related attachment details. + +* The method seamlessly retrieves attachments from both **local** and **remote** storage. + + + +```csharp +using (var session = store.OpenSession()) +{ + // Call 'GetRevision' + // Pass the document ID, the attachment name and the revision change vector + AttachmentResult attachment = session.Advanced.Attachments.GetRevision( + "employees/1", "notes.txt", changeVector: "A:8-T9HdPKqrYUism8P0z7GA1A"); + + if (attachment != null) + { + // Access the content stream: + var stream = attachment.Stream; + + // Access attachment details: + var details = attachment.Details; + } +} +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession()) +{ + // Call 'GetRevision' + // Pass the document ID, the attachment name and the revision change vector + AttachmentResult attachment = await asyncSession.Advanced.Attachments.GetRevisionAsync( + "employees/1", "notes.txt", changeVector: "A:8-T9HdPKqrYUism8P0z7GA1A"); + + if (attachment != null) + { + // Access the content stream: + var stream = attachment.Stream; + + // Access attachment details: + var details = attachment.Details; + } +} +``` + + + +--- + +### Get attachment from a revision - using an operation + +* Use `GetAttachmentOperation` to retrieve a single attachment from a document revision outside of the session context. + The result includes both the content stream and related attachment details. + +* The operation seamlessly retrieves attachments from both **local** and **remote** storage. + + + +```csharp +// Define the get attachment operation +// Specify the document ID and the attachment name. +// Use 'AttachmentType.Revision' to indicate that the attachment is from a revision +// and provide the revision change vector. +var getAttachmentOp = new GetAttachmentOperation("employees/1", "notes.txt", + AttachmentType.Revision, changeVector: "A:8-T9HdPKqrYUism8P0z7GA1A"); + +// Execute the operation by passing it to 'Operations.Send' +AttachmentResult attachment = store.Operations.Send(getAttachmentOp); + +if (attachment != null) +{ + var stream = attachment.Stream; + var details = attachment.Details; +} +``` + + +```csharp +// Define the get attachment operation +// Specify the document ID and the attachment name. +// Use 'AttachmentType.Revision' to indicate that the attachment is from a revision +// and provide the revision change vector. +var getAttachmentOp = new GetAttachmentOperation("employees/1", "notes.txt", + AttachmentType.Revision, changeVector: "A:8-T9HdPKqrYUism8P0z7GA1A"); + +// Execute the operation by passing it to 'Operations.SendAsync' +AttachmentResult attachment = await store.Operations.SendAsync(getAttachmentOp); + +if (attachment != null) +{ + var stream = attachment.Stream; + var details = attachment.Details; +} +``` + + + + + + + +* To retrieve multiple attachments in one call, first get the attachment names, + then build a list of `AttachmentRequest` objects and pass them to the session’s `Get()` method. + +* The method returns attachments from both **local** and **remote** storage. + + + +```csharp +using (var session = store.OpenSession()) +{ + // Load the document + var emp = session.Load("employees/1"); + + // Get the list of attachment names for the document + var attNames = session.Advanced.Attachments.GetNames(emp); + + // Build a list of 'AttachmentRequest' objects (document ID + attachment name) + IEnumerable attList = attNames.Select(x => + new AttachmentRequest("employees/1", x.Name)); + + // Retrieve multiple attachments in a single call + IEnumerator att = session.Advanced.Attachments.Get(attList); + + // Iterate through the results to access both content and details + while (att.MoveNext()) + { + AttachmentEnumeratorResult res = att.Current; + + // Attachment metadata (name, content type, size, hash, etc.) + AttachmentDetails attachmentDetails = res.Details; + + // Attachment content stream + Stream attachmentStream = res.Stream; + } +} +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession()) +{ + // Load the document + var emp = await asyncSession.LoadAsync("employees/1"); + + // Get the list of attachment names for the document + var attNames = asyncSession.Advanced.Attachments.GetNames(emp); + + // Build a list of AttachmentRequest objects (document ID + attachment name) + IEnumerable attList = attNames.Select(x => + new AttachmentRequest("employees/1", x.Name)); + + // Retrieve multiple attachments asynchronously + IEnumerator att = + await asyncSession.Advanced.Attachments.GetAsync(attList); + + // Iterate through the results to access both content and details + while (att.MoveNext()) + { + AttachmentEnumeratorResult res = att.Current; + + // Attachment metadata (name, content type, size, hash, etc.) + AttachmentDetails attachmentDetails = res.Details; + + // Attachment content stream + Stream attachmentStream = res.Stream; + } +} +``` + + + + + + + +* Use `GetRange()` to retrieve a specific byte range of an attachment stream. + +* Getting a partial stream is supported for **local attachments only**. + It is NOT supported for remote attachments. + +* An _InvalidAttachmentRangeException_ will be thrown if the requested range is invalid for the file. + +--- + +### Get partial content - using the session + + + +```csharp +using (var session = store.OpenSession()) +{ + // Option 1: Load the document entity + Employee employee = session.Load("employees/1"); + + // Retrieve a part of the attachment (e.g. bytes 100-200) + AttachmentResult attachmentPartial = session.Advanced.Attachments.GetRange( + entity: employee, name: "notes.txt", from: 100, to: 200); + + // Option 2: Call 'GetRange', pass the document ID (instead of the entity) + // AttachmentResult attachment = + // AttachmentResult attachmentPartial = session.Advanced.Attachments.GetRange( + // documentId: "employees/1", name: "notes.txt", from: 100, to: 200); + + if (attachmentPartial != null) + { + // Access the content stream: + // Only the requested byte range is available in the stream + var stream = attachmentPartial.Stream; + + // Access attachment details: + var details = attachmentPartial.Details; + } +} +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession()) +{ + // Option 1: Load the document entity + Employee employee = await asyncSession.LoadAsync("employees/1"); + + // Retrieve a part of the attachment (e.g. bytes 100-200) + AttachmentResult attachmentPartial = await asyncSession.Advanced.Attachments.GetRangeAsync( + entity: employee, name: "notes.txt", from: 100, to: 200); + + // Option 2: Call 'GetRangeAsync', pass the document ID (instead of the entity) + // AttachmentResult attachment = + // await asyncSession.Advanced.Attachments.GetRangeAsync( + // documentId: "employees/1", name: "notes.txt", from: 100, to: 200); + + if (attachmentPartial != null) + { + // Access the content stream: + // Only the requested byte range is available in the stream + var stream = attachmentPartial.Stream; + + // Access attachment details: + var details = attachmentPartial.Details; + } +} +``` + + + +--- + +### Get partial content - using an operation + + + +```csharp +// Define the get attachment operation +// Specify 'from' and 'to' to retrieve a part of the attachment (e.g. bytes 100-200) +var getAttachmentOp = new GetAttachmentOperation(documentId: "employees/1", + name: "notes - Copy.txt", type: AttachmentType.Document, changeVector: null, + from: 100, to: 200); + +// Execute the operation by passing it to 'Operations.Send' +AttachmentResult attachment = store.Operations.Send(getAttachmentOp); + +if (attachment != null) +{ + // Only the requested byte range is available in the stream + var stream = attachment.Stream; + var details = attachment.Details; +} +``` + + +```csharp +// Define the get attachment operation +// Specify 'from' and 'to' to retrieve a part of the attachment (e.g. bytes 100-200) +var getAttachmentOp = new GetAttachmentOperation(documentId: "employees/1", + name: "notes - Copy.txt", type: AttachmentType.Document, changeVector: null, + from: 100, to: 200); + +// Execute the operation by passing it to 'Operations.SendAsync' +AttachmentResult attachment = await store.Operations.SendAsync(getAttachmentOp); + +if (attachment != null) +{ + // Only the requested byte range is available in the stream + var stream = attachment.Stream; + var details = attachment.Details; +} +``` + + + + + + + +* Use the session’s `Exists()` method to check whether a specific attachment is listed in a document’s metadata. + This works for both local and remote attachments. + +* The method checks **only the document’s metadata**. + It does Not verify whether the binary content actually exists in local or remote storage. + + + +```csharp +using (var session = store.OpenSession()) +{ + // Check if the document metadata references an attachment named "notes.txt" + // Call method 'Exists', pass the document ID + bool exists = session.Advanced.Attachments.Exists("employees/1", "notes.txt"); +} +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession()) +{ + // Check if the document metadata references an attachment named "notes.txt" + // Call method 'Exists', pass the document ID + bool exists = await asyncSession.Advanced.Attachments.ExistsAsync( + "employees/1", "notes.txt"); +} +``` + + + + + + + +### Get attachment via operation + + +```csharp +public GetAttachmentOperation(string documentId, + string name, AttachmentType type, string changeVector, long? from, long? to) +``` + + +| Parameter | Type | Description | +|----------------------|------------------------------|-------------| +| **documentId** | `string` | The ID of the document from which to retrieve the attachment. | +| **name** | `string` | The name of the attachment to retrieve. | +| **type** | `AttachmentType` | Specify whether the attachment is from the current document or a document revision. | +| **changeVector** | `string` | The change vector of the revision to retrieve the attachment from. Use `null` when retrieving from the current document. | +| **from** | `long` | (Optional) The starting byte position (inclusive) for partial content retrieval. | +| **to** | `long` | (Optional) The ending byte position (inclusive) for partial content retrieval. | + +| Return value | Description | +|--------------------|-------------| +| [AttachmentResult](../../document-extensions/attachments/get-attachments#attachmentresult) | The retrieved attachment | + +--- + +### Get attachment via session + + +```csharp +// Avialable overloads: +// ==================== + +bool Exists(string documentId, string name); +Task ExistsAsync(string documentId, string name, CancellationToken token = default); + +AttachmentResult Get(object entity, string name); +Task GetAsync(object entity, string name, CancellationToken token = default); + +AttachmentResult Get(string documentId, string name); +Task GetAsync(string documentId, string name, CancellationToken token = default); + +AttachmentResult GetRange(object entity, string name, long? from, long? to); +Task GetRangeAsync(object entity, string name, long? from, long? to, + CancellationToken token = default); + +AttachmentResult GetRange(string documentId, string name, long? from, long? to); +Task GetRangeAsync(string documentId, string name, long? from, long? to, + CancellationToken token = default); + +AttachmentResult GetRevision(string documentId, string name, string changeVector); +Task GetRevisionAsync(string documentId, string name, string changeVector, + CancellationToken token = default); + +IEnumerator Get(IEnumerable attachments); +Task> GetAsync(IEnumerable attachments, + CancellationToken token = default); +``` + + +| Parameter | Type | Description | +|----------------------|----------------------------------|-------------| +| **documentId** | `string` | The ID of the document from which to retrieve the attachment. | +| **entity** | `string` | The loaded document entity from which to retrieve the attachment. | +| **name** | `string` | The name of the attachment to retrieve (case-insensitive). | +| **changeVector** | `string` | The change vector of the revision to retrieve the attachment from. | +| **from** | `long` | (Optional) The starting byte position (inclusive) for partial content retrieval. | +| **to** | `long` | (Optional) The ending byte position (inclusive) for partial content retrieval. | +| **attachments** | `IEnumerable` | A list of attachment requests to retrieve multiple attachments in a single call.| + +| Return value | Description | +|--------------------|---------------------------------------------------| +| `AttachmentResult` | An object with the retrieved attachment's details | + +--- + +### `AttachmentResult` + + +```csharp +public class AttachmentResult +{ + public Stream Stream; + public AttachmentDetails Details; +} + +public class AttachmentDetails : AttachmentName +{ + public string ChangeVector; + public string DocumentId; +} + +public class AttachmentName +{ + public string Name; + public string Hash; + public string ContentType; + public long Size; +} +``` + + + \ No newline at end of file diff --git a/docs/client-api/operations/attachments/_get-attachment-java.mdx b/docs/document-extensions/attachments/content/_get-attachments-java.mdx similarity index 51% rename from docs/client-api/operations/attachments/_get-attachment-java.mdx rename to docs/document-extensions/attachments/content/_get-attachments-java.mdx index 5f5c63f118..7edcd5d9e5 100644 --- a/docs/client-api/operations/attachments/_get-attachment-java.mdx +++ b/docs/document-extensions/attachments/content/_get-attachments-java.mdx @@ -2,10 +2,91 @@ import Admonition from '@theme/Admonition'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; + + +* This article explains how to retrieve attachments that are already stored in the database. + +* In this article + * [Get attachment via the session](../../document-extensions/attachments/get-attachments#get-attachment-via-the-session) + * [Get attachment via an operation](../../document-extensions/attachments/get-attachments#get-attachment-via-an-operation) + + + + + +There are a few methods that allow you to download attachments from a database: + +**session.advanced().attachments().get** can be used to download an attachment. +**session.advanced().attachments().getNames** can be used to download all attachment names that are attached to a document. +**session.advanced().attachments().getRevision** can be used to download an attachment of a revision document. +**session.advanced().attachments().exists** can be used to determine if an attachment exists on a document. + +### Syntax + + + +{`AttachmentName[] getNames(Object entity); + +boolean exists(String documentId, String name); + +CloseableAttachmentResult get(String documentId, String name); + +CloseableAttachmentResult get(Object entity, String name); + +CloseableAttachmentResult getRevision(String documentId, String name, String changeVector); +`} + + + +### Example + + + +{`try (IDocumentSession session = store.openSession()) \{ + Album album = session.load(Album.class, "albums/1"); + + try (CloseableAttachmentResult file1 = session + .advanced().attachments().get(album, "001.jpg"); + CloseableAttachmentResult file2 = session + .advanced().attachments().get("albums/1", "002.jpg")) \{ + + InputStream inputStream = file1 + .getData(); + + AttachmentDetails attachmentDetails = file1.getDetails(); + String name = attachmentDetails.getName(); + String contentType = attachmentDetails.getContentType(); + String hash = attachmentDetails.getHash(); + long size = attachmentDetails.getSize(); + String documentId = attachmentDetails.getDocumentId(); + String changeVector = attachmentDetails.getChangeVector(); + \} + + AttachmentName[] attachmentNames = session.advanced().attachments().getNames(album); + for (AttachmentName attachmentName : attachmentNames) \{ + + String name = attachmentName.getName(); + String contentType = attachmentName.getContentType(); + String hash = attachmentName.getHash(); + long size = attachmentName.getSize(); + \} + + boolean exists = session.advanced().attachments().exists("albums/1", "003.jpg"); +\} +`} + + + + + + + This operation is used to get an attachment from a document. -## Syntax +### Syntax @@ -118,5 +199,5 @@ public class AttachmentName \{ `} - - + + \ No newline at end of file diff --git a/docs/client-api/operations/attachments/_get-attachment-nodejs.mdx b/docs/document-extensions/attachments/content/_get-attachments-nodejs.mdx similarity index 50% rename from docs/client-api/operations/attachments/_get-attachment-nodejs.mdx rename to docs/document-extensions/attachments/content/_get-attachments-nodejs.mdx index 7a4b8ed098..aa5272b3e8 100644 --- a/docs/client-api/operations/attachments/_get-attachment-nodejs.mdx +++ b/docs/document-extensions/attachments/content/_get-attachments-nodejs.mdx @@ -2,18 +2,96 @@ import Admonition from '@theme/Admonition'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; -* Use the `GetAttachmentOperation` to retrieve an attachment from a document. +* This article explains how to retrieve attachments that are already stored in the database. + +* In this article + * [Get attachment via the session](../../document-extensions/attachments/get-attachments#get-attachment-via-the-session) + * [Get attachment via an operation](../../document-extensions/attachments/get-attachments#get-attachment-via-an-operation) + + -* In this page: + + +There are a few methods that allow you to download attachments from a database: - * [Get attachment example](../../../client-api/operations/attachments/get-attachment.mdx#get-attachment-example) - * [Syntax](../../../client-api/operations/attachments/get-attachment.mdx#syntax) - - -## Get attachment example +**session.advanced.attachments.get()** can be used to download an attachment. +**session.advanced.attachments.getNames()** can be used to download all attachment names that are attached to a document. +**session.advanced.attachments.getRevision()** can be used to download an attachment of a revision document. +**session.advanced.attachments.exists()** can be used to determine if an attachment exists on a document. + +### Syntax + + + +{`session.advanced.attachments.getNames(entity); + +session.advanced.attachments.exists(documentId, name); + +session.advanced.attachments.get(documentId, name); + +session.advanced.attachments.get(entity, name); + +session.advanced.attachments.getRevision(documentId, name, changeVector); +`} + + + +| Parameters | | | +| ------------- | ------------- | ----- | +| **entity** or **documentId** | object or string | instance of the entity or the entity ID | +| **name** | string | attachment name | +| **changeVector** | string | change vector for revision identification | + +| Return Value | | +| ------------- | ------------- | +| `Promise` | Promise resolving to a Readable for attachment content | + +### Example + + + +{`const album = await session.load("albums/1"); + +const file1 = await session.advanced.attachments.get(album, "001.jpg"); +const file2 = await session.advanced.attachments.get("albums/1", "002.jpg"); + +const inputStream = file1.data; + +const attachmentDetails = file1.details; +// \{ +// name: '001.jpg', +// documentId: 'albums/1', +// contentType: 'image/jpeg', +// hash: 'MvUEcrFHSVDts5ZQv2bQ3r9RwtynqnyJzIbNYzu1ZXk=', +// changeVector: '"A:3-K5TR36dafUC98AItzIa6ow"', +// size: 25793 +// \} + +const attachmentNames = await session.advanced.attachments.getNames(album); +for (const attachmentName of attachmentNames) \{ + const name = attachmentName.name; + const contentType = attachmentName.contentType; + const hash = attachmentName.hash; + const size = attachmentName.size; +\} + +const exists = session.advanced.attachments.exists("albums/1", "003.jpg"); +// true + +`} + + + + + + + +### Get attachment example @@ -40,9 +118,7 @@ attachmentResult.data - - -## Syntax +### Syntax @@ -53,10 +129,10 @@ attachmentResult.data | Parameter | Type | Description | |------------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| __documentId__ | `string` | Document ID that contains the attachment. | -| __name__ | `string` | Name of attachment to get. | -| __type__ | `string` | Specify whether getting an attachment from a document or from a revision.
(`"Document"` or `"Revision"`). | -| __changeVector__ | `string` | The ChangeVector of the document or the revision to which the attachment belongs.
Mandatory when getting an attachment from a revision.
Used for concurrency checks (use `null` to skip the check). | +| **documentId** | `string` | Document ID that contains the attachment. | +| **name** | `string` | Name of attachment to get. | +| **type** | `string` | Specify whether getting an attachment from a document or from a revision.
(`"Document"` or `"Revision"`). | +| **changeVector** | `string` | The ChangeVector of the document or the revision to which the attachment belongs.
Mandatory when getting an attachment from a revision.
Used for concurrency checks (use `null` to skip the check). | | Return Value of `store.operations.send(getAttachmentOp)` | | |-----------------------------------------------------------|-----------------------------------------| @@ -92,8 +168,6 @@ attachmentResult.data \} `}
-
- - - - +
+ + \ No newline at end of file diff --git a/docs/document-extensions/attachments/_loading-php.mdx b/docs/document-extensions/attachments/content/_get-attachments-php.mdx similarity index 100% rename from docs/document-extensions/attachments/_loading-php.mdx rename to docs/document-extensions/attachments/content/_get-attachments-php.mdx diff --git a/docs/document-extensions/attachments/_loading-python.mdx b/docs/document-extensions/attachments/content/_get-attachments-python.mdx similarity index 100% rename from docs/document-extensions/attachments/_loading-python.mdx rename to docs/document-extensions/attachments/content/_get-attachments-python.mdx diff --git a/docs/document-extensions/attachments/_indexing-csharp.mdx b/docs/document-extensions/attachments/content/_indexing-attachments-csharp.mdx similarity index 60% rename from docs/document-extensions/attachments/_indexing-csharp.mdx rename to docs/document-extensions/attachments/content/_indexing-attachments-csharp.mdx index 3a37a59522..15e721d7f2 100644 --- a/docs/document-extensions/attachments/_indexing-csharp.mdx +++ b/docs/document-extensions/attachments/content/_indexing-attachments-csharp.mdx @@ -2,47 +2,53 @@ import Admonition from '@theme/Admonition'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; * Indexing attachments allows you to query for documents based on their attachments' details and content. * **Static indexes**: - Both attachments' details and content can be indexed within a static-index definition. + * **Local attachments** - Both attachment details and content can be indexed. + * **Remote attachments** - Only attachment details can be indexed. + Remote attachment content is not indexed by design, to avoid downloading files from cloud storage during indexing, for performance reasons. * **Auto-indexes**: Auto-indexing attachments via dynamic queries is not available at this time. * In this page: - * [Index attachments details](../../document-extensions/attachments/indexing.mdx#index-attachments-details) - * [Index details & content - by attachment name](../../document-extensions/attachments/indexing.mdx#index-details--content---by-attachment-name) - * [Index details & content - all attachments](../../document-extensions/attachments/indexing.mdx#index-details--content---all-attachments) - * [Leveraging indexed attachments](../../document-extensions/attachments/indexing.mdx#leveraging-indexed-attachments) - * [Syntax](../../document-extensions/attachments/indexing.mdx#syntax) + * [Index attachments details](../../document-extensions/attachments/indexing-attachments#index-attachments-details) + * [Index details & content - for specific attachment name](../../document-extensions/attachments/indexing-attachments#index-details-content-for-specific-attachment-name) + * [Index details & content - for all attachments](../../document-extensions/attachments/indexing-attachments#index-details-content-for-all-attachments) + * [Leveraging indexed attachments](../../document-extensions/attachments/indexing-attachments#leveraging-indexed-attachments) + * [Syntax](../../document-extensions/attachments/indexing-attachments#syntax) -## Index attachments details + + **The index**: -* To index **attachments' details**, call `AttachmentsFor()` within the index definition. +* To index **attachment details**, call `AttachmentsFor()` within the index definition. -* `AttachmentsFor()` provides access to the **name**, **size**, **hash**, and **content-type** of each attachment a document has. - These details can then be used when defining the index-fields. +* `AttachmentsFor()` provides access to the **name**, **size**, **hash**, and **content-type** of each attachment the document has. + These details can be used to define the index-fields. Once the index is deployed, you can query the index to find Employee documents based on these attachment properties. -* To index **attachments' content**, see the examples [below](../../document-extensions/attachments/indexing.mdx#index-details--content---by-attachment-name). +* Indexing attachment details is supported for both **local** and **remote** attachments. + To index attachment content, see the examples [below](../../document-extensions/attachments/indexing-attachments#index-details--content---by-attachment-name). - -{`public class Employees_ByAttachmentDetails : +```csharp +public class Employees_ByAttachmentDetails : AbstractIndexCreationTask { public class IndexEntry { - public string EmployeeName { get; set; } - + // The index fields: + public string EmployeeName { get; set; } public string[] AttachmentNames { get; set; } public string[] AttachmentContentTypes { get; set; } public long[] AttachmentSizes { get; set; } @@ -67,12 +73,11 @@ import CodeBlock from '@theme/CodeBlock'; }; } } -`} - +``` - -{`public class Employees_ByAttachmentDetails_JS : AbstractJavaScriptIndexCreationTask +```csharp +public class Employees_ByAttachmentDetails_JS : AbstractJavaScriptIndexCreationTask { public Employees_ByAttachmentDetails_JS() { @@ -84,26 +89,32 @@ import CodeBlock from '@theme/CodeBlock'; return { EmployeeName: employee.FirstName + ' ' + employee.LastName, - AttachmentNames: attachments.map(function(attachment) { return attachment.Name; }), - AttachmentContentTypes: attachments.map(function(attachment) { return attachment.ContentType; }), - AttachmentSizes: attachments.map(function(attachment) { return attachment.Size; }) + AttachmentNames: attachments.map(function(attachment) { + return attachment.Name; + }), + AttachmentContentTypes: attachments.map(function(attachment) { + return attachment.ContentType; + }), + AttachmentSizes: attachments.map(function(attachment) { + return attachment.Size; + }) }; })" }; } } -`} - +``` + **Query the Index**: You can now query for Employee documents based on their attachments details. - -{`List employees = session +```csharp +List employees = session // Query the index for matching employees .Query() // Filter employee results by their attachments details @@ -118,100 +129,107 @@ You can now query for Employee documents based on their attachments details. // Running this query on the Northwind sample data, // results will include 'employees/4-A' and 'employees/5-A'. // These 2 documents contain an attachment by name 'photo.jpg' with a matching size. -`} - +``` - -{`List employees = await asyncSession +```csharp +List employees = await asyncSession .Query() .Where(x => x.AttachmentNames.Contains("photo.jpg")) .Where(x => x.AttachmentSizes.Any(size => size > 20_000)) .OfType() .ToListAsync(); -`} - +``` - -{`List employees = session.Advanced +```csharp +List employees = session.Advanced .DocumentQuery() .WhereEquals("AttachmentNames", "photo.jpg") .WhereGreaterThan("AttachmentSizes", 20_000) .OfType() .ToList(); -`} - +``` - -{`from index "Employees/ByAttachmentDetails" +```sql +from index "Employees/ByAttachmentDetails" where AttachmentNames == "photo.jpg" and AttachmentSizes > 20000 -`} - +``` + + -## Index details & content - by attachment name - + + +Indexing attachment content is supported only for [Local attachments](../../document-extensions/attachments/store-attachments/store-attachments-local). +Indexing attachment content is Not supported for [Remote attachments](../../document-extensions/attachments/store-attachments/store-attachments-remote). + + + **Sample data**: -* Each Employee document in RavenDB's sample data already includes a _photo.jpg_ attachment. - -* For all following examples, let's store a textual attachment (file _notes.txt_) on 3 documents in the 'Employees' collection. +* Each Employee document in RavenDB's sample data already includes a local attachment - _photo.jpg_. +* For the following examples, let's store a local textual attachment (file _notes.txt_) on 3 documents in the 'Employees' collection. - - -{`// Create some sample attachments: + +```csharp +// Create some sample attachments: for (var i = 1; i <= 3; i++) -\{ - var id = $"employees/\{i\}-A"; +{ + var id = $"employees/{i}-A"; // Load an employee document: - var employee = session.Load($"employees/\{i\}-A"); + var employee = session.Load($"employees/{i}-A"); if (employee?.Notes == null || employee.Notes.Count == 0) continue; // Store the employee's notes as an attachment on the document: byte[] bytes = System.Text.Encoding.UTF8.GetBytes(employee.Notes[0]); using (var stream = new MemoryStream(bytes)) - \{ + { session.Advanced.Attachments.Store( - $"employees/\{i\}-A", + $"employees/{i}-A", "notes.txt", stream, "text/plain"); session.SaveChanges(); - \} -\} -`} - + } +} +``` -**The index**: -* To index the **details & content** for a specific attachment, call `LoadAttachment()` within the index definition. - -* In addition to accessing the attachment details, `LoadAttachment()` provides access to the attachment's content, - which can be used when defining the index-fields. +**The index**: + +* Call `LoadAttachment()` within the index definition to access both the **details** and **content** of a specific attachment. + +* Access to the **content** is available only for LOCAL attachments via methods `GetContentAsString()` or `GetContentAsStream()`, + as shown in the example below. + The content can be indexed just like the attachment details. + +* Calling these "get content" methods on a REMOTE attachment is not supported. + In such cases, the index will enter an error state, and a `RemoteAttachmentIndexingException` will appear in the index errors. + To avoid this, always check whether the attachment is local before accessing its content, as demonstrated below. - -{`public class Employees_ByAttachment: +```csharp +public class Employees_ByAttachment: AbstractIndexCreationTask { public class IndexEntry { + // The index fields: public string AttachmentName { get; set; } public string AttachmentContentType { get; set; } public long AttachmentSize { get; set; } - public string AttachmentContent { get; set; } } @@ -221,34 +239,39 @@ for (var i = 1; i <= 3; i++) from employee in employees // Call 'LoadAttachment' to get attachment's details and content - // pass the attachment name, e.g. "notes.txt" + // Pass the attachment name, e.g. "notes.txt" let attachment = LoadAttachment(employee, "notes.txt") + + // Check whether the attachment is stored locally + // 'RemoteAttachmentFlags.None' indicates a LOCAL attachment + let isLocal = attachment.RemoteFlags == RemoteAttachmentFlags.None select new IndexEntry() { - // Index DETAILS of attachment: + // Index attachment DETAILS (available for both LOCAL and REMOTE attachments): AttachmentName = attachment.Name, AttachmentContentType = attachment.ContentType, AttachmentSize = attachment.Size, - // Index CONTENT of attachment: - // Call 'GetContentAsString' to access content - AttachmentContent = attachment.GetContentAsString() + // Index attachment CONTENT (available only for LOCAL attachments): + // Call 'GetContentAsString' to extract the content + AttachmentContent = isLocal ? attachment.GetContentAsString() : null }; - // It can be useful configure Full-Text search on the attachment content index-field + // It can be useful to configure Full-Text search on the AttachmentContent index-field Index(x => x.AttachmentContent, FieldIndexing.Search); - - // Documents with an attachment named 'notes.txt' will be indexed, - // allowing you to query them by either the attachment's details or its content. + + // This index processes Employee documents. + // It allows querying these documents based the "notes.txt" attachment details & content: + // * Attachment details (available for both local and remote attachments) + // * Attachment content (available for local attachments only) } } -`} - +``` - -{`public class Employees_ByAttachment_JS : AbstractJavaScriptIndexCreationTask +```csharp +public class Employees_ByAttachment_JS : AbstractJavaScriptIndexCreationTask { public Employees_ByAttachment_JS() { @@ -256,12 +279,15 @@ for (var i = 1; i <= 3; i++) { @"map('Employees', function (employee) { var attachment = loadAttachment(employee, 'notes.txt'); + + var isLocal = attachment.RemoteFlags === 'None'; return { AttachmentName: attachment.Name, AttachmentContentType: attachment.ContentType, AttachmentSize: attachment.Size, - AttachmentContent: attachment.getContentAsString() + + AttachmentContent: isLocal ? attachment.getContentAsString() : null }; })" }; @@ -277,18 +303,18 @@ for (var i = 1; i <= 3; i++) }; } } -`} - +``` + **Query the Index**: You can now query for Employee documents based on their attachment details and/or its content. - -{`List employees = session +```csharp +List employees = session // Query the index for matching employees .Query() // Can make a full-text search @@ -302,12 +328,11 @@ You can now query for Employee documents based on their attachment details and/o // Results will include 'employees/1-A' and 'employees/2-A'. // Only these 2 documents have an attachment by name 'notes.txt' // that contains either 'Colorado' or 'Dallas'. -`} - +``` - -{`List employees = await asyncSession +```csharp +List employees = await asyncSession // Query the index for matching employees .Query() // Can make a full-text search @@ -315,46 +340,46 @@ You can now query for Employee documents based on their attachment details and/o .Search(x => x.AttachmentContent, "Colorado Dallas") .OfType() .ToListAsync(); -`} - +``` - -{`List employees = session.Advanced +```csharp +List employees = session.Advanced .DocumentQuery() .Search(x => x.AttachmentContent, "Colorado Dallas") .OfType() .ToList(); -`} - +``` - -{`from index "Employees/ByAttachment" +```sql +from index "Employees/ByAttachment" where search(AttachmentContent, "Colorado Dallas") -`} - +``` + - -## Index details & content - all attachments - + + **The index**: -* Use `LoadAttachments()` to be able to index the **details & content** of ALL attachments. +* Call `LoadAttachments()` within the index definition to be able to index the **details & content** of ALL attachments. + +* Access to the content is available only for LOCAL attachments, and it can be indexed just like the details. -* Note how the index example below is employing the [Fanout index](../../indexes/indexing-nested-data.mdx#fanout-index---multiple-index-entries-per-document) pattern. +* Note how the index example below is employing the [Fanout index](../../indexes/indexing-nested-data#fanout-index---multiple-index-entries-per-document) pattern. - -{`public class Employees_ByAllAttachments : +```csharp +public class Employees_ByAllAttachments : AbstractIndexCreationTask { public class IndexEntry { + // The index fields: public string AttachmentName { get; set; } public string AttachmentContentType { get; set; } public long AttachmentSize { get; set; } @@ -369,31 +394,34 @@ where search(AttachmentContent, "Colorado Dallas") from employee in employees from attachment in LoadAttachments(employee) - // This will be a Fanout index - + // This will be a FANOUT index - // the index will generate an index-entry for each attachment per document + + // Check whether the attachment is stored locally + // 'RemoteAttachmentFlags.None' indicates a LOCAL attachment + let isLocal = attachment.RemoteFlags == RemoteAttachmentFlags.None select new IndexEntry { - // Index DETAILS of attachment: + // Index attachment DETAILS (available for both LOCAL and REMOTE attachments): AttachmentName = attachment.Name, AttachmentContentType = attachment.ContentType, AttachmentSize = attachment.Size, - // Index CONTENT of attachment: - // Call 'getContentAsString' to access content - AttachmentContent = attachment.GetContentAsString() + // Index attachment CONTENT (available only for LOCAL attachments): + // Call 'GetContentAsString' to extract the content + AttachmentContent = isLocal ? attachment.GetContentAsString() : null }; - // It can be useful configure Full-Text search on the attachment content index-field + // It can be useful configure Full-Text search on the AttachmentContent index-field Index(x => x.AttachmentContent, FieldIndexing.Search); } } -`} - +``` - -{`public class Employees_ByAllAttachments_JS : AbstractJavaScriptIndexCreationTask +```csharp +public class Employees_ByAllAttachments_JS : AbstractJavaScriptIndexCreationTask { public Employees_ByAllAttachments_JS() { @@ -403,11 +431,13 @@ where search(AttachmentContent, "Colorado Dallas") const allAttachments = loadAttachments(employee); return allAttachments.map(function (attachment) { + var isLocal = attachment.RemoteFlags === 'None'; + return { attachmentName: attachment.Name, attachmentContentType: attachment.ContentType, - attachmentSize: attachment.Size, - attachmentContent: attachment.getContentAsString() + attachmentSize: attachment.Size, + AttachmentContent: isLocal ? attachment.getContentAsString() : null }; }); })" @@ -424,16 +454,16 @@ where search(AttachmentContent, "Colorado Dallas") }; } } -`} - +``` + **Query the Index**: - -{`// Query the index for matching employees +```csharp +// Query the index for matching employees List employees = session .Query() // Filter employee results by their attachments details and content: @@ -449,23 +479,21 @@ List employees = session // Results will include: // 'employees/1-A' and 'employees/2-A' that match the content criteria // 'employees/4-A' and 'employees/5-A' that match the size criteria -`} - +``` - -{`List employees = await asyncSession +```csharp +List employees = await asyncSession .Query() .Search(x => x.AttachmentContent, "Colorado Dallas", options: SearchOptions.Or) .Where(x => x.AttachmentSize > 20_000) .OfType() .ToListAsync(); -`} - +``` - -{`List employees = session +```csharp +List employees = session .Advanced .DocumentQuery() .Search(x => x.AttachmentContent, "Colorado Dallas") @@ -473,69 +501,77 @@ List employees = session .WhereGreaterThan(x => x.AttachmentSize, 20_000) .OfType() .ToList(); -`} - +``` - -{`from index "Employees/ByAllAttachments" +```sql +from index "Employees/ByAllAttachments" where search(AttachmentContent, "Colorado Dallas") or AttachmentSize > 20000 -`} - +``` + - -## Leveraging indexed attachments + * Access to the indexed attachment content opens the door to many different applications, including many that can be integrated directly into RavenDB. * This [blog post](https://ayende.com/blog/192001-B/using-machine-learning-with-ravendb) demonstrates - how image recognition can be applied to indexed attachments using the [additional sources](../../indexes/extending-indexes.mdx) feature. + how image recognition can be applied to indexed attachments using the [additional sources](../../indexes/extending-indexes) feature. The resulting index allows filtering and querying based on image content. + - -## Syntax + #### `AttachmentsFor` - - -{`// Returns a list of attachment details for the specified document. + +```csharp +// Returns a list of attachment details for the specified document (without binary data). IEnumerable AttachmentsFor(object document); -`} - +``` | Parameter | Type | Description | |--------------|----------|-----------------------------------------------------------------| | **document** | `object` | The document object whose attachments details you want to load. | - - -{`// AttachmentsFor returns a list containing the following attachment details object: + +```csharp +// AttachmentsFor returns a list containing the following attachment details object: public class AttachmentName -\{ +{ public string Name; public string Hash; public string ContentType; public long Size; -\} -`} - + public RemoteAttachmentParameters RemoteParameters; +} +``` + + + +```csharp +public class RemoteAttachmentParameters +{ + // The scheduled time to upload the attachment to the remote destination + public DateTime At; + // The identifier of the remote storage destination where the attachment will be uploaded + public string Identifier; +} +``` #### `LoadAttachment` - - -{`// LoadAttachment returns attachment details and methods to access its content. + +```csharp +// LoadAttachment returns attachment details and methods to access its content. public IAttachmentObject LoadAttachment(object document, string attachmentName); -`} - +``` | Parameter | Type | Description | @@ -543,34 +579,55 @@ public IAttachmentObject LoadAttachment(object document, string attachmentName); | **document** | `object` | The document whose attachment you want to load. | | **attachmentName** | `string` | The name of the attachment to load. | - - -{`public interface IAttachmentObject -\{ - public string Name \{ get; \} - public string Hash \{ get; \} - public string ContentType \{ get; \} - public long Size \{ get; \} + +```csharp +// LoadAttachment returns the following object: +public interface IAttachmentObject +{ + public string Name { get; } + public string Hash { get; } + public string ContentType { get; } + public long Size { get; } + + // The scheduled time when the attachment was uploaded to cloud storage + public DateTime? RemoteAt {get; } + // The identifier of the remote storage destination where the attachment is stored. + public string RemoteIdentifier { get; } + // The flags indicating whether the attachment is stored locally or remotely. + public RemoteAttachmentFlags RemoteFlags { get; } + // Methods to access the content of LOCAL attachments only public string GetContentAsString(); public string GetContentAsString(Encoding encoding); public Stream GetContentAsStream(); -\} -`} - +} +``` + + + +```csharp +public enum RemoteAttachmentFlags + { + // No flags are set. The attachment is stored locally. + None = 0, + + // The attachment is stored remotely in cloud storage rather than in the local database. + Remote = 0x1 + } +``` #### `LoadAttachments` - - -{`// Returns a list of ALL attachments for the specified document. + +```csharp +// Returns a list of ALL attachments for the specified document. public IEnumerable LoadAttachments(object document); -`} - +``` | Parameter | Type | Description | |----------------|-----------|--------------------------------------------------| | **document** | `object` | The document whose attachments you want to load. | - + + \ No newline at end of file diff --git a/docs/document-extensions/attachments/_indexing-java.mdx b/docs/document-extensions/attachments/content/_indexing-attachments-java.mdx similarity index 93% rename from docs/document-extensions/attachments/_indexing-java.mdx rename to docs/document-extensions/attachments/content/_indexing-attachments-java.mdx index 3d3f1a7f93..10c3e8616d 100644 --- a/docs/document-extensions/attachments/_indexing-java.mdx +++ b/docs/document-extensions/attachments/content/_indexing-attachments-java.mdx @@ -2,6 +2,8 @@ import Admonition from '@theme/Admonition'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; @@ -14,9 +16,9 @@ import CodeBlock from '@theme/CodeBlock'; Auto-indexing attachments via dynamic queries is not available at this time. * In this page: - * [Syntax](../../document-extensions/attachments/indexing.mdx#syntax) - * [Examples](../../document-extensions/attachments/indexing.mdx#examples) - * [Leveraging indexed attachments](../../document-extensions/attachments/indexing.mdx#leveraging-indexed-attachments) + * [Syntax](../../document-extensions/attachments/indexing-attachments#syntax) + * [Examples](../../document-extensions/attachments/indexing-attachments#examples) + * [Leveraging indexed attachments](../../document-extensions/attachments/indexing-attachments#leveraging-indexed-attachments) ## Syntax @@ -178,7 +180,7 @@ List employees = session.query(Employees_ByAttachmentNames.class) including many that can be integrated directly into RavenDB. * In this [blog post](https://ayende.com/blog/192001-B/using-machine-learning-with-ravendb), - Oren Eini demonstrates how image recognition can be applied to indexed attachments using the [additional sources](../../indexes/extending-indexes.mdx) feature. + Oren Eini demonstrates how image recognition can be applied to indexed attachments using the [additional sources](../../indexes/extending-indexes) feature. The resulting index allows filtering and querying based on image content. diff --git a/docs/document-extensions/attachments/_indexing-nodejs.mdx b/docs/document-extensions/attachments/content/_indexing-attachments-nodejs.mdx similarity index 90% rename from docs/document-extensions/attachments/_indexing-nodejs.mdx rename to docs/document-extensions/attachments/content/_indexing-attachments-nodejs.mdx index cbe219d55d..5149f4b9b7 100644 --- a/docs/document-extensions/attachments/_indexing-nodejs.mdx +++ b/docs/document-extensions/attachments/content/_indexing-attachments-nodejs.mdx @@ -2,6 +2,8 @@ import Admonition from '@theme/Admonition'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; @@ -14,13 +16,14 @@ import CodeBlock from '@theme/CodeBlock'; Auto-indexing attachments via dynamic queries is not available at this time. * In this page: - * [Index attachments details](../../document-extensions/attachments/indexing.mdx#index-attachments-details) - * [Index details & content - by attachment name](../../document-extensions/attachments/indexing.mdx#index-details--content---by-attachment-name) - * [Index details & content - all attachments](../../document-extensions/attachments/indexing.mdx#index-details--content---all-attachments) - * [Leveraging indexed attachments](../../document-extensions/attachments/indexing.mdx#leveraging-indexed-attachments) - * [Syntax](../../document-extensions/attachments/indexing.mdx#syntax) + * [Index attachments details](../../document-extensions/attachments/indexing-attachments#index-attachments-details) + * [Index details & content - for specific attachment name](../../document-extensions/attachments/indexing-attachments#index-details--content---for-specific-attachment-name) + * [Index details & content - for all attachments](../../document-extensions/attachments/indexing-attachments#index-details--content---for-all-attachments) + * [Leveraging indexed attachments](../../document-extensions/attachments/indexing-attachments#leveraging-indexed-attachments) + * [Syntax](../../document-extensions/attachments/indexing-attachments#syntax) + ## Index attachments details @@ -29,10 +32,10 @@ import CodeBlock from '@theme/CodeBlock'; * To index **attachments' details**, call `attachmentsFor()` within the index definition. * `attachmentsFor()` provides access to the **name**, **size**, **hash**, and **content-type** of each attachment a document has. - These details can then be used when defining the index-fields. + These details can be used to define the index-fields. Once the index is deployed, you can query the index to find Employee documents based on these attachment properties. -* To index **attachments' content**, see the examples [below](../../document-extensions/attachments/indexing.mdx#index-details--content---by-attachment-name). +* To index **attachments' content**, see the examples [below](../../document-extensions/attachments/indexing-attachments#index-details--content---by-attachment-name). @@ -99,8 +102,7 @@ where attachmentNames == "photo.jpg" and attachmentSizes > 20000 - -## Index details & content - by attachment name +## Index details & content - for specific attachment name @@ -135,7 +137,7 @@ await session.saveChanges(); **The index**: * To index the **details & content** for a specific attachment, call `loadAttachment()` within the index definition. -* In addition to accessing the attachment details, `loadAttachment()` provides access to the attachment's content, +* In addition to accessing the attachment details, `loadAttachment()` provides access to the attachment's content, which can be used when defining the index-fields. @@ -163,7 +165,7 @@ await session.saveChanges(); \} \}); - // It can be useful configure Full-Text search on the attachment content index-field + // It can be useful to configure Full-Text search on the attachment content index-field this.index("attachmentContent", "Search"); // Documents with an attachment named 'notes.txt' will be indexed, @@ -211,16 +213,14 @@ where search(attachmentContent, "Colorado Dallas") - - -## Index details & content - all attachments +## Index details & content - for all attachments **The index**: * Use `loadAttachments()` to be able to index the **details & content** of ALL attachments. -* Note how the index example below is employing the [Fanout index](../../indexes/indexing-nested-data.mdx#fanout-index---multiple-index-entries-per-document) pattern. +* Note how the index example below is employing the [Fanout index](../../indexes/indexing-nested-data#fanout-index---multiple-index-entries-per-document) pattern. @@ -294,18 +294,15 @@ where attachmentSize > 20000 or search(attachmentContent, "Colorado Dallas") - ## Leveraging indexed attachments * Access to the indexed attachment content opens the door to many different applications, including many that can be integrated directly into RavenDB. * This [blog post](https://ayende.com/blog/192001-B/using-machine-learning-with-ravendb) demonstrates - how image recognition can be applied to indexed attachments using the [additional sources](../../indexes/extending-indexes.mdx) feature. + how image recognition can be applied to indexed attachments using the [additional sources](../../indexes/extending-indexes) feature. The resulting index allows filtering and querying based on image content. - - ## Syntax #### `attachmentsFor` @@ -381,5 +378,4 @@ loadAttachments(document); | Parameter | Type | Description | |-----------------|-----------|--------------------------------------------------| -| **document** | `object` | The document whose attachments you want to load. | - +| **document** | `object` | The document whose attachments you want to load. | \ No newline at end of file diff --git a/docs/document-extensions/attachments/_indexing-php.mdx b/docs/document-extensions/attachments/content/_indexing-attachments-php.mdx similarity index 96% rename from docs/document-extensions/attachments/_indexing-php.mdx rename to docs/document-extensions/attachments/content/_indexing-attachments-php.mdx index 5f6d6d47b0..4d00a8c2df 100644 --- a/docs/document-extensions/attachments/_indexing-php.mdx +++ b/docs/document-extensions/attachments/content/_indexing-attachments-php.mdx @@ -2,6 +2,8 @@ import Admonition from '@theme/Admonition'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; @@ -15,9 +17,9 @@ import CodeBlock from '@theme/CodeBlock'; * In this page: - * [Syntax](../../document-extensions/attachments/indexing.mdx#syntax) - * [Examples](../../document-extensions/attachments/indexing.mdx#examples) - * [Leveraging indexed attachments](../../document-extensions/attachments/indexing.mdx#leveraging-indexed-attachments) + * [Syntax](../../document-extensions/attachments/indexing-attachments#syntax) + * [Examples](../../document-extensions/attachments/indexing-attachments#examples) + * [Leveraging indexed attachments](../../document-extensions/attachments/indexing-attachments#leveraging-indexed-attachments) ## Syntax diff --git a/docs/document-extensions/attachments/_indexing-python.mdx b/docs/document-extensions/attachments/content/_indexing-attachments-python.mdx similarity index 94% rename from docs/document-extensions/attachments/_indexing-python.mdx rename to docs/document-extensions/attachments/content/_indexing-attachments-python.mdx index bc70ead547..446fff0a17 100644 --- a/docs/document-extensions/attachments/_indexing-python.mdx +++ b/docs/document-extensions/attachments/content/_indexing-attachments-python.mdx @@ -2,6 +2,8 @@ import Admonition from '@theme/Admonition'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; @@ -15,9 +17,9 @@ import CodeBlock from '@theme/CodeBlock'; * In this page: - * [Syntax](../../document-extensions/attachments/indexing.mdx#syntax) - * [Examples](../../document-extensions/attachments/indexing.mdx#examples) - * [Leveraging indexed attachments](../../document-extensions/attachments/indexing.mdx#leveraging-indexed-attachments) + * [Syntax](../../document-extensions/attachments/indexing-attachments#syntax) + * [Examples](../../document-extensions/attachments/indexing-attachments#examples) + * [Leveraging indexed attachments](../../document-extensions/attachments/indexing-attachments#leveraging-indexed-attachments) ## Syntax @@ -252,7 +254,7 @@ employees = list( * In this [blog post](https://ayende.com/blog/192001-B/using-machine-learning-with-ravendb), Oren Eini demonstrates how image recognition can be applied to indexed attachments using the - [additional sources](../../indexes/extending-indexes.mdx) feature. + [additional sources](../../indexes/extending-indexes) feature. The resulting index allows filtering and querying based on image content. diff --git a/docs/document-extensions/attachments/content/_overview-csharp.mdx b/docs/document-extensions/attachments/content/_overview-csharp.mdx new file mode 100644 index 0000000000..8af7c29e7f --- /dev/null +++ b/docs/document-extensions/attachments/content/_overview-csharp.mdx @@ -0,0 +1,159 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; + + + +* RavenDB allows you to store files, such as images, PDFs, videos, text files, or any other format, as **attachments** alongside your JSON documents. + +* Each attachment is associated with a specific document and is identified by a unique name and an optional content type (e.g. _image/png_, _application/pdf_). + A single document can have any number of attachments, which are listed in the document’s metadata. + +* Attachments are stored as **binary data**, regardless of content type, and are handled as streams to enable efficient upload and retrieval. + +* Attachments can be stored **locally** on your RavenDB server, + or **remotely** in external storage such as Amazon S3 or any S3-compatible storage service, or Azure Blob Storage. + + * **Local storage**: + By default, attachments are stored locally in RavenDB's internal storage. + They are stored separately from the document itself in a dedicated attachment storage. + This avoids bloating your JSON document with large binary content. + See [Store attachments in local storage](../../document-extensions/attachments/store-attachments/store-attachments-local). + * **Remote storage**: + RavenDB does not enforce a hard limit on the size of locally stored attachments. + However, to reduce local disk usage, you can optionally configure RavenDB to offload attachments to remote storage. + See [Store attachments in remote storage](../../document-extensions/attachments/store-attachments/store-attachments-remote). + +* In this article: + * [Attachments in the documents list view](../../document-extensions/attachments/overview#attachments-in-the-documents-list-view) + * [Attachments in the document view](../../document-extensions/attachments/overview#attachments-in-the-document-view) + * [Attachments stats](../../document-extensions/attachments/overview#attachments-stats) + + + + + +![Document with attachments - list view](../assets/document-with-attachments-1.png) + +1. Open the _Documents_ section in the Studio navigation bar. +2. Select a collection (e.g., _Employees_) to display the documents list view. +3. The attachment flag is displayed for any document that has one or more attachments. + + + + + +![Document with attachments](../assets/document-with-attachments-2.png) + +1. **The document ID**: + The unique identifier of the document. + +2. **Document metadata**: + The Studio does not show the full attachment metadata in the document editor. + It only shows the `@flags` property with the value `HasAttachments` in the document's `@metadata` section + to indicate that the document has attachments. + +3. **Viewing the full metadata**: + Click this JSON button to view the document's raw content, including its full metadata. + You can also get the metadata programmatically using the Client API, see [Get entity metadata](../../client-api/session/how-to/get-and-modify-entity-metadata). + For details on the structure of attachment metadata, refer to: + * [The attachment metadata - local](../../document-extensions/attachments/store-attachments/store-attachments-local#the-attachment-metadata) + * [The attachment metadata - remote](../../document-extensions/attachments/store-attachments/store-attachments-remote#the-attachment-metadata) + +4. **Attachments tab**: + All attachments associated with the document are listed under the _Attachments_ tab in the document _Properties_ pane. + +5. **Add attachment to local storage**: + Click to add a new attachment to the document that will be stored in RavenDB's local storage. + Learn more in [Store attachments in local storage](../../document-extensions/attachments/store-attachments/store-attachments-local). + +6. **Add attachment to remote storage**: + Click to add a new attachment to the document that will be uploaded and stored in a remote destination. + Learn more in [Store attachments in remote storage](../../document-extensions/attachments/store-attachments/store-attachments-remote). + +7. **Storage location**: + The clip icon indicates where the attachment content is stored: + * Purple clip: The attachment is stored locally on the RavenDB server. + * Blue clip: The attachment is stored remotely (e.g., in Amazon S3 or Azure Storage). + +8. **Attachment name**: + The name assigned to the attachment when it was added to the document. + +9. **Attachment size**: + The size of the attachment file. + +10. **Remote attachment details**: + Additional information specific to the remote attachment such as upload time and destination identifier. + +11. **Delete attachment**: + Click to remove the attachment from the document and delete it from local RavenDB storage. + Learn more in [Delete attachments](../../document-extensions/attachments/delete-attachment). + + + + + +### View attachments stats in the Studio + +* The overall number of attachments in the database can be viewed in the **General Database Stats** view in the Studio. + +![Attachments stats](../assets/attachments-stats-1.png) + +1. Go to the **Stats** section in the Studio navigation bar. +2. This it the total number of attachments in the database, from all collections. +3. Click "Show detailed database & indexing stats" to expand the detailed section. + +![Attachments stats](../assets/attachments-stats-2.png) + +3. **Attachments details**: + `total`: + The total number of attachments in the database, from all collections. + `unique`: + The number of unique attachments stored **locally** in the database. + Learn more in [Deduplicating local attachments](../../document-extensions/attachments/store-attachments/store-attachments-local#deduplicating-local-attachments). + `remote`: + The total number of attachments stored in remote storage. + +--- + +### Get attachments stats via the Client API + +* Use the `GetDetailedStatisticsOperation` to retrieve statistics about attachments in the database. + For a list of all available statistics properties, see [Get statistics](../../client-api/operations/maintenance/get-stats). + + + +```csharp +var detailedStats = store.Maintenance.Send(new GetDetailedStatisticsOperation()); + +// Total number of attachments in the database (Local & Remote) +var totalAttachments = detailedStats.CountOfAttachments; + +// Total number of unique attachments in the database (Local only) +var uniqueLocalAttachments = detailedStats.CountOfUniqueAttachments; + +// Total number of Remote attachments in the database +var remoteAttachments = detailedStats.CountOfRemoteAttachments; +``` + + +```csharp +var detailedStats = await store.Maintenance.SendAsync(new GetDetailedStatisticsOperation()); + +// Total number of attachments in the database (Local & Remote) +var totalAttachments = detailedStats.CountOfAttachments; + +// Total number of unique attachments in the database (Local only) +var uniqueLocalAttachments = detailedStats.CountOfUniqueAttachments; + +// Total number of Remote attachments in the database +var remoteAttachments = detailedStats.CountOfRemoteAttachments; + +``` + + + + \ No newline at end of file diff --git a/docs/document-extensions/attachments/_what-are-attachments-csharp.mdx b/docs/document-extensions/attachments/content/_overview-java.mdx similarity index 100% rename from docs/document-extensions/attachments/_what-are-attachments-csharp.mdx rename to docs/document-extensions/attachments/content/_overview-java.mdx diff --git a/docs/document-extensions/attachments/_what-are-attachments-nodejs.mdx b/docs/document-extensions/attachments/content/_overview-nodejs.mdx similarity index 100% rename from docs/document-extensions/attachments/_what-are-attachments-nodejs.mdx rename to docs/document-extensions/attachments/content/_overview-nodejs.mdx diff --git a/docs/document-extensions/attachments/copy-move-rename.mdx b/docs/document-extensions/attachments/copy-move-rename.mdx new file mode 100644 index 0000000000..2a745a47a9 --- /dev/null +++ b/docs/document-extensions/attachments/copy-move-rename.mdx @@ -0,0 +1,38 @@ +--- +title: "Copy / Move / Rename Attachments" +sidebar_label: "Copy / Move / Rename Attachments" +sidebar_position: 5 +--- + +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + +import CopyMoveRenameCsharp from './content/_copy-move-rename-csharp.mdx'; +import CopyMoveRenameJava from './content/_copy-move-rename-java.mdx'; +import CopyMoveRenamePython from './content/_copy-move-rename-python.mdx'; +import CopyMoveRenamePhp from './content/_copy-move-rename-php.mdx'; +import CopyMoveRenameNodejs from './content/_copy-move-rename-nodejs.mdx'; + +export const supportedLanguages = ["csharp", "java", "python", "php", "nodejs"]; + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/document-extensions/attachments/copying-moving-renaming.mdx b/docs/document-extensions/attachments/copying-moving-renaming.mdx deleted file mode 100644 index 8829c35e19..0000000000 --- a/docs/document-extensions/attachments/copying-moving-renaming.mdx +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: "Attachments: Copy, Move, Rename" -sidebar_label: Copying, Moving & Renaming -sidebar_position: 4 ---- - -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -import CopyingMovingRenamingCsharp from './_copying-moving-renaming-csharp.mdx'; -import CopyingMovingRenamingJava from './_copying-moving-renaming-java.mdx'; -import CopyingMovingRenamingPython from './_copying-moving-renaming-python.mdx'; -import CopyingMovingRenamingPhp from './_copying-moving-renaming-php.mdx'; -import CopyingMovingRenamingNodejs from './_copying-moving-renaming-nodejs.mdx'; - -export const supportedLanguages = ["csharp", "java", "python", "php", "nodejs"]; - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/document-extensions/attachments/delete-attachment.mdx b/docs/document-extensions/attachments/delete-attachment.mdx new file mode 100644 index 0000000000..c29a4e8fe3 --- /dev/null +++ b/docs/document-extensions/attachments/delete-attachment.mdx @@ -0,0 +1,38 @@ +--- +title: "Delete Attachments" +sidebar_label: "Delete Attachments" +sidebar_position: 4 +--- + +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + +import DeleteAttachmentCsharp from './content/_delete-attachment-csharp.mdx'; +import DeleteAttachmentJava from './content/_delete-attachment-java.mdx'; +import DeleteAttachmentPython from './content/_delete-attachment-python.mdx'; +import DeleteAttachmentPhp from './content/_delete-attachment-php.mdx'; +import DeleteAttachmentNodejs from './content/_delete-attachment-nodejs.mdx'; + +export const supportedLanguages = ["csharp", "java", "python", "php", "nodejs"]; + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/document-extensions/attachments/deleting.mdx b/docs/document-extensions/attachments/deleting.mdx deleted file mode 100644 index 5d2ba91244..0000000000 --- a/docs/document-extensions/attachments/deleting.mdx +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: "Attachments: Deleting Attachments" -sidebar_label: Deleting -sidebar_position: 3 ---- - -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -import DeletingCsharp from './_deleting-csharp.mdx'; -import DeletingJava from './_deleting-java.mdx'; -import DeletingPython from './_deleting-python.mdx'; -import DeletingPhp from './_deleting-php.mdx'; -import DeletingNodejs from './_deleting-nodejs.mdx'; - -export const supportedLanguages = ["csharp", "java", "python", "php", "nodejs"]; - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/document-extensions/attachments/get-attachments.mdx b/docs/document-extensions/attachments/get-attachments.mdx new file mode 100644 index 0000000000..c6770e860d --- /dev/null +++ b/docs/document-extensions/attachments/get-attachments.mdx @@ -0,0 +1,38 @@ +--- +title: "Get Attachments" +sidebar_label: "Get Attachments" +sidebar_position: 3 +--- + +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + +import GetAttachmentsCsharp from './content/_get-attachments-csharp.mdx'; +import GetAttachmentsJava from './content/_get-attachments-java.mdx'; +import GetAttachmentsPython from './content/_get-attachments-python.mdx'; +import GetAttachmentsPhp from './content/_get-attachments-php.mdx'; +import GetAttachmentsNodejs from './content/_get-attachments-nodejs.mdx'; + +export const supportedLanguages = ["csharp", "java", "python", "php", "nodejs"]; + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/document-extensions/attachments/indexing-attachments.mdx b/docs/document-extensions/attachments/indexing-attachments.mdx new file mode 100644 index 0000000000..bbfd67d430 --- /dev/null +++ b/docs/document-extensions/attachments/indexing-attachments.mdx @@ -0,0 +1,38 @@ +--- +title: "Indexing Attachments" +sidebar_label: "Indexing Attachments" +sidebar_position: 6 +--- + +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + +import IndexingAttachmentsCsharp from './content/_indexing-attachments-csharp.mdx'; +import IndexingAttachmentsJava from './content/_indexing-attachments-java.mdx'; +import IndexingAttachmentsPython from './content/_indexing-attachments-python.mdx'; +import IndexingAttachmentsPhp from './content/_indexing-attachments-php.mdx'; +import IndexingAttachmentsNodejs from './content/_indexing-attachments-nodejs.mdx'; + +export const supportedLanguages = ["csharp", "java", "python", "php", "nodejs"]; + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/document-extensions/attachments/indexing.mdx b/docs/document-extensions/attachments/indexing.mdx deleted file mode 100644 index f4241fa1bb..0000000000 --- a/docs/document-extensions/attachments/indexing.mdx +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: "Index Attachments" -sidebar_label: Indexing -sidebar_position: 5 ---- - -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -import IndexingCsharp from './_indexing-csharp.mdx'; -import IndexingJava from './_indexing-java.mdx'; -import IndexingPython from './_indexing-python.mdx'; -import IndexingPhp from './_indexing-php.mdx'; -import IndexingNodejs from './_indexing-nodejs.mdx'; - -export const supportedLanguages = ["csharp", "java", "python", "php", "nodejs"]; - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/document-extensions/attachments/loading.mdx b/docs/document-extensions/attachments/loading.mdx deleted file mode 100644 index 0c5322b3c1..0000000000 --- a/docs/document-extensions/attachments/loading.mdx +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: "Attachments: Loading Attachments" -sidebar_label: Loading -sidebar_position: 2 ---- - -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -import LoadingCsharp from './_loading-csharp.mdx'; -import LoadingJava from './_loading-java.mdx'; -import LoadingPython from './_loading-python.mdx'; -import LoadingPhp from './_loading-php.mdx'; -import LoadingNodejs from './_loading-nodejs.mdx'; - -export const supportedLanguages = ["csharp", "java", "python", "php", "nodejs"]; - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/document-extensions/attachments/overview.mdx b/docs/document-extensions/attachments/overview.mdx new file mode 100644 index 0000000000..f3c8cb447e --- /dev/null +++ b/docs/document-extensions/attachments/overview.mdx @@ -0,0 +1,28 @@ +--- +title: "Attachments Overview" +sidebar_label: "Attachments Overview" +sidebar_position: 0 +--- + +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + +import OverviewCsharp from './content/_overview-csharp.mdx'; +import OverviewJava from './content/_overview-java.mdx'; +import OverviewNodejs from './content/_overview-nodejs.mdx'; + +export const supportedLanguages = ["csharp", "java", "nodejs"]; + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/document-extensions/attachments/store-attachments/_category_.json b/docs/document-extensions/attachments/store-attachments/_category_.json new file mode 100644 index 0000000000..8e466d3d1c --- /dev/null +++ b/docs/document-extensions/attachments/store-attachments/_category_.json @@ -0,0 +1,4 @@ +{ + "position": 2, + "label": "Store Attachments" +} \ No newline at end of file diff --git a/docs/document-extensions/attachments/store-attachments/assets/add-attachment-to-local-storage.png b/docs/document-extensions/attachments/store-attachments/assets/add-attachment-to-local-storage.png new file mode 100644 index 0000000000..9010663ef1 Binary files /dev/null and b/docs/document-extensions/attachments/store-attachments/assets/add-attachment-to-local-storage.png differ diff --git a/docs/document-extensions/attachments/store-attachments/assets/add-attachment-to-remote-storage-1.png b/docs/document-extensions/attachments/store-attachments/assets/add-attachment-to-remote-storage-1.png new file mode 100644 index 0000000000..951112fdd9 Binary files /dev/null and b/docs/document-extensions/attachments/store-attachments/assets/add-attachment-to-remote-storage-1.png differ diff --git a/docs/document-extensions/attachments/store-attachments/assets/add-attachment-to-remote-storage-2.png b/docs/document-extensions/attachments/store-attachments/assets/add-attachment-to-remote-storage-2.png new file mode 100644 index 0000000000..84ca178978 Binary files /dev/null and b/docs/document-extensions/attachments/store-attachments/assets/add-attachment-to-remote-storage-2.png differ diff --git a/docs/document-extensions/attachments/store-attachments/assets/add-attachment-to-remote-storage-3.png b/docs/document-extensions/attachments/store-attachments/assets/add-attachment-to-remote-storage-3.png new file mode 100644 index 0000000000..c0bccfe429 Binary files /dev/null and b/docs/document-extensions/attachments/store-attachments/assets/add-attachment-to-remote-storage-3.png differ diff --git a/docs/document-extensions/attachments/store-attachments/assets/add-attachment-to-remote-storage-4.png b/docs/document-extensions/attachments/store-attachments/assets/add-attachment-to-remote-storage-4.png new file mode 100644 index 0000000000..dd7a17c94f Binary files /dev/null and b/docs/document-extensions/attachments/store-attachments/assets/add-attachment-to-remote-storage-4.png differ diff --git a/docs/document-extensions/attachments/bulk-insert.mdx b/docs/document-extensions/attachments/store-attachments/bulk-insert.mdx similarity index 50% rename from docs/document-extensions/attachments/bulk-insert.mdx rename to docs/document-extensions/attachments/store-attachments/bulk-insert.mdx index 202cee9900..6dcd0b2f1d 100644 --- a/docs/document-extensions/attachments/bulk-insert.mdx +++ b/docs/document-extensions/attachments/store-attachments/bulk-insert.mdx @@ -1,15 +1,15 @@ --- -title: "Bulk Insert Attachments" -sidebar_label: Bulk Insert -sidebar_position: 6 +title: "Store using Bulk Insert" +sidebar_label: "Store using Bulk Insert" +sidebar_position: 2 --- import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; import LanguageContent from "@site/src/components/LanguageContent"; -import BulkInsertCsharp from './_bulk-insert-csharp.mdx'; -import BulkInsertPython from './_bulk-insert-python.mdx'; -import BulkInsertNodejs from './_bulk-insert-nodejs.mdx'; +import BulkInsertCsharp from './content/_bulk-insert-csharp.mdx'; +import BulkInsertPython from './content/_bulk-insert-python.mdx'; +import BulkInsertNodejs from './content/_bulk-insert-nodejs.mdx'; export const supportedLanguages = ["csharp", "python", "nodejs"]; @@ -25,14 +25,4 @@ export const supportedLanguages = ["csharp", "python", "nodejs"]; - - - - \ No newline at end of file + \ No newline at end of file diff --git a/docs/document-extensions/attachments/store-attachments/content/_bulk-insert-csharp.mdx b/docs/document-extensions/attachments/store-attachments/content/_bulk-insert-csharp.mdx new file mode 100644 index 0000000000..11ad0a380c --- /dev/null +++ b/docs/document-extensions/attachments/store-attachments/content/_bulk-insert-csharp.mdx @@ -0,0 +1,497 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; + + + +* This article explains how to add attachments using RavenDB’s **Bulk Insert** mechanism, + storing attachments alongside documents in a high-throughput, streamed process that avoids sending a separate request for each attachment. + +* In this article: + * [What is Bulk Insert](../../../document-extensions/attachments/store-attachments/bulk-insert#what-is-bulk-insert) + * [Inserting attachment with Bulk Insert](../../../document-extensions/attachments/store-attachments/bulk-insert#inserting-attachments-with-bulk-insert) + * [Bulk Insert attachments - Local storage](../../../document-extensions/attachments/store-attachments/bulk-insert#bulk-insert-attachments-local-storage) + * [Bulk Insert attachments - Remote storage](../../../document-extensions/attachments/store-attachments/bulk-insert#bulk-insert-attachments-remote-storage) + * [Syntax](../../../document-extensions/attachments/store-attachments/bulk-insert#syntax) + + + + +* Bulk Insert is RavenDB's high-performance API for efficiently inserting large volumes of documents and related data (such as attachments) in a streamed manner from the client to the server. + +* Instead of storing documents using the session (as explained in [Storing entities](../../../client-api/session/storing-entities)), + which opens an HTTP request for each _SaveChanges_ call, + Bulk Insert opens a **single long-lived request** and sends multiple documents in **batches**. + This reduces the overhead of repeatedly opening and closing sessions or transactions for each individual request, and lowers network and processing costs. + +* The client organizes the data into **batches** before streaming it to the server. + The server processes the stream as it arrives, each batch is processed and committed as a separate transaction. + +* There is no global transaction across the entire Bulk Insert, which means the operation as a whole is not atomic. + If a failure occurs, previously committed batches remain, and only the current batch is rolled back. + +* Learn more about Bulk Insert in [How to work with Bulk Insert](../../../client-api/bulk-insert/how-to-work-with-bulk-insert-operation). + + + + + +* RavenDB’s Bulk Insert API lets you stream documents and add their attachments in a single bulk insert operation. + +* Use the `AttachmentsFor(documentId).Store(...)` method on the bulk insert instance to add one or more attachments to each document. + This avoids the overhead of sending a separate request for every attachment, just like Bulk Insert does for documents. + +* Using the bulk insert operation, you can store a **new** document and then add attachments to it, + or load or query **existing** documents beforehand and then add attachments to them during the bulk insert. + Attachments can be stored **locally** on the RavenDB server or in **remote storage**. + +* A single TCP/HTTP streaming connection transports both documents and attachments. + The client organizes the data into batches before streaming it to the server. + The server processes the stream as it arrives; each batch is processed and committed as a separate transaction. + +* If storing a document or attachment fails, the server rolls back the current batch. + Previously committed batches remain unaffected. + +* Note: + * If an attachment with the same name is already associated with the document, its content will be **overwritten**. + * The target document must already exist before its attachments are added. + If you try to store an attachment for a document that hasn’t been stored yet, the operation will fail with a `DocumentDoesNotExistException`. + +* When to use Bulk Insert for attachments: + * You need to insert a large number of attachments. + * You don’t require full atomicity across the entire operation, partial success is acceptable. + * You prioritize performance and throughput over session-level convenience or per-document consistency. + + + + + +### Bulk insert documents with multiple attachments + + + +```csharp +var basePath = @"C:\temp"; // folder containing files to attach + +// Create a BulkInsert instance +using (var bulkInsertObj = store.BulkInsert()) +{ + for (int i = 0; i < 10; i++) + { + var docId = $"users/{i}"; + var metadata = new MetadataAsDictionary + { + // Specify the target collection for the new documents + // If not specified, documents will be created under the '@empty' collection + [Constants.Documents.Metadata.Collection] = "users" + }; + // Store a new user document in the BulkInsert instance + bulkInsertObj.Store(new { Name = $"User {i}" }, docId, metadata); + + // Prepare attachment names + var attachmentName1 = $"user_{i}.png"; // name for first attachment + var attachmentName2 = $"notes_{i}.txt"; // name for second attachment + + var filePath1 = Path.Combine(basePath, attachmentName1); + var filePath2 = Path.Combine(basePath, attachmentName2); + + // Call 'AttachmentsFor' to get the BulkInsert attachment interface + // Pass the document ID for which to attach the files + var attachmentsBulkInsert = bulkInsertObj.AttachmentsFor(docId); + + // Attach the first file: + if (File.Exists(filePath1)) + { + using (var fileStream1 = File.OpenRead(filePath1)) + { + // Call 'Store' to add the file to the BulkInsert instance + // The data stored in BulkInsert will be streamed to the server in batches + attachmentsBulkInsert.Store(attachmentName1, fileStream1, "image/png"); + } + } + // Attach the second file: + if (File.Exists(filePath2)) + { + using (var fileStream2 = File.OpenRead(filePath2)) + { + attachmentsBulkInsert.Store(attachmentName2, fileStream2, "text/plain"); + } + } + } +} +``` + + +```csharp +var basePath = @"C:\temp"; // folder containing files to attach + +// Create a BulkInsert instance +await using (var bulkInsertObj = store.BulkInsert()) +{ + for (int i = 0; i < 10; i++) + { + var docId = $"users/{i}"; + var metadata = new MetadataAsDictionary + { + // Specify the target collection for the new documents + // If not specified, documents will be created under the '@empty' collection + [Constants.Documents.Metadata.Collection] = "users" + }; + // Store a new user document in the BulkInsert instance + await bulkInsertObj.StoreAsync(new { Name = $"User {i}" }, docId, metadata); + + // Prepare attachment names + var attachmentName1 = $"user_{i}.png"; // name for first attachment + var attachmentName2 = $"notes_{i}.txt"; // name for second attachment + + var filePath1 = Path.Combine(basePath, attachmentName1); + var filePath2 = Path.Combine(basePath, attachmentName2); + + // Call 'AttachmentsFor' to get the BulkInsert attachment interface + // Pass the document ID for which to attach the files + var attachmentsBulkInsert = bulkInsertObj.AttachmentsFor(docId); + + // Attach the first file: + if (File.Exists(filePath1)) + { + await using (var fileStream1 = File.OpenRead(filePath1)) + { + // Call 'StoreAsync' to add the file to the BulkInsert instance + // The data stored in BulkInsert will be streamed to the server in batches + await attachmentsBulkInsert.StoreAsync( + attachmentName1, fileStream1, "image/png"); + } + } + // Attach the second file: + if (File.Exists(filePath2)) + { + await using (var fileStream2 = File.OpenRead(filePath2)) + { + await attachmentsBulkInsert.StoreAsync( + attachmentName2, fileStream2, "text/plain"); + } + } + } +} +``` + + +```csharp +public class User +{ + public string Id { get; set; } + public string Name { get; set; } + public int Age { get; set; } +} +``` + + + +--- + +### Add attachments to existing documents using Bulk Insert + + + +```csharp +// Query for existing user documents: +List users; + +using (var session = store.OpenSession()) +{ + users = session.Query() + .Where(u => u.Age < 30) + .ToList(); +} + +var basePath = @"C:\temp"; // folder containing files to attach + +// Create a BulkInsert instance +using (var bulkInsertObj = store.BulkInsert()) +{ + foreach (var user in users) + { + // Use the existing document id + var docId = user.Id; + + // Prepare attachment name + var attachmentName = $"user_{user.Id}.png"; + + var filePath = Path.Combine(basePath, attachmentName); + + // Call 'AttachmentsFor' to get the BulkInsert attachment interface + // Pass the document ID for which to attach the files + var attachmentsBulkInsert = bulkInsertObj.AttachmentsFor(docId); + + // Attach the file: + if (File.Exists(filePath)) + { + using (var fileStream = File.OpenRead(filePath)) + { + // Call 'Store' to add the file to the BulkInsert instance + // The data stored in BulkInsert will be streamed to the server in batches + attachmentsBulkInsert.Store(attachmentName, fileStream, "image/png"); + } + } + } +} +``` + + +```csharp +// Query for existing user documents: +List users; + +using (var asyncSession = store.OpenAsyncSession()) +{ + users = await asyncSession.Query() + .Where(u => u.Age < 30) + .ToListAsync(); +} + +var basePath = @"C:\temp"; // folder containing files to attach + +// Create a BulkInsert instance +await using (var bulkInsertObj = store.BulkInsert()) +{ + foreach (var user in users) + { + // Use the existing document id + var docId = user.Id; + + // Prepare attachment name + var attachmentName = $"user_{user.Id}.png"; + + var filePath = Path.Combine(basePath, attachmentName); + + // Call 'AttachmentsFor' to get the BulkInsert attachment interface + // Pass the document ID for which to attach the files + var attachmentsBulkInsert = bulkInsertObj.AttachmentsFor(docId); + + // Attach the file: + if (File.Exists(filePath)) + { + await using (var fileStream = File.OpenRead(filePath)) + { + // Call 'StoreAsync' to add the file to the BulkInsert instance + // The data stored in BulkInsert will be streamed to the server in batches + await attachmentsBulkInsert.StoreAsync(attachmentName, fileStream, "image/png"); + } + } + } +} +``` + + +```csharp +public class User +{ + public string Id { get; set; } + public string Name { get; set; } + public int Age { get; set; } +} +``` + + + + + + + +* Using the Bulk Insert operation, you can add attachments and mark them to be stored in **remote storage**. + +* Ensure that the remote attachment feature is enabled for the database and that remote destinations are configured. + See [Configure remote attachments](../../../document-extensions/attachments/configure-remote-attachments). + +* Inserting attachments via Bulk Insert for remote storage is similar to inserting attachments for local storage, + except that you need to specify the remote **destination identifier** and the **time to upload**. + +* Once the attachment is added to the document on the server, its content is first stored locally. + The background task will then upload it to the specified remote destination when the scheduled time arrives, + as described in [Remote attachment storage flow](../../../document-extensions/attachments/store-attachments/store-attachments-remote#remote-attachment-storage-flow). + + + +```csharp +var basePath = @"C:\temp"; // folder containing files to attach + +// Create a BulkInsert instance +using (var bulkInsertObj = store.BulkInsert()) +{ + for (int i = 0; i < 10; i++) + { + var docId = $"users/{i}"; + var metadata = new MetadataAsDictionary + { + // Specify the target collection for the new documents + // If not specified, documents will be created under the '@empty' collection + [Constants.Documents.Metadata.Collection] = "users" + }; + + // Store a new user document in the BulkInsert instance + bulkInsertObj.Store(new { Name = $"User {i}" }, docId, metadata); + + // Prepare attachment name and path + var attachmentName = $"user_{i}.png"; + var filePath = Path.Combine(basePath, attachmentName); + + // Call 'AttachmentsFor' to get the BulkInsert attachment interface + // Pass the document ID for which to attach the files + var attachmentsBulkInsert = bulkInsertObj.AttachmentsFor(docId); + + // Attach the file: + if (File.Exists(filePath)) + { + using (var fileStream = File.OpenRead(filePath)) + { + // Use the overload that accepts StoreAttachmentParameters + // so we can specify the remote parameters + var parameters = new StoreAttachmentParameters(attachmentName, fileStream) + { + ContentType = "image/png", + RemoteParameters = new RemoteAttachmentParameters( + "my-amazon-storage", // Remote destination ID + DateTime.UtcNow.AddDays(1)) // When to upload to remote storage + }; + + // Call 'Store' to add the file to the BulkInsert instance + // Documents and attachments will be streamed to the server in batches + attachmentsBulkInsert.Store(parameters); + } + } + } +} +``` + + +```csharp +var basePath = @"C:\temp"; // folder containing files to attach + +// Create a BulkInsert instance +await using (var bulkInsertObj = store.BulkInsert()) +{ + for (int i = 0; i < 10; i++) + { + var docId = $"users/{i}"; + var metadata = new MetadataAsDictionary + { + // Specify the target collection for the new documents + // If not specified, documents will be created under the '@empty' collection + [Constants.Documents.Metadata.Collection] = "users" + }; + + // Store a new user document in the BulkInsert instance + await bulkInsertObj.StoreAsync(new { Name = $"User {i}" }, docId, metadata); + + // Prepare attachment name and path + var attachmentName = $"user_{i}.png"; + var filePath = Path.Combine(basePath, attachmentName); + + // Call 'AttachmentsFor' to get the BulkInsert attachment interface + // Pass the document ID for which to attach the files + var attachmentsBulkInsert = bulkInsertObj.AttachmentsFor(docId); + + // Attach the file: + if (File.Exists(filePath)) + { + await using (var fileStream = File.OpenRead(filePath)) + { + // Use the overload that accepts StoreAttachmentParameters + // so we can specify the remote parameters + var parameters = new StoreAttachmentParameters(attachmentName, fileStream) + { + ContentType = "image/png", + RemoteParameters = new RemoteAttachmentParameters( + "my-amazon-storage", // Remote destination ID + DateTime.UtcNow.AddDays(1)) // When to upload to remote storage + }; + + // Call 'StoreAsync' to add the file to the BulkInsert instance + // Documents and attachments will be streamed to the server in batches + await attachmentsBulkInsert.StoreAsync(parameters); + } + } + } +} +``` + + +```csharp +public class User +{ + public string Id { get; set; } + public string Name { get; set; } + public int Age { get; set; } +} +``` + + + + + + + + +```csharp +public AttachmentsBulkInsert AttachmentsFor(string id) +``` + + +| Parameter | Type | Description | +|-----------|----------|---------------------------------------------------------------| +| **id** | `string` | The ID of the document to which the attachment will be added. | + + +```csharp +// Available overloads: + +public void Store(string name, Stream stream, string contentType = null) +public Task StoreAsync( + string name, Stream stream, string contentType = null, CancellationToken token = default) + +public void Store(StoreAttachmentParameters parameters) +public Task StoreAsync( + StoreAttachmentParameters parameters, CancellationToken token = default) +``` + + +| Parameter | Type | Description | +|-----------------|----------|-------------------------------------------------------------| +| **name** | `string` | Name of attachment. | +| **stream** | `Stream` | The stream containing the binary content of the attachment. | +| **contentType** | `string` | The MIME type of the attachment (optional). | +| **parameters** | `StoreAttachmentParameters` | An object that encapsulates all parameters required to store an attachment. | + + +```csharp +public class StoreAttachmentParameters +{ + public string Name { get; set; } // The name of the attachment to store. + public Stream Stream { get; set; } // The stream containing the attachment data. + public string ChangeVector { get; set; } + public string ContentType { get; set; } + + // Set to null or omit to store the attachment locally + public RemoteAttachmentParameters RemoteParameters { get; set; } +} +``` + + + +```csharp +public class RemoteAttachmentParameters +{ + // The scheduled date and time when the attachment should be uploaded to the remote destination. + // Preferably in UTC. + public DateTime At { get; set; } + + // The identifier of the remote storage destination to which the attachment should be uploaded. + public string Identifier { get; set; } +} +``` + + + \ No newline at end of file diff --git a/docs/document-extensions/attachments/_bulk-insert-nodejs.mdx b/docs/document-extensions/attachments/store-attachments/content/_bulk-insert-nodejs.mdx similarity index 88% rename from docs/document-extensions/attachments/_bulk-insert-nodejs.mdx rename to docs/document-extensions/attachments/store-attachments/content/_bulk-insert-nodejs.mdx index 8061e57034..7255aa73e3 100644 --- a/docs/document-extensions/attachments/_bulk-insert-nodejs.mdx +++ b/docs/document-extensions/attachments/store-attachments/content/_bulk-insert-nodejs.mdx @@ -5,13 +5,13 @@ import CodeBlock from '@theme/CodeBlock'; -* [BulkInsert](../../client-api/bulk-insert/how-to-work-with-bulk-insert-operation.mdx) is RavenDB's high-performance data insertion operation. +* [BulkInsert](../../../client-api/bulk-insert/how-to-work-with-bulk-insert-operation.mdx) is RavenDB's high-performance data insertion operation. Use its `attachmentsFor` interface to add attachments to documents with great speed. * In this page: - * [Usage flow](../../document-extensions/attachments/bulk-insert.mdx#usage-flow) - * [Usage example](../../document-extensions/attachments/bulk-insert.mdx#usage-example) - * [Syntax](../../document-extensions/attachments/bulk-insert.mdx#syntax) + * [Usage flow](../../../document-extensions/attachments/bulk-insert.mdx#usage-flow) + * [Usage example](../../../document-extensions/attachments/bulk-insert.mdx#usage-example) + * [Syntax](../../../document-extensions/attachments/bulk-insert.mdx#syntax) @@ -29,8 +29,6 @@ import CodeBlock from '@theme/CodeBlock'; If an attachment with the specified name already exists on the document, the bulk insert operation will overwrite it. - - ## Usage example In this example, we attach a file to all User documents that match a query. @@ -90,8 +88,6 @@ try {
- - ## Syntax @@ -117,8 +113,4 @@ store(name, bytes, contentType); |---------------|----------|------------------------------------| | `name` | `string` | Name of attachment | | `bytes` | `Buffer` | The attachment's content | -| `contentType` | `string` | Type of attachment (default: null) | - - - - +| `contentType` | `string` | Type of attachment (default: null) | \ No newline at end of file diff --git a/docs/document-extensions/attachments/_bulk-insert-python.mdx b/docs/document-extensions/attachments/store-attachments/content/_bulk-insert-python.mdx similarity index 85% rename from docs/document-extensions/attachments/_bulk-insert-python.mdx rename to docs/document-extensions/attachments/store-attachments/content/_bulk-insert-python.mdx index f79c683dbe..344a93c9c6 100644 --- a/docs/document-extensions/attachments/_bulk-insert-python.mdx +++ b/docs/document-extensions/attachments/store-attachments/content/_bulk-insert-python.mdx @@ -5,14 +5,13 @@ import CodeBlock from '@theme/CodeBlock'; -* [bulk_insert](../../client-api/bulk-insert/how-to-work-with-bulk-insert-operation.mdx) is RavenDB's +* [bulk_insert](../../../client-api/bulk-insert/how-to-work-with-bulk-insert-operation.mdx) is RavenDB's high-performance data insertion operation. Use its `attachments_for` interface to add attachments to multiple documents with great speed. -* Use `store` * In this page: - * [Usage flow](../../document-extensions/attachments/bulk-insert.mdx#usage-flow) - * [Usage example](../../document-extensions/attachments/bulk-insert.mdx#usage-example) + * [Usage flow](../../../document-extensions/attachments/bulk-insert.mdx#usage-flow) + * [Usage example](../../../document-extensions/attachments/bulk-insert.mdx#usage-example) @@ -30,11 +29,10 @@ import CodeBlock from '@theme/CodeBlock'; If an attachment with the specified name already exists on the document, the bulk insert operation will overwrite it. - - ## Usage example In this example, we attach a file to all User documents that match a query. + {`# Choose user profiles for which to attach a file @@ -58,8 +56,4 @@ with store.bulk_insert() as bulk_insert: attachments_bulk_insert.store("AttachmentName", bytes_to_attach) `} - - - - - + \ No newline at end of file diff --git a/docs/document-extensions/attachments/store-attachments/content/_store-attachments-local-csharp.mdx b/docs/document-extensions/attachments/store-attachments/content/_store-attachments-local-csharp.mdx new file mode 100644 index 0000000000..5217917118 --- /dev/null +++ b/docs/document-extensions/attachments/store-attachments/content/_store-attachments-local-csharp.mdx @@ -0,0 +1,420 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; + + + +* This article focuses on **storing attachments locally**. + To learn about remote storage, see [Store attachments in remote storage](../../../document-extensions/attachments/store-attachments/store-attachments-remote). + +* In this article + * [Overview](../../../document-extensions/attachments/store-attachments/store-attachments-local#overview) + * [Store attachments using the **Studio**](../../../document-extensions/attachments/store-attachments/store-attachments-local#store-attachments-using-the-studio) + * [Store attachments using the **Client API**](../../../document-extensions/attachments/store-attachments/store-attachments-local#store-attachments-using-the-client-api) + * [Store via the Session](../../../document-extensions/attachments/store-attachments/store-attachments-local#store-attachments-via-the-session) + * [Store via an Operation](../../../document-extensions/attachments/store-attachments/store-attachments-local#store-attachments-via-an-operation) + * [The attachment metadata](../../../document-extensions/attachments/store-attachments/store-attachments-local#the-attachment-metadata) + * [Deduplicating local attachments](../../../document-extensions/attachments/store-attachments/store-attachments-local#deduplicating-local-attachments) + * [Syntax](../../../document-extensions/attachments/store-attachments/store-attachments-local#syntax) + + + + + +* **To store attachments**: + * First, create or load a document. + * Then, attach any number of files using the Studio or the Client API. + +* The document’s metadata is updated to reference each attachment that is added. + This metadata update is considered a document change, which causes the document to be replicated to all other nodes in the database group. + Any actions associated with the document change, such as indexing or ongoing tasks configured for the document, will be triggered. + Learn more in [Attachments and other features](../../todo). + +* When stored **locally**, attachments are not embedded within the document. + Instead, they are saved in a dedicated attachment storage area on the RavenDB server, separate from the document store. + This avoids bloating your JSON documents with large binary data and enables efficient storage and retrieval. + +* **Storing attachments with the same name**: + If you add an attachment with a name that is already associated with the document (case-insensitive), + the new content will replace the existing content in the local storage. + + + + + +You can add and manage attachments directly in the Studio. + +![Add attachment to local storage](../assets/add-attachment-to-local-storage.png) + +1. Open the _Document_ view. The example shows document _employees/1_. +2. Open the _Attachments_ tab in the document _Properties_ pane. +3. Click **Add Attachment** and select the file to attach. + The file will appear in the list and will be automatically associated with the document. +4. The Studio does not show detailed attachment metadata in the document editor. + It only shows the `@flags` property with the value `HasAttachments` in the document's `@metadata` section + to indicate that the document has attachments. +5. Click this JSON button to view the document's raw content, including its full metadata. + Learn more about the attachment metadata in [The attachment metadata](../../../document-extensions/attachments/store-attachments/store-attachments-local#the-attachment-metadata). + + + + + +### Store attachments via the Session + +* Use `session.Advanced.Attachments.Store` to associate an attachment with an **existing** document, + The document can be newly stored in the current session or loaded from the database. + An exception will be thrown if you attempt to add an attachment to a document that does not exist. + +* **Transaction support**: + + * Like documents, attachments are tracked by the session and are not sent to the server immediately. + They are persisted only when you call `session.SaveChanges()`, along with all other changes made in the session, + as part of a single [unit of work](../../../client-api/session/what-is-a-session-and-how-does-it-work#unit-of-work-pattern). + + * If you store both the document and its attachment in the same session, they are saved transactionally: + if saving the document fails, the attachment is not stored, and vice versa. + +#### Example + + + +```csharp +using (var session = store.OpenSession()) +{ + // Store a new document: + // ===================== + var employee1 = new Employee + { + FirstName = "John", + LastName = "Doe" + // ... other properties + }; + + session.Store(employee1, "employees/1"); + + // Or load an existing document: + // ============================= + var employee2 = session.Load("employees/2"); + + // Store attachments locally for new and existing documents: + // ========================================================= + + // Define paths to your source files + var attachmentPath1 = @"C:\temp\image1.png"; + var attachmentPath2 = @"C:\temp\notes.txt"; + + // Define the names under which the attachments will be stored + var attachmentName1 = "image1.png"; + var attachmentName2 = "notes.txt"; + + using (var stream1 = File.Open(attachmentPath1, FileMode.Open, FileAccess.Read)) + using (var stream2 = File.Open(attachmentPath2, FileMode.Open, FileAccess.Read)) + { + // Add attachments to the new document (employee1) + session.Advanced.Attachments.Store(employee1, attachmentName1, stream1, "image/png"); + session.Advanced.Attachments.Store(employee1, attachmentName2, stream2, "text/plain"); + + // Upon save changes: + // Document 'employees/1' will be created with 2 attachments + session.SaveChanges(); + } + + using (var stream1 = File.Open(attachmentPath1, FileMode.Open, FileAccess.Read)) + using (var stream2 = File.Open(attachmentPath2, FileMode.Open, FileAccess.Read)) + { + // Add attachments to the existing document (employee2) + session.Advanced.Attachments.Store(employee2, attachmentName1, stream1, "image/png"); + session.Advanced.Attachments.Store(employee2, attachmentName2, stream2, "text/plain"); + + // Upon save changes: + // 2 attachments will be added to the already existing document 'employees/2' + session.SaveChanges(); + } +} +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession()) +{ + // Store a new document: + // ===================== + var employee1 = new Employee + { + FirstName = "John", + LastName = "Doe" + // ... other properties + }; + + await asyncSession.StoreAsync(employee1, "employees/1"); + + // Or load an existing document: + // ============================= + var employee2 = await asyncSession.LoadAsync("employees/2"); + + // Store attachments locally for new and existing documents: + // ========================================================= + + // Define paths to your source files + var attachmentPath1 = @"C:\temp\image1.png"; + var attachmentPath2 = @"C:\temp\notes.txt"; + + // Define the names under which the attachments will be stored + var attachmentName1 = "image1.png"; + var attachmentName2 = "notes.txt"; + + await using (var stream1 = File.Open(attachmentPath1, FileMode.Open, FileAccess.Read)) + await using (var stream2 = File.Open(attachmentPath2, FileMode.Open, FileAccess.Read)) + { + // Add attachments to the new document (employee1) + asyncSession.Advanced.Attachments.Store(employee1, attachmentName1, stream1, "image/png"); + asyncSession.Advanced.Attachments.Store(employee1, attachmentName2, stream2, "text/plain"); + + // Save changes: + // Document 'employees/1' will be created with 2 attachments + await asyncSession.SaveChangesAsync(); + } + + await using (var stream1 = File.Open(attachmentPath1, FileMode.Open, FileAccess.Read)) + await using (var stream2 = File.Open(attachmentPath2, FileMode.Open, FileAccess.Read)) + { + // Add attachments to the existing document (employee2) + asyncSession.Advanced.Attachments.Store(employee2, attachmentName1, stream1, "image/png"); + asyncSession.Advanced.Attachments.Store(employee2, attachmentName2, stream2, "text/plain"); + + // Save changes: + // 2 attachments will be added to the already existing document 'employees/2' + await asyncSession.SaveChangesAsync(); + } +} +``` + + + +--- + +### Store attachments via an Operation + +* Use the `PutAttachmentOperation` operation to store an attachment outside the context of a session. + +* This is ideal when you need to add an attachment as an independent action and not as part of the transactional _SaveChanges()_ batch. + +* To store multiple attachments, call the operation separately for each attachment you want to add. + +* An exception will be thrown if you attempt to add an attachment to a document that does not exist. + +#### Example + + + +```csharp +// The path to your source file +var attachmentPath = @"C:\temp\image1.png"; + +// Open the file stream +using (var stream = File.Open(attachmentPath, FileMode.Open, FileAccess.Read)) +{ + var documentId = "employees/1"; + var attachmentName = "image1.png"; + var contentType = "image/png"; + + // Define the put attachment operation + var operation = new PutAttachmentOperation(documentId, attachmentName, stream, contentType); + + // Execute the operation by passing it to 'Operations.Send' + var result = store.Operations.Send(operation); +} +``` + + +```csharp +// The path to your source file +var attachmentPath = @"C:\temp\image1.png"; + +// Open the file stream +await using (var stream = new FileStream(attachmentPath, + FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true)) +{ + var documentId = "employees/1"; + var attachmentName = "image1.png"; + var contentType = "image/png"; + + // Define the put attachment operation + var operation = new PutAttachmentOperation(documentId, attachmentName, stream, contentType); + + // Execute the operation by passing it to 'Operations.SendAsync' + var result = await store.Operations.SendAsync(operation); +} +``` + + + + + + + +* When an attachment is added to a document, RavenDB updates the document’s `@metadata` to include information about the attachment within a dedicated `@attachments` section. + +* For example, the metadata of document _employees/1_ from the session's example above contains the following: + + +```json +"@metadata": { + "@attachments": [ + { + "Name": "image1.png", + "Hash": "6MsVgv2an8udIvA1GeRYg/kjvS47EZjb3xKCkwCPBqw=", + "ContentType": "image/png", + "Size": 332916 + }, + { + "Name": "notes.txt", + "Hash": "EjTWwES/SOdzvXkVlT3VIOsIwaiz0jsqOk+VuO/Sbq0=", + "ContentType": "text/plain", + "Size": 27 + } + ], + "@collection": "Employees", + "@change-vector": "A:42-5nIqMb8v3kGQLYhhU90jWg", + "@flags": "HasAttachments", + "@id": "employees/1", + "@last-modified": "2025-11-23T13:48:15.4922720Z" +} +``` + + +The properties listed in the `@attachments` array include the following information: + +`Name`: +The name of the attachment (e.g. photo.png). +`Hash`: +A Base64-encoded SHA-256 hash of the attachment’s content. +Used to detect and avoid storing the same attachment content more than once in the local storage (deduplication). +`ContentType`: +The MIME type of the attachment (e.g. image/png, application/pdf). +`Size`: +The size of the attachment in bytes. + + + + + +* RavenDB avoids storing duplicate binary content in its attachment storage by using content-based deduplication. + +* RavenDB computes a SHA-256 hash of the attachment’s binary content. + This hash is used as the key to the internal attachment storage. + + If multiple attachments (even with different names and across different documents) share the **same hash**, their binary content is stored only once on disk. + Each document still maintains its own metadata reference to the attachment, but the underlying stream is shared. + + A deduplicated stream is removed only when no attachments reference it anymore. + + + + + +### `PutAttachmentOperation` +Add an attachment to a document using a store operation. + + +```csharp +// Avialable overloads: +// =================== + +public PutAttachmentOperation( + string documentId, string name, Stream stream, string contentType = null, + RemoteAttachmentParameters remoteParameters = null, string changeVector = null) + +public PutAttachmentOperation(string documentId, StoreAttachmentParameters parameters) +``` + + +| Return value | Description | +|----------------------|---------------------------------------------| +| [AttachmentDetails](../../../document-extensions/attachments/store-attachments/store-attachments-local#attachmentdetails) | An object with the new attachment's details | + +--- + +### `Session.Advanced.Attachments.Store` +Add an attachment to a document using a session. + + +```csharp +// Avialable overloads: +// ==================== + +void Store(string documentId, StoreAttachmentParameters parameters); +void Store(string documentId, string name, Stream stream, string contentType = null); +void Store(object entity, string name, Stream stream, string contentType = null); +``` + + +--- + +| Parameter | Type | Description | +|----------------------|------------------------------|-------------| +| **documentId** | `string` | The ID of the document to which the attachment will be added. | +| **entity** | `object` | The tracked entity instance to which the attachment will be associated. | +| **name** | `string` | The name under which the attachment will be stored on the server. | +| **stream** | `Stream` | The stream containing the binary content of the attachment. | +| **contentType** | `string` | The MIME type of the attachment (optional).
Common examples include:
`image/png`, `application/pdf`, `text/plain`, `video/mp4`. | +| **parameters** | `StoreAttachmentParameters` | An object that encapsulates all parameters required to store an attachment. | +| **remoteParameters** | `RemoteAttachmentParameters` | Parameters for uploading the attachment to [Remote storage](../../../document-extensions/attachments/store-attachments/store-attachments-remote).
Set to _null_ or omit to store the attachment **locally**. | +| **changeVector** | `string` | The document's change vector used for concurrency control.
If set to `null`, no concurrency check will be performed and the attachment will be added regardless of the document's current state. | + + +```csharp +public class StoreAttachmentParameters +{ + public string Name { get; set; } // The name of the attachment to store. + public Stream Stream { get; set; } // The stream containing the attachment data. + public string ChangeVector { get; set; } + public string ContentType { get; set; } + + // Set to null or omit to store the attachment *locally* + public RemoteAttachmentParameters RemoteParameters { get; set; } +} +``` + + + +```csharp +public class RemoteAttachmentParameters +{ + // The scheduled date and time when the attachment should be uploaded to the remote destination. + // Preferably in UTC. + public DateTime At { get; set; } + + // The identifier of the remote storage destination to which the attachment should be uploaded. + public string Identifier { get; set; } +} +``` + + +--- + +### `AttachmentDetails` + + +```csharp +public class AttachmentDetails : AttachmentName +{ + public string ChangeVector; + public string DocumentId; +} + +public class AttachmentName +{ + public string Name; + public string Hash; + public string ContentType; + public long Size; +} +``` + + +
\ No newline at end of file diff --git a/docs/document-extensions/attachments/store-attachments/content/_store-attachments-local-java.mdx b/docs/document-extensions/attachments/store-attachments/content/_store-attachments-local-java.mdx new file mode 100644 index 0000000000..0ff19133eb --- /dev/null +++ b/docs/document-extensions/attachments/store-attachments/content/_store-attachments-local-java.mdx @@ -0,0 +1,208 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; + + + +* To store an attachment: + * First, create or load a document. + * Then, attach any number of files using the Studio or the Client API. + +* The document’s metadata is updated to reference each attachment that is added. + This metadata update is considered a document change, which causes the document to be replicated to all other nodes in the database group. + Any actions associated with the document change, such as indexing or ongoing tasks configured for the document, will be triggered. + +* When stored **locally**, attachments are not embedded within the document. + Instead, they are saved in a dedicated attachment storage area on the RavenDB server, separate from the document store. + This avoids bloating your JSON documents with large binary data and enables efficient storage and retrieval. + +* **Storing attachments with the same name**: + If you add an attachment with a name that is already associated with the document, + the new content will replace the existing content in the local storage. + +* In this article + * [Store attachment via the session](../../../document-extensions/attachments/store-attachments/store-attachments-local#store-attachment-via-the-session) + * [Store attachment via an operation](../../../document-extensions/attachments/store-attachments/store-attachments-local#store-attachment-via-an-operation) + + + + + +In order to store an attachment in RavenDB you need to create a document. Then you can attach an attachment to the document using the `session.advanced().attachments().store` method. + +Attachments, just like documents, are a part of the session and will only be saved on the Server when `DocumentSession.saveChanges` is executed (you can read more about saving changes in session [here](../../client-api/session/saving-changes.mdx)). + +--- + +### Syntax + +Attachments can be stored using one of the following `session.advanced().attachments().store` methods: + + + +{`void store(String documentId, String name, InputStream stream); + +void store(String documentId, String name, InputStream stream, String contentType); + +void store(Object entity, String name, InputStream stream); + +void store(Object entity, String name, InputStream stream, String contentType); +`} + + + +--- + +### Example + + + +{`try (IDocumentSession session = store.openSession()) \{ + try ( + FileInputStream file1 = new FileInputStream("001.jpg"); + FileInputStream file2 = new FileInputStream("002.jpg"); + FileInputStream file3 = new FileInputStream("003.jpg"); + FileInputStream file4 = new FileInputStream("004.mp4") + ) \{ + Album album = new Album(); + album.setName("Holidays"); + album.setDescription("Holidays travel pictures of the all family"); + album.setTags(new String[] \{ "Holidays Travel", "All Family" \}); + session.store(album, "albums/1"); + + session.advanced().attachments() + .store("albums/1", "001.jpg", file1, "image/jpeg"); + session.advanced().attachments() + .store("albums/1", "002.jpg", file2, "image/jpeg"); + session.advanced().attachments() + .store("albums/1", "003.jpg", file3, "image/jpeg"); + session.advanced().attachments() + .store("albums/1", "004.mp4", file4, "video/mp4"); + + session.saveChanges(); + \} +\} +`} + + + + + + + +This operation is used to put an attachment to a document. + +### Syntax + + + +{`PutAttachmentOperation(String documentId, String name, InputStream stream) + +PutAttachmentOperation(String documentId, String name, InputStream stream, String contentType) + +PutAttachmentOperation(String documentId, String name, InputStream stream, String contentType, String changeVector) +`} + + + + + +{`public class AttachmentDetails extends AttachmentName \{ + private String changeVector; + private String documentId; + + public String getChangeVector() \{ + return changeVector; + \} + + public void setChangeVector(String changeVector) \{ + this.changeVector = changeVector; + \} + + public String getDocumentId() \{ + return documentId; + \} + + public void setDocumentId(String documentId) \{ + this.documentId = documentId; + \} +\} + +public class AttachmentName \{ + private String name; + private String hash; + private String contentType; + private long size; + + public String getName() \{ + return name; + \} + + public void setName(String name) \{ + this.name = name; + \} + + public String getHash() \{ + return hash; + \} + + public void setHash(String hash) \{ + this.hash = hash; + \} + + public String getContentType() \{ + return contentType; + \} + + public void setContentType(String contentType) \{ + this.contentType = contentType; + \} + + public long getSize() \{ + return size; + \} + + public void setSize(long size) \{ + this.size = size; + \} +\} +`} + + + +| Parameter | | | +|------------------| ------------- | ----- | +| **documentId** | String | ID of a document which will contain an attachment | +| **name** | String | Name of an attachment | +| **stream** | InputStream | Stream contains attachment raw bytes | +| **contentType** | String | MIME type of attachment | +| **changeVector** | String | Entity changeVector, used for concurrency checks (`null` to skip check) | + +| Return Value | | +| ------------- | ----- | +| **ChangeVector** | Change vector of created attachment | +| **DocumentId** | ID of document | +| **Name** | Name of created attachment | +| **Hash** | Hash of created attachment | +| **ContentType** | MIME content type of attachment | +| **Size** | Size of attachment | + +--- + +### Example + + + +{`AttachmentDetails attachmentDetails = store + .operations().send(new PutAttachmentOperation("orders/1-A", + "invoice.pdf", + stream, + "application/pdf")); +`} + + + + \ No newline at end of file diff --git a/docs/document-extensions/attachments/store-attachments/content/_store-attachments-local-nodejs.mdx b/docs/document-extensions/attachments/store-attachments/content/_store-attachments-local-nodejs.mdx new file mode 100644 index 0000000000..caf8fcb8db --- /dev/null +++ b/docs/document-extensions/attachments/store-attachments/content/_store-attachments-local-nodejs.mdx @@ -0,0 +1,171 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel' + + + +* To store an attachment: + * First, create or load a document. + * Then, attach any number of files using the Studio or the Client API. + +* The document’s metadata is updated to reference each attachment that is added. + This metadata update is considered a document change, which causes the document to be replicated to all other nodes in the database group. + Any actions associated with the document change, such as indexing or ongoing tasks configured for the document, will be triggered. + +* When stored **locally**, attachments are not embedded within the document. + Instead, they are saved in a dedicated attachment storage area on the RavenDB server, separate from the document store. + This avoids bloating your JSON documents with large binary data and enables efficient storage and retrieval. + +* **Storing attachments with the same name**: + If you add an attachment with a name that is already associated with the document, + the new content will replace the existing content in the local storage. + +* In this article + * [Store attachment via the session](../../../document-extensions/attachments/store-attachments/store-attachments-local#store-attachment-via-the-session) + * [Store attachment via an operation](../../../document-extensions/attachments/store-attachments/store-attachments-local#store-attachment-via-an-operation) + + + + + +* In order to store an attachment you need to create or load a document. + Then you can attach an attachment to the document using the `session.advanced.attachments.store()` method. +* Attachments, just like documents, are a part of the session and will only be saved on the server when `saveChanges()` is executed. + +--- + +### Syntax + +Attachments can be stored using one of the following `session.advanced.attachments.store()` methods: + + + +{`session.advanced.attachments.store(documentId, name, stream, [contentType]); +session.advanced.attachments.store(entity, name, stream, [contentType]); +`} + + + +| Parameter | Type | Description | +| ------------- | ------------- | ----- | +| **entity** or **documentId** | object or string | instance of the entity or the entity ID | +| **name** | string | attachment name | +| **stream** | `Readable` or `Buffer` | attachment content | +| **contentType** | string | attachment content type | + +--- + +### Example + + + +{`const session = store.openSession(); + +const file1 = fs.createReadStream("001.jpg"); +const file2 = fs.createReadStream("002.jpg"); +const file3 = fs.createReadStream("003.jpg"); +const file4 = fs.createReadStream("004.mp4"); + +const album = new Album(); +album.name = "Holidays"; +album.description = "Holidays travel pictures of the all family"; +album.tags = [ "Holidays Travel", "All Family" ]; +await session.store(album, "albums/1"); + +session.advanced.attachments + .store("albums/1", "001.jpg", file1, "image/jpeg"); +session.advanced.attachments + .store("albums/1", "002.jpg", file2, "image/jpeg"); +session.advanced.attachments + .store("albums/1", "003.jpg", file3, "image/jpeg"); +session.advanced.attachments + .store("albums/1", "004.mp4", file4, "video/mp4"); + +await session.saveChanges(); +`} + + + + + + + +Use the `PutAttachmentOperation` to add an attachment to a document. + +--- + +### Put attachment example + + + +{`// Prepare content to attach +const text = "Some content..."; +const byteArray = Buffer.from(text); + +// Define the put attachment operation +const putAttachmentOp = new PutAttachmentOperation( + "employees/1-A", "attachmentName.txt", byteArray, "text/plain"); + +// Execute the operation by passing it to operations.send +const attachmentDetails = await documentStore.operations.send(putAttachmentOp); +`} + + + +--- + +### Syntax + + + +{`// Available overloads: +const putAttachmentOp = new PutAttachmentOperation(documentId, name, stream); +const putAttachmentOp = new PutAttachmentOperation(documentId, name, stream, contentType); +const putAttachmentOp = new PutAttachmentOperation(documentId, name, stream, contentType, changeVector); +`} + + + +| Parameter | Type | Description | +|------------------|------------------------------|-----------------------------------------------------------------------------------| +| __documentId__ | `string` | Document ID to which the attachment will be added | +| __name__ | `string` | Name of attachment to put | +| __stream__ | `stream.Readable` / `Buffer` | A stream that contains the raw bytes of the attachment | +| __contentType__ | `string` | Content type of attachment | +| __changeVector__ | `string` | ChangeVector of attachment,
used for concurrency checks (`null` to skip check) | + +| Return Value of `store.operations.send(putAttachmentOp)` | | +|----------------------------------------------------------|---------------------------------------------| +| `object` | An object with the new attachment's details | + + + +{`// The AttachmentDetails object: +// ============================= +\{ + // Change vector of attachment + changeVector; // string + + // ID of the document that contains the attachment + documentId?; // string + + // Name of attachment + name; // string; + + // Hash of attachment + hash; // string; + + // Content type of attachment + contentType; // string + + // Size of attachment + size; // number +\} +`} + + + +
\ No newline at end of file diff --git a/docs/document-extensions/attachments/_storing-php.mdx b/docs/document-extensions/attachments/store-attachments/content/_store-attachments-local-php.mdx similarity index 100% rename from docs/document-extensions/attachments/_storing-php.mdx rename to docs/document-extensions/attachments/store-attachments/content/_store-attachments-local-php.mdx diff --git a/docs/document-extensions/attachments/_storing-python.mdx b/docs/document-extensions/attachments/store-attachments/content/_store-attachments-local-python.mdx similarity index 100% rename from docs/document-extensions/attachments/_storing-python.mdx rename to docs/document-extensions/attachments/store-attachments/content/_store-attachments-local-python.mdx diff --git a/docs/document-extensions/attachments/store-attachments/content/_store-attachments-remote-csharp.mdx b/docs/document-extensions/attachments/store-attachments/content/_store-attachments-remote-csharp.mdx new file mode 100644 index 0000000000..ed1ee2bde1 --- /dev/null +++ b/docs/document-extensions/attachments/store-attachments/content/_store-attachments-remote-csharp.mdx @@ -0,0 +1,571 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; + + + +* This article focuses on **storing attachments in a remote storage**. + To learn about local storage, see [Store attachments in local storage](../../../document-extensions/attachments/store-attachments/store-attachments-local). + +* A document can have any number of attachments, including both local attachments (stored in RavenDB) + and remote attachments (stored in external storage such as Amazon S3 or Azure. + +* **To store attachments in remote storage**, first enable the feature and configure one or more remote destinations. + See [Configure remote attachments](../../../document-extensions/attachments/configure-remote-attachments). + +* In this article + * [Overview](../../../document-extensions/attachments/store-attachments/store-attachments-remote#overview) + * [Remote attachment upload - flow](../../../document-extensions/attachments/store-attachments/store-attachments-remote#remote-attachment-upload---flow) + * [Remote attachment upload – considerations](../../../document-extensions/attachments/store-attachments/store-attachments-remote#remote-attachment-upload--considerations) + * [Store attachments remotely - using the **Studio**](../../../document-extensions/attachments/store-attachments/store-attachments-remote#store-attachments-remotely-using-the-studio) + * [Store attachments remotely - using the **Client API**](../../../document-extensions/attachments/store-attachments/store-attachments-remote#store-attachments-remotely-using-the-client-api) + * [Store via the Session](../../../document-extensions/attachments/store-attachments/store-attachments-remote#store-attachments-remotely---via-the-session) + * [Store via an Operation](../../../document-extensions/attachments/store-attachments/store-attachments-remote#store-attachments-remotely---via-an-operation) + * [Schedule existing attachments for remote upload](../../../document-extensions/attachments/store-attachments/store-attachments-remote#schedule-existing-attachments-for-remote-upload) + * [The attachment metadata](../../../document-extensions/attachments/store-attachments/store-attachments-remote#the-attachment-metadata) + * [Deduplicating remote attachments](../../../document-extensions/attachments/store-attachments/store-attachments-remote#deduplicating-remote-attachments) + * [Syntax](../../../document-extensions/attachments/store-attachments/store-attachments-remote#syntax) + + + + + +### Remote attachment upload - flow + +* Ensure the Remote Attachments feature is enabled in the database and that remote destinations are configured. + See [Configure remote attachments](../../../document-extensions/attachments/configure-remote-attachments). + +* Create or load a document. + +* Add an attachment to the document using the Studio or the Client API, specifying: + * The remote destination identifier + * The desired upload time + +* The attachment is first **stored locally** in RavenDB’s dedicated attachment storage, + and the document's metadata is updated with information about the attachment and the remote upload parameters. + +* This metadata update is treated as a document update, causing the document to replicate to all nodes in the database group. + As a result, the attachment content is stored locally on all nodes. + +* RavenDB runs a **background task** that periodically scans the database for attachments marked for remote upload. + If the upload time of the attachment has passed when the task runs, the task uploads the attachment to the specified remote storage. + +* **After a successful upload:**: + * The document’s metadata is updated to indicate that the attachment was uploaded. + * The attachment is **deleted from the local storage** of the server that performed the upload. + * The document replicates again, and all other nodes delete their local copies of the attachment. + * Any actions triggered by the document change, such as indexing or ongoing tasks configured for the document, will be executed. + Learn more in [Attachments and other features](../../../document-extensions/attachments/attachments-and-other-features). + +--- + +### Remote attachment upload – considerations + +* **Can the upload process be canceled?** + You can stop the upload process by disabling the remote destination in the remote attachments settings, + or by temporarily disabling the remote attachments feature entirely. (See [Configure remote attachments](../../../document-extensions/attachments/configure-remote-attachments)). + Alternatively, you can remove the remote destination configuration if it is no longer needed. + +* **Can a remote attachment be pulled back into local storage?** + There is no built-in mechanism to reverse the upload. + To "un-remote" an attachment, you'll need to retrieve it from the remote storage (see [Get attachments](../../../document-extensions/attachments/get-attachments)), + and then store it again in the database as a local attachment (see [Store attachments in local storage](../../../document-extensions/attachments/store-attachments/store-attachments-local)). + +* **Why is my attachment not uploading?** + Remote upload will not occur in the following cases: + * Configuration issues: + The Remote Attachments feature is disabled, or the specified destination is disabled, + or the destination identifier is invalid or not configured. See [Configure remote attachments](../../../document-extensions/attachments/configure-remote-attachments). + * The upload time has not yet arrived. + * The server cannot connect to the remote storage destination. + * Revisions are enabled for the document’s collection. See [Remote attachments and revisions](../../../document-extensions/attachments/attachments-and-other-features#attachments-revisions). + +* **What happens if the upload fails?** + If the upload attempt fails (e.g., due to a network issue or misconfiguration), + the background task will try again during the next scheduled scan. + However, if the attachment fails to upload more than 3 times, RavenDB will raise an alert and remove it from the upload queue. + + + + + +![Add attachment to remote storage 1](../assets/add-attachment-to-remote-storage-1.png) + +1. Open the _Document_ view. The example shows document _employees/1_. +2. Open the _Attachments_ tab in the document _Properties_ pane. +3. Click the dropdown menu next to the _Add Attachment_ button and select **Add attachment to remote storage**. + +![Add attachment to remote storage 2](../assets/add-attachment-to-remote-storage-2.png) + +1. **Add an attachment**: + Drag a file here or click to browse. +2. **Remote destination identifier**: + Select or enter the custom destination identifier defined for the remote storage. +3. **Schedule upload time**: + Pick the date and time when the attachment should be uploaded to the remote storage. +4. **Save the attachment with remote settings** + Clicking _Save_ adds the attachment to the document along with the specified remote upload configuration. + +![Add attachment to remote storage 3](../assets/add-attachment-to-remote-storage-3.png) + +1. The file will appear in the list and will be automatically associated with the document. + Hover over this entry to see the attachment details. +2. This tooltip displays the **attachment details**: + * `Upload time`: The scheduled time when the attachment will be uploaded to the remote storage. + * `Destination ID`: The identifier of the remote destination where the attachment will be uploaded. + * `Flags`: While the file is still stored locally and pending upload, this value is `None`. +3. The Studio does not show detailed attachment metadata in the document editor. + It only shows the `@flags` property with the value `HasAttachments` in the document's `@metadata` section + to indicate that the document has attachments. +4. Click this JSON button to view the document's raw content, including its full metadata. + Learn more about the attachment metadata in [The attachment metadata](../../../document-extensions/attachments/store-attachments/store-attachments-remote#the-attachment-metadata). + +![Add attachment to remote storage 4](../assets/add-attachment-to-remote-storage-4.png) + + + + + +### Store attachments remotely - via the Session + +* Use `session.Advanced.Attachments.Store` to associate an attachment with an **existing** document, + The document can be newly stored in the current session or loaded from the database. + An exception will be thrown if you attempt to add an attachment to a document that does not exist. + +* When storing remote attachments, you must specify the **destination identifier** and the **scheduled upload time**. + The destination identifier must be pre-configured. See [Configure remote attachments](../../../document-extensions/attachments/configure-remote-attachments). + +* **Transaction support**: + + * Like documents, attachments are tracked by the session and are not sent to the server immediately. + They are persisted only when you call `session.SaveChanges()`, along with all other changes made in the session, + as part of a single [unit of work](../../../client-api/session/what-is-a-session-and-how-does-it-work#unit-of-work-pattern). + + * If you store both the document and its attachment in the same session, they are saved transactionally: + if saving the document fails, the attachment is not stored, and vice versa. + +#### Example + + + +```csharp +using (var session = store.OpenSession()) +{ + // Store a new document (Or load an existing document): + // ==================================================== + var employee = new Employee + { + FirstName = "John", + LastName = "Doe" + // ... other properties + }; + + session.Store(employee, "employees/1"); + + // Store attachments in a remote destination: + // ========================================== + + // Define paths to your source files + var attachmentPath1 = @"C:\temp\image1.png"; + var attachmentPath2 = @"C:\temp\notes.txt"; + + // Define the names used to reference the attachments in the document + var attachmentName1 = "image1.png"; + var attachmentName2 = "notes.txt"; + + // Define the remote parameters for the attachments + var remoteParameters = new RemoteAttachmentParameters( + identifier: "my-amazon-storage", // The remote destination ID you’ve defined + at: DateTime.UtcNow.AddDays(1)); // When to upload to the remote storage + + using (var stream1 = File.Open(attachmentPath1, FileMode.Open, FileAccess.Read)) + using (var stream2 = File.Open(attachmentPath2, FileMode.Open, FileAccess.Read)) + { + var storeParameters1 = new StoreAttachmentParameters(attachmentName1, stream1) + { + RemoteParameters = remoteParameters, + ContentType = "image/png" + }; + + var storeParameters2 = new StoreAttachmentParameters(attachmentName2, stream2) + { + RemoteParameters = remoteParameters, + ContentType = "text/plain" + }; + + // Add the attachments to the document + session.Advanced.Attachments.Store("employees/1", storeParameters1); + session.Advanced.Attachments.Store("employees/1", storeParameters2); + + session.SaveChanges(); + + // The attachments will be stored locally + // and then uploaded to the remote destination at the scheduled time. + // After the upload completes, RavenDB will delete them from the local storage. + + // The document itself will continue to reference the attachment metadata, + // allowing future operations like retrieving the data from the remote location. + } +} +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession()) +{ + // Store a new document (Or load an existing document): + // ==================================================== + var employee = new Employee + { + FirstName = "John", + LastName = "Doe" + // ... other properties + }; + + await asyncSession.StoreAsync(employee, "employees/1"); + + // Store attachments in a remote destination: + // ========================================== + + // Define paths to your source files + var attachmentPath1 = @"C:\temp\image1.png"; + var attachmentPath2 = @"C:\temp\notes.txt"; + + // Define the names used to reference the attachments in the document + var attachmentName1 = "image1.png"; + var attachmentName2 = "notes.txt"; + + // Define the remote parameters for the attachments + var remoteParameters = new RemoteAttachmentParameters( + identifier: "my-amazon-storage", // The remote destination ID you’ve defined + at: DateTime.UtcNow.AddDays(1)); // When to upload to the remote storage + + using (var stream1 = File.Open(attachmentPath1, FileMode.Open, FileAccess.Read)) + using (var stream2 = File.Open(attachmentPath2, FileMode.Open, FileAccess.Read)) + { + var storeParameters1 = new StoreAttachmentParameters(attachmentName1, stream1) + { + RemoteParameters = remoteParameters, + ContentType = "image/png" + }; + + var storeParameters2 = new StoreAttachmentParameters(attachmentName2, stream2) + { + RemoteParameters = remoteParameters, + ContentType = "text/plain" + }; + + // Add the attachments to the document + asyncSession.Advanced.Attachments.Store("employees/1", storeParameters1); + asyncSession.Advanced.Attachments.Store("employees/1", storeParameters2); + + await asyncSession.SaveChangesAsync(); + + // The attachments will be stored locally + // and then uploaded to the remote destination at the scheduled time. + // After the upload completes, RavenDB will delete them from the local storage. + + // The document itself will continue to reference the attachment metadata, + // allowing future operations like retrieving the data from the remote location. + } +} +``` + + + +--- + +### Store attachments remotely - via an Operation + +* Use the `PutAttachmentOperation` operation to store an attachment outside the context of a session. + +* This is ideal when you need to add an attachment as an independent action and not as part of the transactional _SaveChanges()_ batch. + +* To store multiple attachments, call the operation separately for each attachment you want to add. + +* An exception will be thrown if you attempt to add an attachment to a document that does not exist. + +#### Example + + + +```csharp +// The path to your source file +var attachmentPath = @"C:\temp\image1.png"; + +using (var stream = File.Open(attachmentPath, FileMode.Open, FileAccess.Read)) +{ + var documentId = "employees/1"; + + // The name used to reference the attachment in the document + var attachmentName = "image1.png"; + + var contentType = "image/png"; + // Define the remote parameters for the attachments + var remoteParameters = new RemoteAttachmentParameters( + identifier: "my-amazon-storage", // The remote destination ID you’ve defined + at: DateTime.UtcNow.AddDays(1)); // When to upload to the remote storage + + var storeParams = new StoreAttachmentParameters(attachmentName, stream) + { + RemoteParameters = remoteParameters, + ContentType = "image/png" + }; + + // Define the put attachment operation + var operation = new PutAttachmentOperation(documentId, storeParams); + + // Execute the operation by passing it to 'Operations.Send' + var result = store.Operations.Send(operation); +} +``` + + +```csharp +// The path to your source file +var attachmentPath = @"C:\temp\image1.png"; + +// Open the file asynchronously as a FileStream +await using (var stream = new FileStream(attachmentPath, + FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true)) +{ + var documentId = "employees/1"; + + // The name used to reference the attachment in the document + var attachmentName = "image1.png"; + + var contentType = "image/png"; + + // Define the remote parameters for the attachments + var remoteParameters = new RemoteAttachmentParameters( + identifier: "my-amazon-storage", // The remote destination ID you’ve defined + at: DateTime.UtcNow.AddDays(1)); // When to upload to the remote storage + + var storeParams = new StoreAttachmentParameters(attachmentName, stream) + { + RemoteParameters = remoteParameters, + ContentType = "image/png" + }; + + // Define the put attachment operation + var operation = new PutAttachmentOperation(documentId, storeParams); + + // Execute the operation by passing it to 'Operations.SendAsync' + var result = await store.Operations.SendAsync(operation); +} +``` + + + + + + + +* You can schedule existing **local** attachments for remote upload using a **patch operation**. + Learn more about patching in general in [Single document patch operations](../../../client-api/operations/patching/single-document). + +* In the patch script, specify: + * The name of the existing local attachment to mark + * The remote destination identifier + * The scheduled upload time + +* Once patching is successful: + * The document's metadata is updated with the requested remote attachment parameters, + as described in [The attachment metadata](../../../document-extensions/attachments/store-attachments/store-attachments-remote#the-attachment-metadata). + + * If the feature is enabled and destinations are defined (see [Configure remote attachments](../../../document-extensions/attachments/configure-remote-attachments)), + the background task will upload the attachment to the specified remote destination when the scheduled time arrives. + + + +```csharp +// Define the path request +var patch = new PatchRequest +{ + // The script to execute: + Script = "attachments(this, args.name).remote(args.identifier, args.at);", + + // Provide values for the script arguments: + Values = + { + // The name of the existing attachment to mark for remote upload + { "name", "image1.png" }, + + // The remote destination identifier + { "identifier", "my-amazon-storage" }, + + // The scheduled time for uploading to remote storage + { "at", DateTime.UtcNow.AddDays(1) }, + } +}; + +// Define the patch operation +// Specify the document that contains the attachment to mark for remote upload +var patchOperation = new PatchOperation("employees/1", null, patch); + +// Execute the operation by passing it to 'Operations.Send' +PatchStatus patchResult = store.Operations.Send(patchOperation); + +// Possible outcomes: +// ================== + +// * PatchStatus.Patched: +// The attachment was successfully marked as remote. +// The actual upload will be performed by the background task + +// * PatchStatus.NotModified: +// The attachment does not exist; no exception is thrown. + +// * PatchStatus.DocumentDoesNotExist: +// The document does not exist; no exception is thrown. +``` + + +```csharp +// Define the path request +var patch = new PatchRequest +{ + // The script to execute: + Script = "attachments(this, args.name).remote(args.identifier, args.at);", + + // Provide values for the script arguments: + Values = + { + // The name of the existing attachment to mark for remote upload + { "name", "image1.png" }, + + // The remote destination identifier + { "identifier", "my-amazon-storage" }, + + // The scheduled time for uploading to remote storage + { "at", DateTime.UtcNow.AddDays(1) }, + } +}; + +// Define the patch operation +// Specify the document that contains the attachment to mark for remote upload +var patchOperation = new PatchOperation("employees/1", null, patch); + +// Execute the operation by passing it to 'Operations.SendAsync' +PatchStatus patchResult = await store.Operations.SendAsync(patchOperation); +``` + + + + + + + +* When an attachment is added to a document, RavenDB updates the document’s `@metadata` to include information about the attachment within a dedicated `@attachments` section. + +* Note the `RemoteParameters` object added to the metadata when the attachment is configured for **remote storage**. + +* For example, the metadata of document _employees/1_ from the session's example above contains the following: + + + +```json +"@metadata": { + "@attachments": [ + { + "Name": "image1.png", + "Hash": "6MsVgv2an8udIvA1GeRYg/kjvS47EZjb3xKCkwCPBqw=", + "ContentType": "image/png", + "Size": 332916, + "RemoteParameters": { + "Identifier": "my-amazon-storage", + "At": "2025-11-30T06:42:37.3969030Z", + "Flags": "None" + } + } + ], + "@collection": "Employees", + "@change-vector": "A:42-5nIqMb8v3kGQLYhhU90jWg", + "@flags": "HasAttachments", + "@id": "employees/1", + "@last-modified": "2025-11-23T13:48:15.4922720Z" +} +``` + + +```json +"@metadata": { + "@attachments": [ + { + "Name": "image1.png", + "Hash": "6MsVgv2an8udIvA1GeRYg/kjvS47EZjb3xKCkwCPBqw=", + "ContentType": "image/png", + "Size": 332916, + "RemoteParameters": { + "Identifier": "my-amazon-storage", + "At": "2025-11-30T06:42:37.3969030Z", + "Flags": "Remote" + } + } + ], + "@collection": "Employees", + "@change-vector": "A:42-5nIqMb8v3kGQLYhhU90jWg", + "@flags": "HasAttachments", + "@id": "employees/1", + "@last-modified": "2025-11-23T13:48:15.4922720Z" +} +``` + + + +The properties listed in the `@attachments` array include the following information: + +`Name`: +The name of the attachment (e.g. photo.png). +`Hash`: +A Base64-encoded SHA-256 hash of the attachment’s content. +Used to detect and avoid storing the same attachment content more than once in the local storage (deduplication). +`ContentType`: +The MIME type of the attachment (e.g. image/png, application/pdf). +`Size`: +The size of the attachment in bytes. +`RemoteParameters`: +A nested object that is added to the metadata if the attachment is configured for remote storage. +It includes the following sub-properties: + * `Identifier`: The identifier of the remote destination (e.g. "my-amazon-storage"). + * `At`: The timestamp when the attachment is scheduled to be uploaded to remote storage. + * `Flags`: Indicates the status of the attachment: + * `None`: **The attachment is pending upload to remote storage**. + * `Remote`: **The attachment has been successfully uploaded to remote storage**. + + + + + +RavenDB employs an efficient deduplication mechanism when storing attachments in a remote destination. +This ensures that identical attachment content is not uploaded multiple times, optimizing storage and transfer costs. + +**Remote attachment naming**: +Attachments are stored in the remote destination using a **file name** based on a Base64-encoded SHA-256 hash +of their content, under the path defined by the destination’s container or bucket: +`{ContainerOrBucket}/{RemoteFolderName}/{Base64Hash}`. + +**Remote deduplication is based on hash & size**: +Unlike [local attachments](../../../document-extensions/attachments/store-attachments/store-attachments-local#deduplicating-local-attachments), +which deduplicate files based only on their SHA-256 hash, +remote attachments are deduplicated based on **both the hash and the file size**. + +**Pre-upload check**: +Before uploading an attachment to the remote destination, RavenDB: + * Checks if a file with the same hash already exists in the remote storage. + * If such a file exists, it verifies that the size matches: + * **If the hash and size match**, the file is not uploaded again. + * **If the hash matches but the size differs**, the file is treated as partially uploaded or corrupted, + and RavenDB re-uploads it to ensure completeness. + + + + + +Please refer to [the syntax section](../../../document-extensions/attachments/store-attachments/store-attachments-local#syntax) +in the _Store local attachments_ article for all available overloads and syntax options. + + \ No newline at end of file diff --git a/docs/document-extensions/attachments/store-attachments/store-attachments-local.mdx b/docs/document-extensions/attachments/store-attachments/store-attachments-local.mdx new file mode 100644 index 0000000000..39f879131a --- /dev/null +++ b/docs/document-extensions/attachments/store-attachments/store-attachments-local.mdx @@ -0,0 +1,38 @@ +--- +title: "Store Attachments in Local Storage" +sidebar_label: "Store Attachments in Local Storage" +sidebar_position: 0 +--- + +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + +import StoreLocalCsharp from './content/_store-attachments-local-csharp.mdx'; +import StoreLocalJava from './content/_store-attachments-local-java.mdx'; +import StoreLocalPython from './content/_store-attachments-local-python.mdx'; +import StoreLocalPhp from './content/_store-attachments-local-php.mdx'; +import StoreLocalNodejs from './content/_store-attachments-local-nodejs.mdx'; + +export const supportedLanguages = ["csharp", "java", "python", "php", "nodejs"]; + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/document-extensions/attachments/store-attachments/store-attachments-remote.mdx b/docs/document-extensions/attachments/store-attachments/store-attachments-remote.mdx new file mode 100644 index 0000000000..06c47a7eb0 --- /dev/null +++ b/docs/document-extensions/attachments/store-attachments/store-attachments-remote.mdx @@ -0,0 +1,18 @@ +--- +title: "Store Attachments in Remote Storage" +sidebar_label: "Store Attachments in Remote Storage" +sidebar_position: 1 +--- + +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + +import StoreRemoteCsharp from './content/_store-attachments-remote-csharp.mdx'; + +export const supportedLanguages = ["csharp"]; + + + + + + \ No newline at end of file diff --git a/docs/document-extensions/attachments/storing.mdx b/docs/document-extensions/attachments/storing.mdx deleted file mode 100644 index 7ee1404442..0000000000 --- a/docs/document-extensions/attachments/storing.mdx +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: "Attachments: Storing Attachments" -sidebar_label: Storing -sidebar_position: 1 ---- - -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -import StoringCsharp from './_storing-csharp.mdx'; -import StoringJava from './_storing-java.mdx'; -import StoringPython from './_storing-python.mdx'; -import StoringPhp from './_storing-php.mdx'; -import StoringNodejs from './_storing-nodejs.mdx'; - -export const supportedLanguages = ["csharp", "java", "python", "php", "nodejs"]; - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/document-extensions/attachments/what-are-attachments.mdx b/docs/document-extensions/attachments/what-are-attachments.mdx deleted file mode 100644 index e925da8f1d..0000000000 --- a/docs/document-extensions/attachments/what-are-attachments.mdx +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: "What are Attachments" -sidebar_label: What are Attachments -sidebar_position: 0 ---- - -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -import WhatAreAttachmentsCsharp from './_what-are-attachments-csharp.mdx'; -import WhatAreAttachmentsJava from './_what-are-attachments-java.mdx'; -import WhatAreAttachmentsNodejs from './_what-are-attachments-nodejs.mdx'; - -export const supportedLanguages = ["csharp", "java", "nodejs"]; - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/document-extensions/overview-extensions.mdx b/docs/document-extensions/overview-extensions.mdx index fbfd9d7169..399abac346 100644 --- a/docs/document-extensions/overview-extensions.mdx +++ b/docs/document-extensions/overview-extensions.mdx @@ -10,72 +10,73 @@ import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; import LanguageContent from "@site/src/components/LanguageContent"; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; -# Document Extensions Overview * Document extensions are data entities associated with documents. -* Document extensions are stored separately so that **modifying** an extension value (e.g. a counter - or a time series entry) will not modify its parent document. +* Document extensions are stored separately, so **modifying** an extension (e.g., a counter or time series entry) + does not modify the parent document. -* **Creating or deleting** an extension changes the parent document's meta-data. - This document change may trigger indexing, ETL tasks, and various other operations. +* **Creating or deleting** an extension modifies the parent document's metadata. + This change may trigger indexing, ETL tasks, or other operations. -* On a [sharded database](../sharding/overview.mdx), document extensions - are stored in the same bucket as the document that owns them. - Read about document extensions on a sharded database in the section - [dedicated to this subject](../sharding/document-extensions.mdx). +* On a [sharded database](../sharding/overview.mdx), document extensions are stored in the same bucket as their parent document. + Learn more in [Sharding: Document Extensions](../sharding/document-extensions.mdx). -* In this page: - * [Document Extensions](../document-extensions/overview-extensions.mdx#document-extensions) - * [Studio Document Extension Views](../document-extensions/overview-extensions.mdx#studio-document-extension-views) +* In this article: + * [Document extensions](../document-extensions/overview-extensions.mdx#document-extensions) + * [Document extensions in the Studio](../document-extensions/overview-extensions.mdx#document-extensions-in-the-studio) -## Document Extensions + + * [Counters](../document-extensions/counters/overview.mdx) - RavenDB's distributed counters are numeric data variables that can be added to documents and used - for various counting tasks. + RavenDB’s distributed counters are numeric values attached to documents, + used to maintain and increment counts in the database, across all nodes in the cluster. + They are suitable for counting tasks in a distributed environment, such as tracking views, votes, or usage metrics. -* [Attachments](../document-extensions/attachments/what-are-attachments.mdx) - Attachments are binary streams (videos, images, PDF, etc.) that can be bound to an existing document. +* [Attachments](../document-extensions/attachments/overview.mdx) + Attachments are binary streams (e.g., videos, images, PDFs) that can be associated with an existing document. * [Time Series](../document-extensions/timeseries/overview.mdx) - Time series are vectors of data that collect values over time, store the values consecutively across the cluster, - and manage the collected data with high efficiency and performance. + Time series are sequences of timestamped values collected over time, stored consecutively, + and optimized for efficient, high-performance data handling across the cluster. * [Revisions](../document-extensions/revisions/overview.mdx) - Document Revisions are snapshots of documents and their extensions that can be created to give access to a document's history. - + Revisions are snapshots of documents and their associated data, + allowing access to the document’s full history over time. + + -## Studio Document Extension Views - -#### Document Extensions Flags +### Extensions flags ![Document Extensions in Collections View](./assets/extensions-collections-view.png) 1. **Documents Tab** - Select to view document options. + Open the _Documents_ tab. 2. **Collection** - Select a documents collection. -3. **Extensions** - View which types of extensions are added to the documents. + Select a collection. +3. **Extensions flags** + View which types of extensions are associated with the documents. ![Document Extensions Icons](./assets/extensions-logos.png) -#### Document Extensions View - -Select a specific document to manage the extensions. -In Studio > click Documents Tab > select specific Collection > select specific DocumentID to navigate to the following view: - -![Managing Document Extensions in Studio](./assets/extensions-managing-single-doc.png) +--- -1. Attachments Settings -2. Counters Settings -3. Time-Series Settings -4. Revisions Settings +### Extensions in the document view +Open a specific document to view and manage its extensions. +![Managing Document Extensions in Studio](./assets/extensions-managing-single-doc.png) +1. Attachments +2. Counters +3. Time Series +4. Revisions + + \ No newline at end of file diff --git a/docs/document-extensions/revisions/_revisions-and-other-features-csharp.mdx b/docs/document-extensions/revisions/_revisions-and-other-features-csharp.mdx index 1906658a54..2b22fdf186 100644 --- a/docs/document-extensions/revisions/_revisions-and-other-features-csharp.mdx +++ b/docs/document-extensions/revisions/_revisions-and-other-features-csharp.mdx @@ -169,7 +169,7 @@ When a document is [reverted](../../document-extensions/revisions/revert-revisio A document revision will be created when: - * A new [attachment](../../document-extensions/attachments/what-are-attachments.mdx) is **added** to the document. + * A new [attachment](../../document-extensions/attachments/overview) is **added** to the document. * An attachment is **deleted** from the document. ### Stored Data: diff --git a/docs/server/kb/javascript-engine.mdx b/docs/server/kb/javascript-engine.mdx index ce7ad7adfb..f25db1a326 100644 --- a/docs/server/kb/javascript-engine.mdx +++ b/docs/server/kb/javascript-engine.mdx @@ -104,6 +104,12 @@ RavenDB provides the following set of predefined functions: | **timeseries.increment(timestamp, value)** | `void` | Increments a single value of an incremental time series entry at the specified timestamp. | | **timeseries.increment(value)** | `void` | Increments a single value of an incremental time series entry at the current time. | +#### **Attachment operations**: + +| Method Signature | Return type | Description | +|-----------------------------------------------------------------------|--------------|-----------------------------------------------------------------------------------------------------------------| +| **attachments(attachmentName).remote(destinationID,
uploadTime)** | `object` | Updates the document's metadata with remote parameters to schedule the attachment for upload to remote storage. | + #### **Compare-exchange**: | Method Signature | Return type | Description |