diff --git a/entity-framework/core/providers/cosmos/modeling.md b/entity-framework/core/providers/cosmos/modeling.md index 2ceebf6728..119775be25 100644 --- a/entity-framework/core/providers/cosmos/modeling.md +++ b/entity-framework/core/providers/cosmos/modeling.md @@ -2,7 +2,7 @@ title: Modeling - Azure Cosmos DB Provider - EF Core description: Configuring the model with the Azure Cosmos DB EF Core Provider author: roji -ms.date: 09/19/2024 +ms.date: 09/26/2024 uid: core/providers/cosmos/modeling --- # Configuring the model with the EF Core Azure Cosmos DB Provider @@ -334,3 +334,25 @@ To configure an entity type to use [optimistic concurrency](xref:core/saving/con To make it easier to resolve concurrency errors you can map the eTag to a CLR property using . [!code-csharp[Main](../../../../samples/core/Cosmos/ModelBuilding/OrderContext.cs?name=ETagProperty)] + +## Database triggers + +> [!NOTE] +> Database trigger execution support was introduced in EF Core 10.0. + +Azure Cosmos DB supports pre- and post-triggers that run before or after database operations. EF Core can be configured to execute these triggers when performing save operations. + +> [!IMPORTANT] +> Triggers are executed server-side by Azure Cosmos DB when EF Core performs operations, but they are not enforced - operations can be performed without running triggers if accessing the database directly. This means triggers should not be used for security-related functionality such as authentication or auditing, as they can be bypassed by applications that access the database directly without using EF Core. + +To configure triggers on an entity type, use the `HasTrigger` method: + +[!code-csharp[TriggerConfiguration](../../../../samples/core/Cosmos/ModelBuilding/TriggerSample.cs?name=TriggerConfiguration)] + +The `HasTrigger` method requires: + +* **modelName**: The name of the trigger in Azure Cosmos DB +* **triggerType**: Either `TriggerType.Pre` (executed before the operation) or `TriggerType.Post` (executed after the operation) +* **triggerOperation**: The operation that should execute the trigger - `Create`, `Replace`, `Delete`, or `All` + +Before triggers can be executed, they must be created in Azure Cosmos DB using the Cosmos SDK or Azure portal. The trigger name configured in EF Core must match the trigger name in Azure Cosmos DB. diff --git a/samples/core/Cosmos/Cosmos.csproj b/samples/core/Cosmos/Cosmos.csproj index 8539b9f9df..7ba28aaa96 100644 --- a/samples/core/Cosmos/Cosmos.csproj +++ b/samples/core/Cosmos/Cosmos.csproj @@ -2,11 +2,11 @@ Exe - net8.0 + net10.0 - + diff --git a/samples/core/Cosmos/ModelBuilding/TriggerSample.cs b/samples/core/Cosmos/ModelBuilding/TriggerSample.cs new file mode 100644 index 0000000000..68a75e5ae9 --- /dev/null +++ b/samples/core/Cosmos/ModelBuilding/TriggerSample.cs @@ -0,0 +1,73 @@ +using Microsoft.Azure.Cosmos.Scripts; +using Microsoft.EntityFrameworkCore; +using System.Threading.Tasks; + +namespace Cosmos.ModelBuilding; + +public static class TriggerSample +{ + public static async Task ConfigureTriggers() + { + var contextOptions = new DbContextOptionsBuilder() + .UseCosmos("https://localhost:8081", "account-key", "sample"); + + using var context = new TriggerContext(contextOptions.Options); + + // Ensure database is created + await context.Database.EnsureCreatedAsync(); + + // Create a new product - this will trigger the PreInsertTrigger + var product = new Product + { + Id = 1, + Name = "Sample Product", + Price = 19.99m, + Category = "Electronics" + }; + + context.Products.Add(product); + await context.SaveChangesAsync(); + + // Update the product - this will trigger the UpdateTrigger + product.Price = 24.99m; + await context.SaveChangesAsync(); + + // Delete the product - this will trigger the PostDeleteTrigger + context.Products.Remove(product); + await context.SaveChangesAsync(); + } +} + +public class TriggerContext : DbContext +{ + public TriggerContext(DbContextOptions options) : base(options) { } + + public DbSet Products { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasPartitionKey(p => p.Category); + + #region TriggerConfiguration + // Configure pre-trigger for create operations + entity.HasTrigger("PreInsertTrigger", TriggerType.Pre, TriggerOperation.Create); + + // Configure post-trigger for delete operations + entity.HasTrigger("PostDeleteTrigger", TriggerType.Post, TriggerOperation.Delete); + + // Configure trigger for replace operations + entity.HasTrigger("UpdateTrigger", TriggerType.Pre, TriggerOperation.Replace); + #endregion + }); + } +} + +public class Product +{ + public int Id { get; set; } + public string Name { get; set; } = null!; + public decimal Price { get; set; } + public string Category { get; set; } = null!; +} \ No newline at end of file diff --git a/samples/global.json b/samples/global.json new file mode 100644 index 0000000000..be4942c34f --- /dev/null +++ b/samples/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "10.0.100-rc.1.25451.107" + } +} \ No newline at end of file