Browse Source

Implemented Current Tenant fiter for EF Core.

pull/179/head
Halil İbrahim Kalkan 9 years ago
parent
commit
6c1985b18f
  1. 92
      src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  2. 2
      src/Volo.Abp/Volo/Abp/MultiTenancy/NullCurrentTenant.cs
  3. 68
      test/Volo.Abp.EntityFrameworkCore.Tests/Migrations/20171026084129_Added_TenantId_To_Person.Designer.cs
  4. 25
      test/Volo.Abp.EntityFrameworkCore.Tests/Migrations/20171026084129_Added_TenantId_To_Person.cs
  5. 2
      test/Volo.Abp.EntityFrameworkCore.Tests/Migrations/TestAppDbContextModelSnapshot.cs
  6. 59
      test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/MultiTenant_Filter_Tests.cs
  7. 8
      test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/Person.cs
  8. 10
      test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs

92
src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs

@ -1,6 +1,7 @@
using System; using System;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq; using System.Linq;
using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -9,6 +10,7 @@ using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
using Volo.Abp.Guids; using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Reflection; using Volo.Abp.Reflection;
using Volo.Abp.Uow; using Volo.Abp.Uow;
@ -17,12 +19,21 @@ namespace Volo.Abp.EntityFrameworkCore
public abstract class AbpDbContext<TDbContext> : DbContext public abstract class AbpDbContext<TDbContext> : DbContext
where TDbContext : DbContext where TDbContext : DbContext
{ {
public Guid? CurrentTenantId => CurrentTenant?.Id;
protected virtual bool IsMayHaveTenantFilterEnabled => true; //TODO: Change this when data filtering system is full implemented
public ICurrentTenant CurrentTenant { get; set; }
public IGuidGenerator GuidGenerator { get; set; } public IGuidGenerator GuidGenerator { get; set; }
private static readonly MethodInfo ConfigureGlobalFiltersMethodInfo = typeof(AbpDbContext<TDbContext>).GetMethod(nameof(ConfigureGlobalFilters), BindingFlags.Instance | BindingFlags.NonPublic);
protected AbpDbContext(DbContextOptions<TDbContext> options) protected AbpDbContext(DbContextOptions<TDbContext> options)
: base(options) : base(options)
{ {
GuidGenerator = SimpleGuidGenerator.Instance; GuidGenerator = SimpleGuidGenerator.Instance;
CurrentTenant = NullCurrentTenant.Instance;
} }
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
@ -32,6 +43,10 @@ namespace Volo.Abp.EntityFrameworkCore
foreach (var entityType in modelBuilder.Model.GetEntityTypes()) foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{ {
ConfigureConcurrencyStamp(entityType); ConfigureConcurrencyStamp(entityType);
ConfigureGlobalFiltersMethodInfo
.MakeGenericMethod(entityType.ClrType)
.Invoke(this, new object[] { modelBuilder, entityType });
} }
} }
@ -135,5 +150,82 @@ namespace Volo.Abp.EntityFrameworkCore
} }
} }
} }
protected void ConfigureGlobalFilters<TEntity>(ModelBuilder modelBuilder, IMutableEntityType entityType)
where TEntity : class
{
if (entityType.BaseType == null && ShouldFilterEntity<TEntity>(entityType))
{
var filterExpression = CreateFilterExpression<TEntity>();
if (filterExpression != null)
{
modelBuilder.Entity<TEntity>().HasQueryFilter(filterExpression);
}
}
}
protected virtual bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType) where TEntity : class
{
if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity)))
{
return true;
}
return false;
}
protected virtual Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()
where TEntity : class
{
Expression<Func<TEntity, bool>> expression = null;
if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity)))
{
/* This condition should normally be defined as below:
* !IsMayHaveTenantFilterEnabled || ((IMayHaveTenant)e).TenantId == CurrentTenantId
* But this causes a problem with EF Core (see https://github.com/aspnet/EntityFrameworkCore/issues/9502)
* So, we made a workaround to make it working. It works same as above.
*/
Expression<Func<TEntity, bool>> multiTenantFilter = e => ((IMultiTenant)e).TenantId == CurrentTenantId || (((IMultiTenant)e).TenantId == CurrentTenantId) == IsMayHaveTenantFilterEnabled;
expression = expression == null ? multiTenantFilter : CombineExpressions(expression, multiTenantFilter);
}
return expression;
}
protected virtual Expression<Func<T, bool>> CombineExpressions<T>(Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
{
var parameter = Expression.Parameter(typeof(T));
var leftVisitor = new ReplaceExpressionVisitor(expression1.Parameters[0], parameter);
var left = leftVisitor.Visit(expression1.Body);
var rightVisitor = new ReplaceExpressionVisitor(expression2.Parameters[0], parameter);
var right = rightVisitor.Visit(expression2.Body);
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(left, right), parameter);
}
class ReplaceExpressionVisitor : ExpressionVisitor
{
private readonly Expression _oldValue;
private readonly Expression _newValue;
public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
{
_oldValue = oldValue;
_newValue = newValue;
}
public override Expression Visit(Expression node)
{
if (node == _oldValue)
{
return _newValue;
}
return base.Visit(node);
}
}
} }
} }

