Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: AdjustIndex preserves existing filter #711

Merged
merged 2 commits into from
Aug 25, 2023
Merged
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
Expand Up @@ -21,7 +21,7 @@ public static class IMultiTenantDbContextExtensions

var changedMultiTenantEntities = changeTracker.Entries().
Where(e => e.State == EntityState.Added || e.State == EntityState.Modified || e.State == EntityState.Deleted).
Where(e => e.Metadata.IsMultiTenant());
Where(e => e.Metadata.IsMultiTenant()).ToList();

// ensure tenant context is valid
if (changedMultiTenantEntities.Any())
Expand All @@ -32,19 +32,19 @@ public static class IMultiTenantDbContextExtensions

// get list of all added entities with MultiTenant annotation
var addedMultiTenantEntities = changedMultiTenantEntities.
Where(e => e.State == EntityState.Added);
Where(e => e.State == EntityState.Added).ToList();

// handle Tenant Id mismatches for added entities
var mismatchedAdded = addedMultiTenantEntities.
Where(e => (string?)e.Property("TenantId").CurrentValue != null &&
(string?)e.Property("TenantId").CurrentValue != tenantInfo.Id);
(string?)e.Property("TenantId").CurrentValue != tenantInfo.Id).ToList();

if (mismatchedAdded.Any())
{
switch (tenantMismatchMode)
{
case TenantMismatchMode.Throw:
throw new MultiTenantException($"{mismatchedAdded.Count()} added entities with Tenant Id mismatch."); ;
throw new MultiTenantException($"{mismatchedAdded.Count} added entities with Tenant Id mismatch.");

case TenantMismatchMode.Ignore:
// no action needed
Expand All @@ -70,19 +70,19 @@ public static class IMultiTenantDbContextExtensions

// get list of all modified entities with MultiTenant annotation
var modifiedMultiTenantEntities = changedMultiTenantEntities.
Where(e => e.State == EntityState.Modified);
Where(e => e.State == EntityState.Modified).ToList();

// handle Tenant Id mismatches for modified entities
var mismatchedModified = modifiedMultiTenantEntities.
Where(e => (string?)e.Property("TenantId").CurrentValue != null &&
(string?)e.Property("TenantId").CurrentValue != tenantInfo.Id);
(string?)e.Property("TenantId").CurrentValue != tenantInfo.Id).ToList();

if (mismatchedModified.Any())
{
switch (tenantMismatchMode)
{
case TenantMismatchMode.Throw:
throw new MultiTenantException($"{mismatchedModified.Count()} modified entities with Tenant Id mismatch."); ;
throw new MultiTenantException($"{mismatchedModified.Count} modified entities with Tenant Id mismatch.");

case TenantMismatchMode.Ignore:
// no action needed
Expand All @@ -99,14 +99,14 @@ public static class IMultiTenantDbContextExtensions

// handle Tenant Id not set for modified entities
var notSetModified = modifiedMultiTenantEntities.
Where(e => (string?)e.Property("TenantId").CurrentValue == null);
Where(e => (string?)e.Property("TenantId").CurrentValue == null).ToList();

if (notSetModified.Any())
{
switch (tenantNotSetMode)
{
case TenantNotSetMode.Throw:
throw new MultiTenantException($"{notSetModified.Count()} modified entities with Tenant Id not set."); ;
throw new MultiTenantException($"{notSetModified.Count} modified entities with Tenant Id not set.");

case TenantNotSetMode.Overwrite:
foreach (var e in notSetModified)
Expand All @@ -119,19 +119,19 @@ public static class IMultiTenantDbContextExtensions

// get list of all deleted entities with MultiTenant annotation
var deletedMultiTenantEntities = changedMultiTenantEntities.
Where(e => e.State == EntityState.Deleted);
Where(e => e.State == EntityState.Deleted).ToList();

// handle Tenant Id mismatches for deleted entities
var mismatchedDeleted = deletedMultiTenantEntities.
Where(e => (string?)e.Property("TenantId").CurrentValue != null &&
(string?)e.Property("TenantId").CurrentValue != tenantInfo.Id);
(string?)e.Property("TenantId").CurrentValue != tenantInfo.Id).ToList();

if (mismatchedDeleted.Any())
{
switch (tenantMismatchMode)
{
case TenantMismatchMode.Throw:
throw new MultiTenantException($"{mismatchedDeleted.Count()} deleted entities with Tenant Id mismatch."); ;
throw new MultiTenantException($"{mismatchedDeleted.Count} deleted entities with Tenant Id mismatch.");

case TenantMismatchMode.Ignore:
// no action needed
Expand All @@ -145,14 +145,14 @@ public static class IMultiTenantDbContextExtensions

// handle Tenant Id not set for deleted entities
var notSetDeleted = deletedMultiTenantEntities.
Where(e => (string?)e.Property("TenantId").CurrentValue == null);
Where(e => (string?)e.Property("TenantId").CurrentValue == null).ToList();

if (notSetDeleted.Any())
{
switch (tenantNotSetMode)
{
case TenantNotSetMode.Throw:
throw new MultiTenantException($"{notSetDeleted.Count()} deleted entities with Tenant Id not set."); ;
throw new MultiTenantException($"{notSetDeleted.Count} deleted entities with Tenant Id not set.");

case TenantNotSetMode.Overwrite:
// no action needed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ public MultiTenantEntityTypeBuilder AdjustIndex(IMutableIndex index)
if (index.IsUnique)
indexBuilder.IsUnique();

if (index.GetFilter() is string filter)
{
indexBuilder.HasFilter(filter);
}

return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Refer to the solution LICENSE file for more information.

using System;
using System.Collections;
using System.Linq;
using Finbuckle.MultiTenant.EntityFrameworkCore;
using Finbuckle.MultiTenant.EntityFrameworkCore.Test.MultiTenantEntityTypeBuilder;
Expand Down Expand Up @@ -34,76 +35,87 @@ public void AdjustIndexOnAdjustIndex()
{
IMutableIndex? origIndex = null;

using (var db = GetDbContext(builder =>
{
builder.Entity<Blog>().HasIndex(e => e.BlogId);

origIndex = builder.Entity<Blog>().Metadata.GetIndexes().First();
builder.Entity<Blog>().IsMultiTenant().AdjustIndex(origIndex);
}))
using var db = GetDbContext(builder =>
{
var index = db.Model.FindEntityType(typeof(Blog))?.GetIndexes().First();
Assert.Contains("BlogId", index!.Properties.Select(p => p.Name));
Assert.Contains("TenantId", index.Properties.Select(p => p.Name));
}
builder.Entity<Blog>().HasIndex(e => e.BlogId);

origIndex = builder.Entity<Blog>().Metadata.GetIndexes().First();
builder.Entity<Blog>().IsMultiTenant().AdjustIndex(origIndex);
});
var index = db.Model.FindEntityType(typeof(Blog))?.GetIndexes().First();
Assert.Contains("BlogId", index!.Properties.Select(p => p.Name));
Assert.Contains("TenantId", index.Properties.Select(p => p.Name));
}

[Fact]
public void PreserveIndexNameOnAdjustIndex()
{
IMutableIndex? origIndex = null;

using (var db = GetDbContext(builder =>
{
builder.Entity<Blog>()
.HasIndex(e => e.BlogId, "CustomIndexName")
.HasDatabaseName("CustomIndexDbName");

origIndex = builder.Entity<Blog>().Metadata.GetIndexes().First();
builder.Entity<Blog>().IsMultiTenant().AdjustIndex(origIndex);
}))
using var db = GetDbContext(builder =>
{
var index = db.Model.FindEntityType(typeof(Blog))?.GetIndexes().First();
Assert.Equal("CustomIndexName", index!.Name);
Assert.Equal("CustomIndexDbName", index.GetDatabaseName());
}
builder.Entity<Blog>()
.HasIndex(e => e.BlogId, "CustomIndexName")
.HasDatabaseName("CustomIndexDbName");

origIndex = builder.Entity<Blog>().Metadata.GetIndexes().First();
builder.Entity<Blog>().IsMultiTenant().AdjustIndex(origIndex);
});
var index = db.Model.FindEntityType(typeof(Blog))?.GetIndexes().First();
Assert.Equal("CustomIndexName", index!.Name);
Assert.Equal("CustomIndexDbName", index.GetDatabaseName());
}

[Fact]
public void PreserveIndexUniquenessOnAdjustIndex()
{
using (var db = GetDbContext(builder =>
{
builder.Entity<Blog>().HasIndex(e => e.BlogId).IsUnique();
builder.Entity<Blog>().HasIndex(e => e.Url);

foreach (var index in builder.Entity<Blog>().Metadata.GetIndexes().ToList())
builder.Entity<Blog>().IsMultiTenant().AdjustIndex(index);
}))
using var db = GetDbContext(builder =>
{
builder.Entity<Blog>().HasIndex(e => e.BlogId).IsUnique();
builder.Entity<Blog>().HasIndex(e => e.Url);

foreach (var index in builder.Entity<Blog>().Metadata.GetIndexes().ToList())
builder.Entity<Blog>().IsMultiTenant().AdjustIndex(index);
});
{
var index = db.Model.FindEntityType(typeof(Blog))?
.GetIndexes()
.Single(i => i.Properties.Select(p => p.Name).Contains("BlogId"));
.GetIndexes()
.Single(i => i.Properties.Select(p => p.Name).Contains("BlogId"));
Assert.True(index!.IsUnique);
index = db.Model.FindEntityType(typeof(Blog))?
.GetIndexes()
.Single(i => i.Properties.Select(p => p.Name).Contains("Url"));
.GetIndexes()
.Single(i => i.Properties.Select(p => p.Name).Contains("Url"));
Assert.False(index!.IsUnique);
}
}

[Fact]
public void PreserveIndexFilterOnAdjustIndex()
{
using var db = GetDbContext(builder =>
{
var index = builder.Entity<Blog>().HasIndex(e => e.BlogId).IsUnique().HasFilter("some filter").Metadata;
builder.Entity<Blog>().IsMultiTenant().AdjustIndex(index);
});

var index = db.GetService<IDesignTimeModel>().Model.FindEntityType(typeof(Blog))?
.GetIndexes()
.Single(i => i.Properties.Select(p => p.Name).Contains("BlogId"));
Assert.Equal("some filter", index!.GetFilter());
}

[Fact]
public void AdjustPrimaryKeyOnAdjustKey()
{
using (var db = GetDbContext(builder =>
{
var key = builder.Entity<Post>().Metadata.GetKeys().First();
builder.Entity<Post>().IsMultiTenant().AdjustKey(key, builder);
}))
using var db = GetDbContext(builder =>
{
var key = builder.Entity<Post>().Metadata.GetKeys().First();
builder.Entity<Post>().IsMultiTenant().AdjustKey(key, builder);
});
{
var key = db.Model.FindEntityType(typeof(Post))?.GetKeys().ToList();

Assert.Single(key!);
Assert.Single((IEnumerable)key!);
Assert.Equal(2, key![0].Properties.Count);
Assert.Contains("PostId", key[0].Properties.Select(p => p.Name));
Assert.Contains("TenantId", key[0].Properties.Select(p => p.Name));
Expand All @@ -113,16 +125,16 @@ public void AdjustPrimaryKeyOnAdjustKey()
[Fact]
public void AdjustDependentForeignKeyOnAdjustPrimaryKey()
{
using (var db = GetDbContext(builder =>
{
var key = builder.Entity<Blog>().Metadata.GetKeys().First();
using var db = GetDbContext(builder =>
{
var key = builder.Entity<Blog>().Metadata.GetKeys().First();

builder.Entity<Blog>().IsMultiTenant().AdjustKey(key, builder);
}))
builder.Entity<Blog>().IsMultiTenant().AdjustKey(key, builder);
});
{
var key = db.Model.FindEntityType(typeof(Post))?.GetForeignKeys().ToList();

Assert.Single(key!);
Assert.Single((IEnumerable)key!);
Assert.Equal(2, key![0].Properties.Count);
Assert.Contains("BlogId", key[0].Properties.Select(p => p.Name));
Assert.Contains("TenantId", key[0].Properties.Select(p => p.Name));
Expand All @@ -132,15 +144,15 @@ public void AdjustDependentForeignKeyOnAdjustPrimaryKey()
[Fact]
public void AdjustAlternateKeyOnAdjustKey()
{
using (var db = GetDbContext(builder =>
{
var key = builder.Entity<Blog>().HasAlternateKey(b => b.Url).Metadata;
builder.Entity<Blog>().IsMultiTenant().AdjustKey(key, builder);
}))
using var db = GetDbContext(builder =>
{
var key = builder.Entity<Blog>().HasAlternateKey(b => b.Url).Metadata;
builder.Entity<Blog>().IsMultiTenant().AdjustKey(key, builder);
});
{
var key = db.Model.FindEntityType(typeof(Blog))?.GetKeys().Where(k => !k.IsPrimaryKey()).ToList();

Assert.Single(key!);
Assert.Single((IEnumerable)key!);
Assert.Equal(2, key![0].Properties.Count);
Assert.Contains("Url", key[0].Properties.Select(p => p.Name));
Assert.Contains("TenantId", key[0].Properties.Select(p => p.Name));
Expand All @@ -150,20 +162,20 @@ public void AdjustAlternateKeyOnAdjustKey()
[Fact]
public void AdjustDependentForeignKeyOnAdjustAlternateKey()
{
using (var db = GetDbContext(builder =>
{
var key = builder.Entity<Blog>().HasAlternateKey(b => b.Url).Metadata;
builder.Entity<Post>()
.HasOne(p => p.Blog!)
.WithMany(b => b.Posts!)
.HasForeignKey(p => p.Title) // Since Title is a string lets use it as key to Blog.Url
.HasPrincipalKey(b => b.Url);
builder.Entity<Blog>().IsMultiTenant().AdjustKey(key, builder);
}))
using var db = GetDbContext(builder =>
{
var key = builder.Entity<Blog>().HasAlternateKey(b => b.Url).Metadata;
builder.Entity<Post>()
.HasOne(p => p.Blog!)
.WithMany(b => b.Posts!)
.HasForeignKey(p => p.Title) // Since Title is a string lets use it as key to Blog.Url
.HasPrincipalKey(b => b.Url);
builder.Entity<Blog>().IsMultiTenant().AdjustKey(key, builder);
});
{
var key = db.Model.FindEntityType(typeof(Post))?.GetForeignKeys().ToList();

Assert.Single(key!);
Assert.Single((IEnumerable)key!);
Assert.Equal(2, key![0].Properties.Count);
Assert.Contains("Title", key[0].Properties.Select(p => p.Name));
Assert.Contains("TenantId", key[0].Properties.Select(p => p.Name));
Expand Down
8 changes: 0 additions & 8 deletions test/Finbuckle.MultiTenant.Test/TenantInfoShould.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,12 @@ namespace Finbuckle.MultiTenant.Test
{
public class TenantInfoShould
{
[Fact]
public void AlwaysFail()
{
Assert.True(false);
}

[Fact]
public void ThrowIfIdSetWithLengthAboveTenantIdMaxLength()
{
// OK
// ReSharper disable once ObjectCreationAsStatement
new TenantInfo { Id = "".PadRight(1, 'a') };

// OK
// ReSharper disable once ObjectCreationAsStatement
new TenantInfo { Id = "".PadRight(Constants.TenantIdMaxLength, 'a') };

Expand Down