示例#1
0
        /// <summary>
        /// Generates assocation keys in metadata.
        /// </summary>
        /// <param name="association">Association model.</param>
        /// <param name="backReference">Association side.</param>
        private void BuildAssociationMetadataKey(AssociationModel association, bool backReference)
        {
            // metadata model to update with keys
            var metadata = backReference ? association.TargetMetadata : association.SourceMetadata;

            // if metadata keys already specified in model, skip generation
            // (we don't generate them before this stage, so it means user generated them manually)
            if (metadata.ExpressionPredicate != null ||
                metadata.QueryExpressionMethod != null ||
                metadata.ThisKey != null ||
                metadata.ThisKeyExpression != null ||
                metadata.OtherKey != null ||
                metadata.OtherKeyExpression != null)
            {
                return;
            }

            if (association.FromColumns == null ||
                association.ToColumns == null ||
                association.FromColumns.Length != association.ToColumns.Length ||
                association.FromColumns.Length == 0)
            {
                throw new InvalidOperationException($"Invalid association configuration: association columns missing or mismatch on both sides of assocation.");
            }

            var thisColumns  = backReference ? association.ToColumns : association.FromColumns;
            var otherColumns = !backReference ? association.ToColumns : association.FromColumns;

            var thisBuilder  = !backReference ? _entityBuilders[association.Source] : _entityBuilders[association.Target];
            var otherBuilder = backReference ? _entityBuilders[association.Source] : _entityBuilders[association.Target];

            // we generate keys using nameof operator to get property name to have refactoring-friendly mappings
            // (T4 always used strings here)
            var separator = association.FromColumns.Length > 1 ? AST.Constant(",", true) : null;

            for (var i = 0; i < association.FromColumns.Length; i++)
            {
                if (i > 0)
                {
                    // add comma separator
                    metadata.ThisKeyExpression  = AST.Add(metadata.ThisKeyExpression !, separator !);
                    metadata.OtherKeyExpression = AST.Add(metadata.OtherKeyExpression !, separator !);
                }

                // generate nameof() expressions for current key column
                var thisKey  = AST.NameOf(AST.Member(thisBuilder.Type.Type, _columnProperties[thisColumns [0]].Reference));
                var otherKey = AST.NameOf(AST.Member(otherBuilder.Type.Type, _columnProperties[otherColumns[0]].Reference));

                // append column name to key
                metadata.ThisKeyExpression  = metadata.ThisKeyExpression == null ? thisKey  : AST.Add(metadata.ThisKeyExpression, thisKey);
                metadata.OtherKeyExpression = metadata.OtherKeyExpression == null ? otherKey : AST.Add(metadata.OtherKeyExpression, thisKey);
            }
        }