2
src/Volo.Abp/Volo/Abp/MultiTenancy/NullCurrentTenant.cs

@ -5,6 +5,8 @@ namespace Volo.Abp.MultiTenancy
{ {
public class NullCurrentTenant : ICurrentTenant, ISingletonDependency public class NullCurrentTenant : ICurrentTenant, ISingletonDependency
{ {
public static NullCurrentTenant Instance { get; } = new NullCurrentTenant();
public Guid? Id { get; } = null; public Guid? Id { get; } = null;
} }
} }

68
test/Volo.Abp.EntityFrameworkCore.Tests/Migrations/20171026084129_Added_TenantId_To_Person.Designer.cs

@ -0,0 +1,68 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using System;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.TestApp.EntityFrameworkCore;
namespace Volo.Abp.EntityFrameworkCore.Tests.Migrations
{
[DbContext(typeof(TestAppDbContext))]
[Migration("20171026084129_Added_TenantId_To_Person")]
partial class Added_TenantId_To_Person
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
modelBuilder.Entity("Volo.Abp.TestApp.Domain.Person", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Age");
b.Property<string>("Name");
b.Property<Guid?>("TenantId");
b.HasKey("Id");
b.ToTable("People");
});
modelBuilder.Entity("Volo.Abp.TestApp.Domain.Phone", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Number");
b.Property<Guid>("PersonId");
b.Property<int>("Type");
b.HasKey("Id");
b.HasIndex("PersonId");
b.ToTable("AppPhones");
});
modelBuilder.Entity("Volo.Abp.TestApp.Domain.Phone", b =>
{
b.HasOne("Volo.Abp.TestApp.Domain.Person")
.WithMany("Phones")
.HasForeignKey("PersonId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

25
test/Volo.Abp.EntityFrameworkCore.Tests/Migrations/20171026084129_Added_TenantId_To_Person.cs

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace Volo.Abp.EntityFrameworkCore.Tests.Migrations
{
public partial class Added_TenantId_To_Person : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "TenantId",
table: "People",
type: "BLOB",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TenantId",
table: "People");
}
}
}

2
test/Volo.Abp.EntityFrameworkCore.Tests/Migrations/TestAppDbContextModelSnapshot.cs

@ -29,6 +29,8 @@ namespace Volo.Abp.EntityFrameworkCore.Tests.Migrations
b.Property<string>("Name"); b.Property<string>("Name");
b.Property<Guid?>("TenantId");
b.HasKey("Id"); b.HasKey("Id");
b.ToTable("People"); b.ToTable("People");

59
test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/MultiTenant_Filter_Tests.cs

