Exemplo n.º 1
0
        /// <summary>
        /// Generates association property in entity class for one side of association.
        /// </summary>
        /// <param name="entityAssociations">Lookup for association properties group in each entity.</param>
        /// <param name="owner">Assocation property owner entity.</param>
        /// <param name="type">Association property type.</param>
        /// <param name="propertyModel">Association property model.</param>
        /// <param name="metadata">Association property metadata.</param>
        private void BuildAssociationProperty(
            Dictionary <EntityModel, PropertyGroup> entityAssociations,
            EntityModel owner,
            IType type,
            PropertyModel propertyModel,
            AssociationMetadata metadata)
        {
            // if entity class doesn't have assocation properties group yet
            // (not created yet by previous associations) - create it
            if (!entityAssociations.TryGetValue(owner, out var associations))
            {
                entityAssociations.Add(
                    owner,
                    associations = _entityBuilders[owner]
                                   .Regions()
                                   .New(ENTITY_ASSOCIATIONS_REGION)
                                   .Properties(false));
            }

            // by default property type will be null here, but user could override it manually
            // and we should respect it
            if (propertyModel.Type == null)
            {
                propertyModel.Type = type;
            }

            // declare property
            var propertyBuilder = DefineProperty(associations, propertyModel);

            // and it's metadata
            _metadataBuilder.BuildAssociationMetadata(metadata, propertyBuilder);
        }
Exemplo n.º 2
0
 public AssociationModel(
     AssociationMetadata sourceMetadata,
     AssociationMetadata tagetMetadata,
     EntityModel source,
     EntityModel target,
     bool manyToOne)
 {
     SourceMetadata = sourceMetadata;
     TargetMetadata = tagetMetadata;
     Source         = source;
     Target         = target;
     ManyToOne      = manyToOne;
 }
Exemplo n.º 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));
            }
        }
Exemplo n.º 4
0
        /// <summary>
        /// Defines association from foreign key.
        /// </summary>
        /// <param name="fk">Foreign key schema object.</param>
        /// <param name="defaultSchemas">List of default database schema names.</param>
        /// <returns>Assocation model.
        /// Could return <c>null</c> if any of foreign key relation tables were not loaded into model.</returns>
        private AssociationModel?BuildAssociations(ForeignKey fk, ISet <string> defaultSchemas)
        {
            if (!_entities.TryGetValue(fk.Source, out var source) ||
                !_entities.TryGetValue(fk.Target, out var target))
            {
                return(null);
            }

            var fromColumns   = new ColumnModel[fk.Relation.Count];
            var toColumns     = new ColumnModel[fk.Relation.Count];
            var sourceColumns = _columns[source.Entity];
            var targetColumns = _columns[target.Entity];

            // identify cardinality of relation (one-to-one vs one-to-many) and nullability using following information:
            // - nullability of foreign key columns (from/source side of association will be nullable in this case)
            //      same for to/target side of relation
            // - wether source/target columns used as PK or not (possible cardinality)
            // TODO: for now we don't have information about unique constraints (except PK), which also affects cardinality
            var fromOptional = true;
            // if PK(and UNIQUE in future) and FK sizes on target table differ - this is definitely not one-to-one relation
            var manyToOne = fk.Relation.Count != sourceColumns.Values.Count(c => c.Metadata.IsPrimaryKey);

            for (var i = 0; i < fk.Relation.Count; i++)
            {
                var mapping = fk.Relation[i];
                fromColumns[i] = sourceColumns[mapping.SourceColumn];
                toColumns[i]   = targetColumns[mapping.TargetColumn];

                // if at least one column in foreign key is not nullable, we mark it as required
                if (fromOptional && !fromColumns[i].Metadata.CanBeNull)
                {
                    fromOptional = false;
                }
                // if at least one column in foreign key is not a part of PK (or unique constraint in future), association has many-to-one cardinality
                // TODO: when adding unique constrains support make sure to validate that all FK columns are part of same PK/UNIQUE constrain
                // in case of composite key
                if (!manyToOne && !fromColumns[i].Metadata.IsPrimaryKey)
                {
                    manyToOne = true;
                }
            }

            var sourceMetadata = new AssociationMetadata()
            {
                CanBeNull = fromOptional
            };
            // back-reference is always optional
            var targetMetadata = new AssociationMetadata()
            {
                CanBeNull = true
            };

            var association = new AssociationModel(sourceMetadata, targetMetadata, source.Entity, target.Entity, manyToOne);

            association.FromColumns = fromColumns;
            association.ToColumns   = toColumns;

            // use foreign key name for xml-doc comment generation
            var summary = fk.Name;
            var backreferenceSummary = $"{fk.Name} backreference";

            // use foreign key column name for association name generation
            var sourceColumnName    = fk.Relation.Count == 1 ? fk.Relation[0].SourceColumn : null;
            var fromAssociationName = GenerateAssociationName(
                fk.Target,
                fk.Source,
                sourceColumnName,
                fk.Name,
                _options.DataModel.SourceAssociationPropertyNameOptions,
                defaultSchemas);
            var toAssocationName = GenerateAssociationName(
                fk.Source,
                fk.Target,
                null,
                fk.Name,
                manyToOne
                                        ? _options.DataModel.TargetMultipleAssociationPropertyNameOptions
                                        : _options.DataModel.TargetSingularAssociationPropertyNameOptions,
                defaultSchemas);

            // define association properties on on entities
            if (_options.DataModel.GenerateAssociations)
            {
                association.Property = new PropertyModel(fromAssociationName)
                {
                    Modifiers = Modifiers.Public,
                    IsDefault = true,
                    HasSetter = true,
                    Summary   = summary
                };
                association.BackreferenceProperty = new PropertyModel(toAssocationName)
                {
                    Modifiers = Modifiers.Public,
                    IsDefault = true,
                    HasSetter = true,
                    Summary   = backreferenceSummary
                };
            }

            // define association extension methods
            if (_options.DataModel.GenerateAssociationExtensions)
            {
                association.Extension = new MethodModel(fromAssociationName)
                {
                    Modifiers = Modifiers.Public | Modifiers.Static | Modifiers.Extension,
                    Summary   = summary
                };

                association.BackreferenceExtension = new MethodModel(toAssocationName)
                {
                    Modifiers = Modifiers.Public | Modifiers.Static | Modifiers.Extension,
                    Summary   = backreferenceSummary
                };
            }

            return(association);
        }