/// <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);
 }
Esempio n. 3
0
 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);
 }