private void InitProperties() { var entityType = typeof(TEntity); var entityTypeInfo = entityType.GetTypeInfo(); var tableAliasAttribute = entityTypeInfo.GetCustomAttribute <TableAttribute>(); TableName = tableAliasAttribute != null ? tableAliasAttribute.Name : entityTypeInfo.Name; AllProperties = entityType.GetProperties().Where(q => q.CanWrite).ToArray(); var props = AllProperties.Where(ExpressionHelper.GetPrimitivePropertiesPredicate()).ToArray(); // Filter the non stored properties SqlProperties = props.Where(p => !p.GetCustomAttributes <NotMappedAttribute>().Any()).Select(p => new SqlPropertyMetadata(p)).ToArray(); // Filter key properties KeySqlProperties = props.Where(p => p.GetCustomAttributes <KeyAttribute>().Any()).Select(p => new SqlPropertyMetadata(p)).ToArray(); // Use identity as key pattern var identityProperty = props.FirstOrDefault(p => p.GetCustomAttributes <IdentityAttribute>().Any()); IdentitySqlProperty = identityProperty != null ? new SqlPropertyMetadata(identityProperty) : null; var dateChangedProperty = props.FirstOrDefault(p => p.GetCustomAttributes <DateChangedAttribute>().Count() == 1); if (dateChangedProperty != null && (dateChangedProperty.PropertyType == typeof(DateTime) || dateChangedProperty.PropertyType == typeof(DateTime?))) { DateChangedProperty = props.FirstOrDefault(p => p.GetCustomAttributes <DateChangedAttribute>().Any()); } }
/// <summary> /// Get join/nested properties /// </summary> /// <returns></returns> private static SqlJoinPropertyMetadata[] GetJoinPropertyMetadata(PropertyInfo[] joinPropertiesInfo) { // Filter and get only non collection nested properties var singleJoinTypes = joinPropertiesInfo.Where(p => !p.PropertyType.IsConstructedGenericType).ToArray(); var joinPropertyMetadatas = new List <SqlJoinPropertyMetadata>(); foreach (var propertyInfo in singleJoinTypes) { var joinInnerProperties = propertyInfo.PropertyType.GetProperties().Where(q => q.CanWrite) .Where(ExpressionHelper.GetPrimitivePropertiesPredicate()).ToArray(); joinPropertyMetadatas.AddRange(joinInnerProperties.Where(p => !p.GetCustomAttributes <NotMappedAttribute>().Any()) .Select(p => new SqlJoinPropertyMetadata(propertyInfo, p)).ToArray()); } return(joinPropertyMetadatas.ToArray()); }
private StringBuilder AppendJoinToSelect(StringBuilder originalBuilder, params Expression <Func <TEntity, object> >[] includes) { var joinsBuilder = new StringBuilder(); foreach (var include in includes) { var propertyName = ExpressionHelper.GetPropertyName(include); var joinProperty = AllProperties.First(x => x.Name == propertyName); var attrJoin = joinProperty.GetCustomAttribute <JoinAttributeBase>(); if (attrJoin != null) { var joinString = ""; if (attrJoin is LeftJoinAttribute) { joinString = "LEFT JOIN "; } else if (attrJoin is InnerJoinAttribute) { joinString = "INNER JOIN "; } else if (attrJoin is RightJoinAttribute) { joinString = "RIGHT JOIN "; } var joinType = joinProperty.PropertyType.IsGenericType() ? joinProperty.PropertyType.GenericTypeArguments[0] : joinProperty.PropertyType; var properties = joinType.GetProperties().Where(ExpressionHelper.GetPrimitivePropertiesPredicate()); var props = properties.Where(p => !p.GetCustomAttributes <NotMappedAttribute>().Any()).Select(p => new PropertyMetadata(p)); originalBuilder.Append(", " + GetFieldsSelect(attrJoin.TableName, props)); joinsBuilder.Append($"{joinString} {attrJoin.TableName} ON {TableName}.{attrJoin.Key} = {attrJoin.TableName}.{attrJoin.ExternalKey} "); } } return(joinsBuilder); }
private void InitProperties() { var entityType = typeof(TEntity); var entityTypeInfo = entityType.GetTypeInfo(); var tableAttribute = entityTypeInfo.GetCustomAttribute <TableAttribute>(); TableName = tableAttribute != null ? tableAttribute.Name : entityTypeInfo.Name; TableSchema = tableAttribute != null ? tableAttribute.Schema : string.Empty; AllProperties = entityType.FindClassProperties().Where(q => q.CanWrite).ToArray(); var props = AllProperties.Where(ExpressionHelper.GetPrimitivePropertiesPredicate()).ToArray(); var joinProperties = AllProperties.Where(p => p.GetCustomAttributes <JoinAttributeBase>().Any()).ToArray(); SqlJoinProperties = GetJoinPropertyMetadata(joinProperties); // Filter the non stored properties SqlProperties = props.Where(p => !p.GetCustomAttributes <NotMappedAttribute>().Any()).Select(p => new SqlPropertyMetadata(p)).ToArray(); // Filter key properties KeySqlProperties = props.Where(p => p.GetCustomAttributes <KeyAttribute>().Any()).Select(p => new SqlPropertyMetadata(p)).ToArray(); // Use identity as key pattern var identityProperty = props.FirstOrDefault(p => p.GetCustomAttributes <IdentityAttribute>().Any()); IdentitySqlProperty = identityProperty != null ? new SqlPropertyMetadata(identityProperty) : null; var dateChangedProperty = props.FirstOrDefault(p => p.GetCustomAttributes <UpdatedAtAttribute>().Any()); if (dateChangedProperty != null && (dateChangedProperty.PropertyType == typeof(DateTime) || dateChangedProperty.PropertyType == typeof(DateTime?))) { UpdatedAtProperty = dateChangedProperty; UpdatedAtPropertyMetadata = new SqlPropertyMetadata(UpdatedAtProperty); } }
private string AppendJoinToSelect(SqlQuery originalBuilder, params Expression <Func <TEntity, object> >[] includes) { var joinBuilder = new StringBuilder(); foreach (var include in includes) { var joinProperty = AllProperties.First(q => q.Name == ExpressionHelper.GetPropertyName(include)); var declaringType = joinProperty.DeclaringType.GetTypeInfo(); var tableAttribute = declaringType.GetCustomAttribute <TableAttribute>(); var tableName = tableAttribute != null ? tableAttribute.Name : declaringType.Name; var attrJoin = joinProperty.GetCustomAttribute <JoinAttributeBase>(); if (attrJoin == null) { continue; } var joinString = ""; if (attrJoin is LeftJoinAttribute) { joinString = "LEFT JOIN"; } else if (attrJoin is InnerJoinAttribute) { joinString = "INNER JOIN"; } else if (attrJoin is RightJoinAttribute) { joinString = "RIGHT JOIN"; } var joinType = joinProperty.PropertyType.IsGenericType() ? joinProperty.PropertyType.GenericTypeArguments[0] : joinProperty.PropertyType; var properties = joinType.FindClassProperties().Where(ExpressionHelper.GetPrimitivePropertiesPredicate()); var props = properties.Where(p => !p.GetCustomAttributes <NotMappedAttribute>().Any()).Select(p => new SqlPropertyMetadata(p)).ToArray(); if (Config.UseQuotationMarks) { switch (Config.SqlProvider) { case SqlProvider.MSSQL: tableName = "[" + tableName + "]"; attrJoin.TableName = GetTableNameWithSchemaPrefix(attrJoin.TableName, attrJoin.TableSchema, "[", "]"); attrJoin.Key = "[" + attrJoin.Key + "]"; attrJoin.ExternalKey = "[" + attrJoin.ExternalKey + "]"; attrJoin.TableAlias = "[" + attrJoin.TableAlias + "]"; foreach (var prop in props) { prop.ColumnName = "[" + prop.ColumnName + "]"; } break; case SqlProvider.MySQL: tableName = "`" + tableName + "`"; attrJoin.TableName = GetTableNameWithSchemaPrefix(attrJoin.TableName, attrJoin.TableSchema, "`", "`"); attrJoin.Key = "`" + attrJoin.Key + "`"; attrJoin.ExternalKey = "`" + attrJoin.ExternalKey + "`"; attrJoin.TableAlias = "`" + attrJoin.TableAlias + "`"; foreach (var prop in props) { prop.ColumnName = "`" + prop.ColumnName + "`"; } break; case SqlProvider.PostgreSQL: tableName = "\"" + tableName + "\""; attrJoin.TableName = GetTableNameWithSchemaPrefix(attrJoin.TableName, attrJoin.TableSchema, "\"", "\""); attrJoin.Key = "\"" + attrJoin.Key + "\""; attrJoin.ExternalKey = "\"" + attrJoin.ExternalKey + "\""; attrJoin.TableAlias = "\"" + attrJoin.TableAlias + "\""; foreach (var prop in props) { prop.ColumnName = "\"" + prop.ColumnName + "\""; } break; default: throw new ArgumentOutOfRangeException(nameof(Config.SqlProvider)); } } else { attrJoin.TableName = GetTableNameWithSchemaPrefix(attrJoin.TableName, attrJoin.TableSchema); } originalBuilder.SqlBuilder.Append($", {GetFieldsSelect(attrJoin.TableAlias, props)}"); joinBuilder.Append( $"{joinString} {attrJoin.TableName} AS {attrJoin.TableAlias} ON {tableName}.{attrJoin.Key} = {attrJoin.TableAlias}.{attrJoin.ExternalKey} "); if (LogicalDelete && props.Any(s => s.PropertyName == LogicalDeleteProperty.Name)) { bool isDateTime = LogicalDeleteProperty.PropertyType.IsDateTime(); joinBuilder.AppendFormat(" AND {0}.{1} {2} {3} ", attrJoin.TableAlias, LogicalDeletePropertyMetadata.ColumnName, isDateTime ? "IS" : "!=", isDateTime ? "NULL" : GetLogicalDeleteValue()); } } return(joinBuilder.ToString()); }
private string AppendJoinToSelect(SqlQuery originalBuilder, params Expression <Func <TEntity, object> >[] includes) { var joinBuilder = new StringBuilder(); foreach (var include in includes) { var joinProperty = AllProperties.First(q => q.Name == ExpressionHelper.GetPropertyName(include)); var declaringType = joinProperty.DeclaringType.GetTypeInfo(); var tableAttribute = declaringType.GetCustomAttribute <TableAttribute>(); var tableName = tableAttribute != null ? tableAttribute.Name : declaringType.Name; var attrJoin = joinProperty.GetCustomAttribute <JoinAttributeBase>(); if (attrJoin == null) { continue; } var joinString = ""; if (attrJoin is LeftJoinAttribute) { joinString = "LEFT JOIN"; } else if (attrJoin is InnerJoinAttribute) { joinString = "INNER JOIN"; } else if (attrJoin is RightJoinAttribute) { joinString = "RIGHT JOIN"; } var joinType = joinProperty.PropertyType.IsGenericType() ? joinProperty.PropertyType.GenericTypeArguments[0] : joinProperty.PropertyType; var properties = joinType.FindClassProperties().Where(ExpressionHelper.GetPrimitivePropertiesPredicate()); var props = properties.Where(p => !p.GetCustomAttributes <NotMappedAttribute>().Any()).Select(p => new SqlPropertyMetadata(p)).ToArray(); if (Config.UseQuotationMarks) { switch (Config.SqlConnector) { case ESqlConnector.MSSQL: tableName = "[" + tableName + "]"; attrJoin.TableName = GetTableNameWithSchemaPrefix(attrJoin.TableName, attrJoin.TableSchema, "[", "]"); attrJoin.Key = "[" + attrJoin.Key + "]"; attrJoin.ExternalKey = "[" + attrJoin.ExternalKey + "]"; foreach (var prop in props) { prop.ColumnName = "[" + prop.ColumnName + "]"; } break; case ESqlConnector.MySQL: tableName = "`" + tableName + "`"; attrJoin.TableName = GetTableNameWithSchemaPrefix(attrJoin.TableName, attrJoin.TableSchema, "`", "`"); attrJoin.Key = "`" + attrJoin.Key + "`"; attrJoin.ExternalKey = "`" + attrJoin.ExternalKey + "`"; foreach (var prop in props) { prop.ColumnName = "`" + prop.ColumnName + "`"; } break; case ESqlConnector.PostgreSQL: tableName = "\"" + tableName + "\""; attrJoin.TableName = GetTableNameWithSchemaPrefix(attrJoin.TableName, attrJoin.TableSchema, "\"", "\""); attrJoin.Key = "\"" + attrJoin.Key + "\""; attrJoin.ExternalKey = "\"" + attrJoin.ExternalKey + "\""; foreach (var prop in props) { prop.ColumnName = "\"" + prop.ColumnName + "\""; } break; default: throw new ArgumentOutOfRangeException(nameof(Config.SqlConnector)); } } else { attrJoin.TableName = GetTableNameWithSchemaPrefix(attrJoin.TableName, attrJoin.TableSchema); } originalBuilder.SqlBuilder.Append(", " + GetFieldsSelect(attrJoin.TableName, props)); joinBuilder.Append(joinString + " " + attrJoin.TableName + " ON " + tableName + "." + attrJoin.Key + " = " + attrJoin.TableName + "." + attrJoin.ExternalKey + " "); } return(joinBuilder.ToString()); }
public SqlGenerator(ESqlConnector sqlConnector) { SqlConnector = sqlConnector; var entityType = typeof(TEntity); var entityTypeInfo = entityType.GetTypeInfo(); var aliasAttribute = entityTypeInfo.GetCustomAttribute <TableAttribute>(); this.TableName = aliasAttribute != null ? aliasAttribute.Name : entityTypeInfo.Name; AllProperties = entityType.GetProperties(); //Load all the "primitive" entity properties var props = AllProperties.Where(ExpressionHelper.GetPrimitivePropertiesPredicate()).ToArray(); //Filter the non stored properties this.BaseProperties = props.Where(p => !p.GetCustomAttributes <NotMappedAttribute>().Any()).Select(p => new PropertyMetadata(p)); //Filter key properties this.KeyProperties = props.Where(p => p.GetCustomAttributes <KeyAttribute>().Any()).Select(p => new PropertyMetadata(p)); //Use identity as key pattern var identityProperty = props.FirstOrDefault(p => p.GetCustomAttributes <IdentityAttribute>().Any()); this.IdentityProperty = identityProperty != null ? new PropertyMetadata(identityProperty) : null; //Status property (if exists, and if it does, it must be an enumeration) var statusProperty = props.FirstOrDefault(p => p.GetCustomAttributes <StatusAttribute>().Any()); if (statusProperty == null) { return; } StatusProperty = new PropertyMetadata(statusProperty); if (statusProperty.PropertyType.IsBool()) { var deleteProperty = props.FirstOrDefault(p => p.GetCustomAttributes <DeletedAttribute>().Any()); if (deleteProperty == null) { return; } LogicalDelete = true; LogicalDeleteValue = 1; // true } else if (statusProperty.PropertyType.IsEnum()) { var deleteOption = statusProperty.PropertyType.GetFields() .FirstOrDefault(f => f.GetCustomAttribute <DeletedAttribute>() != null); if (deleteOption == null) { return; } var enumValue = Enum.Parse(statusProperty.PropertyType, deleteOption.Name); if (enumValue != null) { LogicalDeleteValue = Convert.ChangeType(enumValue, Enum.GetUnderlyingType(statusProperty.PropertyType)); } LogicalDelete = true; } }
private string AppendJoinToSelect(SqlQuery originalBuilder, params Expression <Func <TEntity, object> >[] includes) { var joinBuilder = new StringBuilder(); foreach (var include in includes) { var joinProperty = AllProperties.First(q => q.Name == ExpressionHelper.GetPropertyName(include)); var attrJoin = joinProperty.GetCustomAttribute <JoinAttributeBase>(); if (attrJoin == null) { continue; } var declaringType = joinProperty.DeclaringType.GetTypeInfo(); var tableAttribute = declaringType.GetCustomAttribute <TableAttribute>(); var tableName = tableAttribute != null ? tableAttribute.Name : declaringType.Name; var joinString = ""; switch (attrJoin) { case LeftJoinAttribute _: joinString = "LEFT JOIN"; break; case InnerJoinAttribute _: joinString = "INNER JOIN"; break; case RightJoinAttribute _ when Config.SqlProvider == SqlProvider.SQLite: throw new NotSupportedException("SQLite doesn't support RIGHT JOIN"); case RightJoinAttribute _: joinString = "RIGHT JOIN"; break; case CrossJoinAttribute _: joinString = "CROSS JOIN"; break; } var joinType = joinProperty.PropertyType.IsGenericType ? joinProperty.PropertyType.GenericTypeArguments[0] : joinProperty.PropertyType; var properties = joinType.FindClassProperties().Where(ExpressionHelper.GetPrimitivePropertiesPredicate()); var props = properties.Where(p => !p.GetCustomAttributes <NotMappedAttribute>().Any()).Select(p => new SqlPropertyMetadata(p)).ToArray(); if (Config.UseQuotationMarks) { switch (Config.SqlProvider) { case SqlProvider.MSSQL: tableName = "[" + tableName + "]"; attrJoin.TableName = GetTableNameWithSchemaPrefix(attrJoin.TableName, attrJoin.TableSchema, "[", "]"); attrJoin.Key = "[" + attrJoin.Key + "]"; attrJoin.ExternalKey = "[" + attrJoin.ExternalKey + "]"; attrJoin.TableAlias = "[" + attrJoin.TableAlias + "]"; foreach (var prop in props) { prop.ColumnName = "[" + prop.ColumnName + "]"; } break; case SqlProvider.MySQL: tableName = "`" + tableName + "`"; attrJoin.TableName = GetTableNameWithSchemaPrefix(attrJoin.TableName, attrJoin.TableSchema, "`", "`"); attrJoin.Key = "`" + attrJoin.Key + "`"; attrJoin.ExternalKey = "`" + attrJoin.ExternalKey + "`"; attrJoin.TableAlias = "`" + attrJoin.TableAlias + "`"; foreach (var prop in props) { prop.ColumnName = "`" + prop.ColumnName + "`"; } break; case SqlProvider.SQLite: break; case SqlProvider.PostgreSQL: tableName = "\"" + tableName + "\""; attrJoin.TableName = GetTableNameWithSchemaPrefix(attrJoin.TableName, attrJoin.TableSchema, "\"", "\""); attrJoin.Key = "\"" + attrJoin.Key + "\""; attrJoin.ExternalKey = "\"" + attrJoin.ExternalKey + "\""; attrJoin.TableAlias = "\"" + attrJoin.TableAlias + "\""; foreach (var prop in props) { prop.ColumnName = "\"" + prop.ColumnName + "\""; } break; default: throw new ArgumentOutOfRangeException(nameof(Config.SqlProvider)); } } else { attrJoin.TableName = GetTableNameWithSchemaPrefix(attrJoin.TableName, attrJoin.TableSchema); } originalBuilder.SqlBuilder.Append($", {GetFieldsSelect(attrJoin.TableAlias, props)}"); joinBuilder.Append( attrJoin is CrossJoinAttribute ? $"{joinString} {attrJoin.TableName} AS {attrJoin.TableAlias}" : $"{joinString} {attrJoin.TableName} AS {attrJoin.TableAlias} ON {tableName}.{attrJoin.Key} = {attrJoin.TableAlias}.{attrJoin.ExternalKey} "); } return(joinBuilder.ToString()); }
private void InitProperties() { var entityType = typeof(TEntity); var entityTypeInfo = entityType.GetTypeInfo(); var tableAttribute = entityTypeInfo.GetCustomAttribute <TableAttribute>(); TableName = tableAttribute != null ? tableAttribute.Name : entityTypeInfo.Name; TableSchema = tableAttribute != null ? tableAttribute.Schema : string.Empty; AllProperties = entityType.FindClassProperties().Where(q => q.CanWrite).ToArray(); var props = AllProperties.Where(ExpressionHelper.GetPrimitivePropertiesPredicate()).ToArray(); var joinProperties = AllProperties.Where(p => p.GetCustomAttributes <JoinAttributeBase>().Any()).ToArray(); SqlJoinProperties = GetJoinPropertyMetadata(joinProperties); // Filter the non stored properties SqlProperties = props.Where(p => !p.GetCustomAttributes <NotMappedAttribute>().Any()).Select(p => new SqlPropertyMetadata(p)).ToArray(); KeyUpsertSqlProperties = props.Where(e => e.GetCustomAttributes <UpsertKeyAttribute>().Any()) .Select(p => new SqlPropertyMetadata(p)) .ToArray(); // Filter key properties KeySqlProperties = props.Where(p => p.GetCustomAttributes <KeyAttribute>().Any()).Select(p => new SqlPropertyMetadata(p)).ToArray(); if (!KeySqlProperties.Any()) { var prop = props.SingleOrDefault(p => p.Name.Equals("Id", StringComparison.OrdinalIgnoreCase)); //skip adding key if prop is null, which means the model doesn't have Id property.... if (prop != null) { KeySqlProperties = new SqlPropertyMetadata[1]; KeySqlProperties[0] = new SqlPropertyMetadata(prop); } } // Use identity as key pattern var identityProperty = props.FirstOrDefault(p => p.GetCustomAttributes <IdentityAttribute>().Any()); IdentitySqlProperty = identityProperty != null ? new SqlPropertyMetadata(identityProperty) : null; var dateChangedProperty = props.FirstOrDefault(p => p.GetCustomAttributes <UpdatedAtAttribute>().Count() == 1); if (dateChangedProperty != null && (dateChangedProperty.PropertyType == typeof(DateTime) || dateChangedProperty.PropertyType == typeof(DateTime?))) { UpdatedAtProperty = props.FirstOrDefault(p => p.GetCustomAttributes <UpdatedAtAttribute>().Any()); UpdatedAtPropertyMetadata = new SqlPropertyMetadata(UpdatedAtProperty); } var modifiedDateProperty = props.FirstOrDefault(p => p.GetCustomAttributes <ModifiedAtAttribute>().Count() == 1); if (modifiedDateProperty != null && (modifiedDateProperty.PropertyType == typeof(DateTime) || modifiedDateProperty.PropertyType == typeof(DateTime?))) { ModifiedAtProperty = props.FirstOrDefault(p => p.GetCustomAttributes <ModifiedAtAttribute>().Any()); ModifiedAtPropertyMetadata = new SqlPropertyMetadata(ModifiedAtProperty); } }
private string AppendJoinToSelect(SqlQuery originalBuilder, params Expression <Func <TEntity, object> >[] includes) { var joinSql = ""; var joinedProperties = new List <PropertyInfo>(); foreach (var include in includes) { var propertyName = ExpressionHelper.GetFullPropertyName(include); var joinProperty = AllProperties.Concat(joinedProperties).First(x => { if (x.DeclaringType != null) { return(x.DeclaringType.FullName + "." + x.Name == propertyName); } return(false); }); var tableName = GetTableNameOrAlias(joinProperty.DeclaringType); var attrJoin = joinProperty.GetCustomAttribute <JoinAttributeBase>(); if (attrJoin == null) { continue; } var joinString = ""; if (attrJoin is LeftJoinAttribute) { joinString = "LEFT JOIN"; } else if (attrJoin is InnerJoinAttribute) { joinString = "INNER JOIN"; } else if (attrJoin is RightJoinAttribute) { joinString = "RIGHT JOIN"; } var joinType = joinProperty.PropertyType.IsGenericType() ? joinProperty.PropertyType.GenericTypeArguments[0] : joinProperty.PropertyType; joinedProperties.AddRange(joinType.GetProperties().Where(p => !p.GetCustomAttributes <NotMappedAttribute>().Any())); var properties = joinType.GetProperties().Where(ExpressionHelper.GetPrimitivePropertiesPredicate()); var props = properties.Where(p => !p.GetCustomAttributes <NotMappedAttribute>().Any()).Select(p => new SqlPropertyMetadata(p)).ToArray(); switch (SqlConnector) { case ESqlConnector.MSSQL: tableName = "[" + tableName + "]"; attrJoin.TableName = "[" + attrJoin.TableName + "]"; attrJoin.Key = "[" + attrJoin.Key + "]"; attrJoin.ExternalKey = "[" + attrJoin.ExternalKey + "]"; foreach (var prop in props) { prop.ColumnName = "[" + prop.ColumnName + "]"; } break; case ESqlConnector.MySQL: tableName = "`" + tableName + "`"; attrJoin.TableName = "`" + attrJoin.TableName + "`"; attrJoin.Key = "`" + attrJoin.Key + "`"; attrJoin.ExternalKey = "`" + attrJoin.ExternalKey + "`"; foreach (var prop in props) { prop.ColumnName = "`" + prop.ColumnName + "`"; } break; case ESqlConnector.PostgreSQL: break; default: throw new ArgumentOutOfRangeException(nameof(SqlConnector)); } originalBuilder.SqlBuilder.Append(", " + GetFieldsSelect(attrJoin.TableName, props)); joinSql += joinString + " " + attrJoin.TableName + " ON " + tableName + "." + attrJoin.Key + " = " + attrJoin.TableName + "." + attrJoin.ExternalKey + " "; } return(joinSql); }