示例#2
0
        /// <summary>
        /// Generates code (AST) for single association in both directions using properties and/or extension methods.
        /// </summary>
        /// <param name="association">Association model.</param>
        /// <param name="extensionAssociations">Association extensions region provider.</param>
        /// <param name="entityAssociations">Assocation property groups for each entity.</param>
        /// <param name="extensionEntityAssociations">Association methods groupsfor each entity.</param>
        private void BuildAssociation(
            AssociationModel association,
            Func <RegionGroup> extensionAssociations,
            Dictionary <EntityModel, PropertyGroup> entityAssociations,
            Dictionary <EntityModel, MethodGroup> extensionEntityAssociations)
        {
            // get source and target entities for association
            // source is foreign key source table
            // target is foreign key target table
            if (!_entityBuilders.TryGetValue(association.Source, out var sourceBuilder) ||
                !_entityBuilders.TryGetValue(association.Target, out var targetBuilder))
            {
                // data model misconfiguration (e.g. entity was removed from model, but not its associations)
                throw new InvalidOperationException("Discovered association that connects tables, missing from current model.");
            }

            // build metadata keys for association
            // (add fields used to define assocation to metadata)
            BuildAssociationMetadataKey(association, false);
            BuildAssociationMetadataKey(association, true);

            // Type of assocation on source side.
            // Nullable for nullable foreign key.
            var sourceType = targetBuilder.Type.Type.WithNullability(association.SourceMetadata.CanBeNull);

            // Type of association on target side.
            var tagetType = sourceBuilder.Type.Type;

            if (association.ManyToOne)
            {
                // for many-to-one assocations has collection type, defined by user preferences
                if (_options.DataModel.AssociationCollectionAsArray)
                {
                    tagetType = AST.ArrayType(tagetType, false);
                }
                else if (_dataModel.AssociationCollectionType != null)
                {
                    tagetType = _dataModel.AssociationCollectionType.WithTypeArguments(tagetType);
                }
                else
                {
                    // default type is IEnumerable
                    tagetType = WellKnownTypes.System.Collections.Generic.IEnumerable(tagetType);
                }
            }
            else             // if one-to-one association targets nullable columns, association is nullable
            {
                tagetType = tagetType.WithNullability(true);
            }

            // (if enabled) generate property-based association from source to target
            if (association.Property != null)
            {
                BuildAssociationProperty(
                    entityAssociations,
                    association.Source,
                    sourceType,
                    association.Property,
                    association.SourceMetadata);
            }

            // (if enabled) generate property-based back-reference association from target to source
            if (association.BackreferenceProperty != null)
            {
                BuildAssociationProperty(
                    entityAssociations,
                    association.Target,
                    tagetType,
                    association.BackreferenceProperty,
                    association.TargetMetadata);
            }

            // (if enabled) generate extension-based association from source to target
            if (association.Extension != null)
            {
                BuildAssociationExtension(
                    extensionEntityAssociations,
                    extensionAssociations,
                    sourceBuilder,
                    targetBuilder,
                    sourceType,
                    association.Extension,
                    association.SourceMetadata,
                    association,
                    false);
            }

            // (if enabled) generate extension-based back-reference association from target to source
            if (association.BackreferenceExtension != null)
            {
                BuildAssociationExtension(
                    extensionEntityAssociations,
                    extensionAssociations,
                    targetBuilder,
                    sourceBuilder,
                    tagetType,
                    association.BackreferenceExtension,
                    association.TargetMetadata,
                    association,
                    true);
            }
        }
