/// <summary> /// Configures an entity to be tenanted (owned by a tenant). /// </summary> /// <typeparam name="TEntity">Type that represents the entity.</typeparam> /// <typeparam name="TTenantKey">Type that represents the tenant key.</typeparam> /// <param name="builder">Builder describing the entity.</param> /// <param name="tenantId">Expression to read the currently scoped tenant ID.</param> /// <param name="tenancyModelState">The same state object that was passed out of <see cref="ModelBuilder"/>.HasTenancy().</param> /// <param name="propertyName">Name of the property on the entity that references the ID of the owning tenant, if it does not exist a shadow property will be added to the entity's model.</param> /// <param name="hasIndex">True if the tenant ID reference column should be indexed in the database, this will override any previously configured value in <see cref="TenantReferenceOptions"/>.</param> /// <param name="indexNameFormat">Format or name of the index, only applicable if <paramref name="hasIndex"/> is true, this will override any previously configured value in <see cref="TenantReferenceOptions"/>.</param> /// <param name="nullTenantReferenceHandling">Determines if a null tenant reference is allowed and how querying for null tenant references is handled.</param> public static void HasTenancy <TEntity, TTenantKey>( this EntityTypeBuilder <TEntity> builder, Expression <Func <TTenantKey> > tenantId, TenancyModelState <TTenantKey> tenancyModelState, string propertyName = null, bool?hasIndex = null, string indexNameFormat = null, NullTenantReferenceHandling?nullTenantReferenceHandling = null) where TEntity : class { ArgCheck.NotNull(nameof(builder), builder); ArgCheck.NotNull(nameof(tenancyModelState), tenancyModelState); // get overrides or defaults var tenantKeyType = typeof(TTenantKey); propertyName ??= tenancyModelState.DefaultOptions.ReferenceName ?? throw new ArgumentNullException(nameof(propertyName)); hasIndex ??= tenancyModelState.DefaultOptions.IndexReferences; indexNameFormat ??= tenancyModelState.DefaultOptions.IndexNameFormat; nullTenantReferenceHandling ??= tenancyModelState.DefaultOptions.NullTenantReferenceHandling; tenancyModelState.Properties[typeof(TEntity)] = new TenantReferenceOptions() { ReferenceName = propertyName, IndexReferences = hasIndex.Value, IndexNameFormat = indexNameFormat, NullTenantReferenceHandling = nullTenantReferenceHandling.Value, }; // add property var property = builder.Property(tenantKeyType, propertyName); // is required / not null if (nullTenantReferenceHandling == NullTenantReferenceHandling.NotNullDenyAccess || nullTenantReferenceHandling == NullTenantReferenceHandling.NotNullGlobalAccess) { property.IsRequired(); } // add index if (hasIndex.Value) { var index = builder.HasIndex(propertyName); if (!string.IsNullOrEmpty(indexNameFormat)) { index.HasName(string.Format(indexNameFormat, propertyName)); } } // add tenant query filter // Entity e var entityParameter = Expression.Parameter(typeof(TEntity), "e"); // "TenantId" var propertyNameConstant = Expression.Constant(propertyName, typeof(string)); // EF.Property<long> var efPropertyMethod = ((MethodCallExpression)((Expression <Func <object, string, TTenantKey> >)((e, p) => EF.Property <TTenantKey>(e, p))).Body).Method; // EF.Property<long>(e, "TenantId") var efPropertyCall = Expression.Call(efPropertyMethod, entityParameter, propertyNameConstant); // _tenancyContext.Tenant.Id == EF.Property<long>(e, "TenantId") var tenantCondition = Expression.Equal(tenantId.Body, efPropertyCall); if (nullTenantReferenceHandling == NullTenantReferenceHandling.NotNullDenyAccess || nullTenantReferenceHandling == NullTenantReferenceHandling.NotNullGlobalAccess) { var nullableTenantKeyType = tenantKeyType.IsValueType ? typeof(Nullable <>).MakeGenericType(tenantKeyType) : tenantKeyType; var nullableTenantKey = tenantKeyType.IsValueType ? Expression.Convert(tenantId.Body, nullableTenantKeyType) : tenantId.Body; var nullTenantKey = Expression.Constant(null, nullableTenantKeyType); if (nullTenantReferenceHandling == NullTenantReferenceHandling.NotNullDenyAccess) { // (long?)_tenancyContext.Tenant.Id != (long?)null && _tenancyContext.Tenant.Id == EF.Property<long>(e, "TenantId") tenantCondition = Expression.AndAlso(Expression.NotEqual(nullableTenantKey, nullTenantKey), tenantCondition); } else if (nullTenantReferenceHandling == NullTenantReferenceHandling.NotNullGlobalAccess) { // (long?)_tenancyContext.Tenant.Id == (long?)null || _tenancyContext.Tenant.Id == EF.Property<long>(e, "TenantId") tenantCondition = Expression.OrElse(Expression.Equal(nullableTenantKey, nullTenantKey), tenantCondition); } } var lambda = Expression.Lambda(tenantCondition, entityParameter); builder.HasQueryFilter(lambda); }
public void Property_throws_when_invoked_outside_of_query() { Assert.Equal( CoreStrings.PropertyMethodInvoked, Assert.Throws <InvalidOperationException>(() => EF.Property <object>(new object(), "")).Message); }
public void Property_throws_when_invoked_outside_of_query() { Assert.Equal( "The EF.Property<T> method may only be used within LINQ queries.", Assert.Throws <InvalidOperationException>(() => EF.Property <object>(new object(), "")).Message); }