Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
using System;
using System.Threading.Tasks;
using FluentAssertions;
using Wemogy.Infrastructure.Database.Core.UnitTests.Fakes.Entities;
using Xunit;

namespace Wemogy.Infrastructure.Database.Core.UnitTests.Repositories;

public partial class RepositoryTestBase
{
[Fact]
public async Task UpsertAsync_ShouldCreateIfNotExist()
{
// Arrange
await ResetAsync();
var user = User.Faker.Generate();

// Act
Exception? exception = null;
User? updatedUser = null;

try
{
updatedUser = await MicrosoftUserRepository.UpsertAsync(
user,
user.TenantId);
}
catch (Exception ex)
{
exception = ex;
}

// Assert
if (exception is NotSupportedException)
{
// Expected outcome in certain implementations
return;
}

exception.Should().BeNull();
updatedUser.Should().NotBeNull();
updatedUser.Id.Should().Be(user.Id);
updatedUser.TenantId.Should().Be(user.TenantId);
}

[Fact]
public async Task UpsertAsync_ShouldReplaceIfExist()
{
// Arrange
await ResetAsync();
var user = User.Faker.Generate();
await MicrosoftUserRepository.CreateAsync(user);
user.Firstname = "Updated";

// Act
Exception? exception = null;
User? updatedUser = null;

try
{
updatedUser = await MicrosoftUserRepository.UpsertAsync(
user,
user.TenantId);
}
catch (Exception ex)
{
exception = ex;
}

// Assert
if (exception is NotSupportedException)
{
// Expected outcome in certain implementations
return;
}

exception.Should().BeNull();
updatedUser.Should().NotBeNull();
updatedUser.Firstname.Should().Be("Updated");
updatedUser.Id.Should().Be(user.Id);
updatedUser.TenantId.Should().Be(user.TenantId);
}

[Fact]
public async Task UpsertAsyncWithoutPartitionKey_ShouldCreateIfNotExist()
{
// Arrange
await ResetAsync();
var user = User.Faker.Generate();

// Act
Exception? exception = null;
User? updatedUser = null;

try
{
updatedUser = await MicrosoftUserRepository.UpsertAsync(user);
}
catch (Exception ex)
{
exception = ex;
}

// Assert
if (exception is NotSupportedException)
{
// Expected outcome in certain implementations
return;
}

exception.Should().BeNull();
updatedUser.Should().NotBeNull();
updatedUser.Id.Should().Be(user.Id);
updatedUser.TenantId.Should().Be(user.TenantId);
}

[Fact]
public async Task UpsertAsyncWithoutPartitionKey_ShouldReplaceIfExist()
{
// Arrange
await ResetAsync();
var user = User.Faker.Generate();
await MicrosoftUserRepository.CreateAsync(user);
user.Firstname = "Updated";

// Act
Exception? exception = null;
User? updatedUser = null;

try
{
updatedUser = await MicrosoftUserRepository.UpsertAsync(user);
}
catch (Exception ex)
{
exception = ex;
}

// Assert
if (exception is NotSupportedException)
{
// Expected outcome in certain implementations
return;
}

exception.Should().BeNull();
updatedUser.Should().NotBeNull();
updatedUser.Firstname.Should().Be("Updated");
updatedUser.Id.Should().Be(user.Id);
updatedUser.TenantId.Should().Be(user.TenantId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,8 @@ Task<long> CountAsync(
Task DeleteAsync(string id, string partitionKey);

Task DeleteAsync(Expression<Func<TEntity, bool>> predicate);

Task<TEntity> UpsertAsync(TEntity entity);

Task<TEntity> UpsertAsync(TEntity entity, string partitionKey);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Threading.Tasks;

namespace Wemogy.Infrastructure.Database.Core.Abstractions;

/// <summary>
/// Defines methods for upserting entities in a database repository.
/// </summary>
public partial interface IDatabaseRepository<TEntity>
{
/// <summary>
/// Upsert an entity in the database.
/// </summary>
/// <param name="entity">The entity to upsert</param>
/// <returns>The upserted entity as persisted</returns>
Task<TEntity> UpsertAsync(TEntity entity);

/// <summary>
/// Upsert an entity in the database, using the specified partition key.
/// </summary>
/// <param name="entity">The entity to upsert.</param>
/// <param name="partitionKey">The partition key to use for the upsert operation.</param>
/// <returns>The upserted entity as persisted</returns>
Task<TEntity> UpsertAsync(TEntity entity, string partitionKey);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Threading.Tasks;

namespace Wemogy.Infrastructure.Database.Core.Plugins.MultiTenantDatabase.Repositories;

/// <summary>
/// Repository for handling multi-tenant database operations for <typeparamref name="TEntity"/>.
/// </summary>
public partial class MultiTenantDatabaseRepository<TEntity>
{
/// <summary>
/// Inserts or updates the specified entity in the database.
/// </summary>
/// <param name="entity">The entity to upsert.</param>
/// <returns>The upserted entity.</returns>
public Task<TEntity> UpsertAsync(TEntity entity)
{
return _databaseRepository.UpsertAsync(entity);
}

/// <summary>
/// Inserts or updates the specified entity in the database using the provided partition key.
/// </summary>
/// <param name="entity">The entity to upsert.</param>
/// <param name="partitionKey">The partition key to use for the operation.</param>
/// <returns>The upserted entity.</returns>
public Task<TEntity> UpsertAsync(TEntity entity, string partitionKey)
{
return _databaseRepository.UpsertAsync(entity, partitionKey);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Threading.Tasks;
using Wemogy.Infrastructure.Database.Core.Abstractions;

namespace Wemogy.Infrastructure.Database.Core.Repositories;

/// <summary>
/// Represents a repository for performing database operations on entities of type <typeparamref name="TEntity"/>.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
public partial class DatabaseRepository<TEntity>
where TEntity : class, IEntityBase
{
/// <summary>
/// Inserts or updates the specified entity in the database using the provided partition key.
/// </summary>
/// <param name="entity">The entity to upsert.</param>
/// <param name="partitionKey">The partition key for the entity.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the upserted entity.</returns>
public Task<TEntity> UpsertAsync(TEntity entity, string partitionKey)
{
return _database.UpsertAsync(
entity,
partitionKey);
}

/// <summary>
/// Inserts or updates the specified entity in the database.
/// </summary>
/// <param name="entity">The entity to upsert.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the upserted entity.</returns>
public Task<TEntity> UpsertAsync(TEntity entity)
{
return _database.UpsertAsync(entity);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -175,5 +175,20 @@
<DefaultPackFolder>content</DefaultPackFolder>
<BuildAction>Compile</BuildAction>
</Compile>
<Compile Update="Abstractions\IDatabaseRepository`1.Upsert.cs">
<CodeLanguage>cs</CodeLanguage>
<DefaultPackFolder>content</DefaultPackFolder>
<BuildAction>Compile</BuildAction>
</Compile>
<Compile Update="Plugins\MultiTenantDatabase\Repositories\MultiTenantDatabaseRepository`1.Upsert.cs">
<CodeLanguage>cs</CodeLanguage>
<DefaultPackFolder>content</DefaultPackFolder>
<BuildAction>Compile</BuildAction>
</Compile>
<Compile Update="Repositories\DatabaseRepository`1.Upsert.cs">
<CodeLanguage>cs</CodeLanguage>
<DefaultPackFolder>content</DefaultPackFolder>
<BuildAction>Compile</BuildAction>
</Compile>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,33 @@ public async Task<TEntity> ReplaceAsync(TEntity entity)
}
}

public async Task<TEntity> UpsertAsync(TEntity entity)
{
var partitionKey = ResolvePartitionKey(entity);
var upsertResponse = await _container.UpsertItemAsync(
entity,
partitionKey.CosmosPartitionKey,
new ItemRequestOptions
{
EnableContentResponseOnWrite = true
});

return upsertResponse.Resource;
}

public async Task<TEntity> UpsertAsync(TEntity entity, string partitionKey)
{
var upsertResponse = await _container.UpsertItemAsync(
entity,
new PartitionKey<string>(partitionKey).CosmosPartitionKey,
new ItemRequestOptions
{
EnableContentResponseOnWrite = true
});

return upsertResponse.Resource;
}

public Task DeleteAsync(string id, string partitionKey)
{
return DeleteAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,56 @@ public Task<TEntity> ReplaceAsync(TEntity entity)
return Task.FromResult(entity.Clone());
}

public Task<TEntity> UpsertAsync(TEntity entity)
{
var id = ResolveIdValue(entity);
var partitionKeyValue = ResolvePartitionKeyValue(entity);

if (!EntityPartitions.TryGetValue(
partitionKeyValue,
out var entities))
{
entities = new List<TEntity>();
EntityPartitions.Add(
partitionKeyValue,
entities);
}

var existingEntity = entities.AsQueryable().FirstOrDefault("e => e.Id.Equals(@0)", id);

if (existingEntity != null)
{
entities.Remove(existingEntity);
}

entities.Add(entity.Clone());
return Task.FromResult(entity.Clone());
}

public Task<TEntity> UpsertAsync(TEntity entity, string partitionKey)
{
if (!EntityPartitions.TryGetValue(
partitionKey,
out var entities))
{
entities = new List<TEntity>();
EntityPartitions.Add(
partitionKey,
entities);
}

var id = ResolveIdValue(entity);
var existingEntity = entities.AsQueryable().FirstOrDefault("e => e.Id.Equals(@0)", id);

if (existingEntity != null)
{
entities.Remove(existingEntity);
}

entities.Add(entity.Clone());
return Task.FromResult(entity.Clone());
}

public Task DeleteAsync(string id, string partitionKey)
{
if (!EntityPartitions.TryGetValue(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,16 @@ public async Task<TEntity> ReplaceAsync(TEntity entity)
return entity;
}

public Task<TEntity> UpsertAsync(TEntity entity)
{
throw new NotSupportedException();
}

public Task<TEntity> UpsertAsync(TEntity entity, string partitionKey)
{
throw new NotSupportedException();
}

public async Task DeleteAsync(string id, string partitionKey)
{
var filter = GetEntityFilterDefinition(
Expand Down
Loading