示例#3
0
        /// <summary>
        /// Generates association extension method in extensions class for one side of association.
        /// </summary>
        /// <param name="extensionEntityAssociations">Association methods groupsfor each entity.</param>
        /// <param name="extensionAssociations">Association extensions region provider.</param>
        /// <param name="thisEntity">Entity class for this side of assocation (used for extension <c>this</c> parameter).</param>
        /// <param name="resultEntity">Entity class for other side of assocation (used for extension result type).</param>
        /// <param name="type">Association result type.</param>
        /// <param name="extensionModel">Extension method model.</param>
        /// <param name="metadata">Association methodo metadata.</param>
        /// <param name="associationModel">Association model.</param>
        /// <param name="backReference">Identifies current side of assocation.</param>
        private void BuildAssociationExtension(
            Dictionary <EntityModel, MethodGroup> extensionEntityAssociations,
            Func <RegionGroup> extensionAssociations,
            ClassBuilder thisEntity,
            ClassBuilder resultEntity,
            IType type,
            MethodModel extensionModel,
            AssociationMetadata metadata,
            AssociationModel associationModel,
            bool backReference)
        {
            // create (if missing) assocations region for specific owner (this) entity
            var key = backReference ? associationModel.Target : associationModel.Source;

            if (!extensionEntityAssociations.TryGetValue(key, out var associations))
            {
                extensionEntityAssociations.Add(
                    key,
                    associations = extensionAssociations()
                                   .New(string.Format(EXTENSIONS_ENTITY_ASSOCIATIONS_REGION, thisEntity.Type.Name.Name))
                                   .Methods(false));
            }

            // define extension method
            var methodBuilder = DefineMethod(associations, extensionModel).Returns(type);

            // and it's metadata
            _metadataBuilder.BuildAssociationMetadata(metadata, methodBuilder);

            // build method parameters...
            var thisParam = AST.Parameter(thisEntity.Type.Type, AST.Name(EXTENSIONS_ENTITY_THIS_PARAMETER), CodeParameterDirection.In);
            var ctxParam  = AST.Parameter(WellKnownTypes.LinqToDB.IDataContext, AST.Name(EXTENSIONS_ENTITY_CONTEXT_PARAMETER), CodeParameterDirection.In);

            methodBuilder.Parameter(thisParam);
            methodBuilder.Parameter(ctxParam);

            // ... and body
            if (associationModel.FromColumns == null || associationModel.ToColumns == null)
            {
                // association doesn't specify relation columns (e.g. defined usign expression method)
                // so we should generate exception for non-query execution
                methodBuilder
                .Body()
                .Append(
                    AST.Throw(
                        AST.New(
                            WellKnownTypes.System.InvalidOperationException,
                            AST.Constant(EXCEPTION_QUERY_ONLY_ASSOCATION_CALL, true))));
            }
            else
            {
                // generate association query for non-query invocation

                // As method body here could conflict with custom return type for many-to-one assocation
                // we forcebly override return type here to IQueryable<T>
                if (associationModel.ManyToOne && backReference)
                {
                    methodBuilder.Returns(WellKnownTypes.System.Linq.IQueryable(resultEntity.Type.Type));
                }

                var lambdaParam = AST.LambdaParameter(AST.Name(EXTENSIONS_ASSOCIATION_FILTER_PARAMETER), resultEntity.Type.Type);

                // generate assocation key columns filter, which compare
                // `this` entity parameter columns with return table entity columns
                var fromObject = backReference ? lambdaParam : thisParam;
                var toObject   = !backReference ? lambdaParam : thisParam;

                ICodeExpression?filter = null;

                for (var i = 0; i < associationModel.FromColumns.Length; i++)
                {
                    var fromColumn = _columnProperties[associationModel.FromColumns[i]];
                    var toColumn   = _columnProperties[associationModel.ToColumns[i]];

                    var cond = AST.Equal(
                        AST.Member(fromObject.Reference, fromColumn.Reference),
                        AST.Member(toObject.Reference, toColumn.Reference));

                    filter = filter == null ? cond : AST.And(filter, cond);
                }

                // generate filter lambda function
                var filterLambda = AST
                                   .Lambda(
                    WellKnownTypes.System.Linq.Expressions.Expression(
                        WellKnownTypes.System.Func(WellKnownTypes.System.Boolean, resultEntity.Type.Type)),
                    true)
                                   .Parameter(lambdaParam);
                filterLambda.Body().Append(AST.Return(filter !));

                // ctx.GetTable<ResultEntity>()
                var body = AST.ExtCall(
                    WellKnownTypes.LinqToDB.DataExtensions,
                    WellKnownTypes.LinqToDB.DataExtensions_GetTable,
                    WellKnownTypes.LinqToDB.ITable(resultEntity.Type.Type),
                    new[] { resultEntity.Type.Type },
                    false,
                    ctxParam.Reference);

                // append First/FirstOrDefault (for optional association)
                // for non-many relation
                if (!backReference || !associationModel.ManyToOne)
                {
                    var optional = backReference ? associationModel.TargetMetadata.CanBeNull : associationModel.SourceMetadata.CanBeNull;

                    // .First(t => t.PK == thisEntity.PK)
                    // or
                    // .FirstOrDefault(t => t.PK == thisEntity.PK)
                    body = AST.ExtCall(
                        WellKnownTypes.System.Linq.Queryable,
                        optional ? WellKnownTypes.System.Linq.Queryable_FirstOrDefault : WellKnownTypes.System.Linq.Queryable_First,
                        resultEntity.Type.Type.WithNullability(optional),
                        new[] { resultEntity.Type.Type },
                        true,
                        body,
                        filterLambda.Method);
                }
                else
                {
                    // .Where(t => t.PK == thisEntity.PK)
                    body = AST.ExtCall(
                        WellKnownTypes.System.Linq.Queryable,
                        WellKnownTypes.System.Linq.Queryable_Where,
                        WellKnownTypes.System.Linq.IQueryable(resultEntity.Type.Type),
                        new [] { resultEntity.Type.Type },
                        true,
                        body,
                        filterLambda.Method);
                }

                methodBuilder.Body().Append(AST.Return(body));
            }
        }