@ -0,0 +1,59 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using Shouldly;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.MultiTenancy;
using Volo.Abp.TestApp;
using Volo.Abp.TestApp.Domain;
using Xunit;
namespace Volo.Abp.EntityFrameworkCore.Repositories
{
public class MultiTenant_Filter_Tests : EntityFrameworkCoreTestBase
{
private ICurrentTenant _fakeCurrentTenant;
private readonly IRepository<Person> _personRepository;
public MultiTenant_Filter_Tests()
{
_personRepository = GetRequiredService<IRepository<Person>>();
}
protected override void AfterAddApplication(IServiceCollection services)
{
_fakeCurrentTenant = Substitute.For<ICurrentTenant>();
services.AddSingleton(_fakeCurrentTenant);
}
[Fact]
public async Task Should_Get_Person_For_Current_Tenant()
{
//TenantId = null
_fakeCurrentTenant.Id.Returns((Guid?)null);
var people = await _personRepository.GetListAsync();
people.Count.ShouldBe(1);
people.Any(p => p.Name == "Douglas").ShouldBeTrue();
//TenantId = TestDataBuilder.TenantId1
_fakeCurrentTenant.Id.Returns(TestDataBuilder.TenantId1);
people = await _personRepository.GetListAsync();
people.Count.ShouldBe(2);
people.Any(p => p.Name == TestDataBuilder.TenantId1 + "-Person1").ShouldBeTrue();
people.Any(p => p.Name == TestDataBuilder.TenantId1 + "-Person2").ShouldBeTrue();
//TenantId = TestDataBuilder.TenantId2
_fakeCurrentTenant.Id.Returns(TestDataBuilder.TenantId2);
people = await _personRepository.GetListAsync();
people.Count.ShouldBe(0);
}
}
}

8
test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/Person.cs

@ -1,11 +1,14 @@
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.TestApp.Domain namespace Volo.Abp.TestApp.Domain
{ {
public class Person : AggregateRoot public class Person : AggregateRoot, IMultiTenant
{ {
public virtual Guid? TenantId { get; set; }
public virtual string Name { get; set; } public virtual string Name { get; set; }
public virtual int Age { get; set; } public virtual int Age { get; set; }
@ -17,11 +20,12 @@ namespace Volo.Abp.TestApp.Domain
} }
public Person(Guid id, string name, int age) public Person(Guid id, string name, int age, Guid? tenantId = null)
{ {
Id = id; Id = id;
Name = name; Name = name;
Age = age; Age = age;
TenantId = tenantId;
Phones = new Collection<Phone>(); Phones = new Collection<Phone>();
} }

10
test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs

@ -7,6 +7,9 @@ namespace Volo.Abp.TestApp
{ {
public class TestDataBuilder : ITransientDependency public class TestDataBuilder : ITransientDependency
{ {
public static Guid TenantId1 { get; } = new Guid("55687dce-595c-41b4-a024-2a5e991ac8f4");
public static Guid TenantId2 { get; } = new Guid("f522d19f-5a86-4278-98fb-0577319c544a");
private readonly IRepository<Person> _personRepository; private readonly IRepository<Person> _personRepository;
public TestDataBuilder(IRepository<Person> personRepository) public TestDataBuilder(IRepository<Person> personRepository)
@ -24,7 +27,14 @@ namespace Volo.Abp.TestApp
var douglas = new Person(Guid.NewGuid(), "Douglas", 42); var douglas = new Person(Guid.NewGuid(), "Douglas", 42);
douglas.Phones.Add(new Phone(douglas.Id, "123456789")); douglas.Phones.Add(new Phone(douglas.Id, "123456789"));
douglas.Phones.Add(new Phone(douglas.Id, "123456780", PhoneType.Home)); douglas.Phones.Add(new Phone(douglas.Id, "123456780", PhoneType.Home));
_personRepository.Insert(douglas); _personRepository.Insert(douglas);
var tenant1Person1 = new Person(Guid.NewGuid(), TenantId1 + "-Person1", 42, TenantId1);
var tenant1Person2 = new Person(Guid.NewGuid(), TenantId1 + "-Person2", 43, TenantId1);
_personRepository.Insert(tenant1Person1);
_personRepository.Insert(tenant1Person2);
} }
} }
} }
Loading…
Cancel
Save