/mcs/class/referencesource/System.Data.Linq/SqlClient/Query/Translator.cs
C# | 521 lines | 446 code | 45 blank | 30 comment | 166 complexity | 76a3146022956883516cf83c8f4694e2 MD5 | raw file
Possible License(s): GPL-2.0, CC-BY-SA-3.0, LGPL-2.0, MPL-2.0-no-copyleft-exception, LGPL-2.1, Unlicense, Apache-2.0
- using System;
- using System.Collections.Generic;
- using System.Linq.Expressions;
- using System.Reflection;
- using System.Collections.ObjectModel;
- using System.Text;
- using System.Data.Linq;
- using System.Data.Linq.Mapping;
- using System.Data.Linq.Provider;
- using System.Linq;
- using System.Diagnostics.CodeAnalysis;
- namespace System.Data.Linq.SqlClient {
- internal class Translator {
- IDataServices services;
- SqlFactory sql;
- TypeSystemProvider typeProvider;
- internal Translator(IDataServices services, SqlFactory sqlFactory, TypeSystemProvider typeProvider) {
- this.services = services;
- this.sql = sqlFactory;
- this.typeProvider = typeProvider;
- }
- internal SqlSelect BuildDefaultQuery(MetaType rowType, bool allowDeferred, SqlLink link, Expression source) {
- System.Diagnostics.Debug.Assert(rowType != null && rowType.Table != null);
- if (rowType.HasInheritance && rowType.InheritanceRoot != rowType) {
- // RowType is expected to be an inheritance root.
- throw Error.ArgumentWrongValue("rowType");
- }
- SqlTable table = sql.Table(rowType.Table, rowType, source);
- SqlAlias tableAlias = new SqlAlias(table);
- SqlAliasRef tableAliasRef = new SqlAliasRef(tableAlias);
- SqlExpression projection = this.BuildProjection(tableAliasRef, table.RowType, allowDeferred, link, source);
- return new SqlSelect(projection, tableAlias, source);
- }
- internal SqlExpression BuildProjection(SqlExpression item, MetaType rowType, bool allowDeferred, SqlLink link, Expression source) {
- if (!rowType.HasInheritance) {
- return this.BuildProjectionInternal(item, rowType, (rowType.Table != null) ? rowType.PersistentDataMembers : rowType.DataMembers, allowDeferred, link, source);
- }
- else {
- // Build a type case that represents a switch between the various type.
- List<MetaType> mappedTypes = new List<MetaType>(rowType.InheritanceTypes);
- List<SqlTypeCaseWhen> whens = new List<SqlTypeCaseWhen>();
- SqlTypeCaseWhen @else = null;
- MetaType root = rowType.InheritanceRoot;
- MetaDataMember discriminator = root.Discriminator;
- Type dt = discriminator.Type;
- SqlMember dm = sql.Member(item, discriminator.Member);
- foreach (MetaType type in mappedTypes) {
- if (type.HasInheritanceCode) {
- SqlNew defaultProjection = this.BuildProjectionInternal(item, type, type.PersistentDataMembers, allowDeferred, link, source);
- if (type.IsInheritanceDefault) {
- @else = new SqlTypeCaseWhen(null, defaultProjection);
- }
- // Add an explicit case even for the default.
- // Redundant results will be optimized out later.
- object code = InheritanceRules.InheritanceCodeForClientCompare(type.InheritanceCode, dm.SqlType);
- SqlExpression match = sql.Value(dt, sql.Default(discriminator), code, true, source);
- whens.Add(new SqlTypeCaseWhen(match, defaultProjection));
- }
- }
- if (@else == null) {
- throw Error.EmptyCaseNotSupported();
- }
- whens.Add(@else); // Add the else at the end.
-
- return sql.TypeCase(root.Type, root, dm, whens.ToArray(), source);
- }
- }
- /// <summary>
- /// Check whether this member will be preloaded.
- /// </summary>
- private bool IsPreloaded(MemberInfo member) {
- if (this.services.Context.LoadOptions == null) {
- return false;
- }
- return this.services.Context.LoadOptions.IsPreloaded(member);
- }
- private SqlNew BuildProjectionInternal(SqlExpression item, MetaType rowType, IEnumerable<MetaDataMember> members, bool allowDeferred, SqlLink link, Expression source) {
- List<SqlMemberAssign> bindings = new List<SqlMemberAssign>();
- foreach (MetaDataMember mm in members) {
- if (allowDeferred && (mm.IsAssociation || mm.IsDeferred)) {
- // check if this member is the reverse association to the supplied link
- if (link != null && mm != link.Member && mm.IsAssociation
- && mm.MappedName == link.Member.MappedName
- && !mm.Association.IsMany
- && !IsPreloaded(link.Member.Member)) {
- // place a new link here with an expansion that is previous link's root expression.
- // this will allow joins caused by reverse association references to 'melt' away. :-)
- SqlLink mlink = this.BuildLink(item, mm, source);
- mlink.Expansion = link.Expression;
- bindings.Add(new SqlMemberAssign(mm.Member, mlink));
- }
- else {
- bindings.Add(new SqlMemberAssign(mm.Member, this.BuildLink(item, mm, source)));
- }
- }
- else if (!mm.IsAssociation) {
- bindings.Add(new SqlMemberAssign(mm.Member, sql.Member(item, mm)));
- }
- }
- ConstructorInfo cons = rowType.Type.GetConstructor(BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic, null, System.Type.EmptyTypes, null);
- if (cons == null) {
- throw Error.MappedTypeMustHaveDefaultConstructor(rowType.Type);
- }
- return sql.New(rowType, cons, null, null, bindings, source);
- }
- private SqlLink BuildLink(SqlExpression item, MetaDataMember member, Expression source) {
- if (member.IsAssociation) {
- SqlExpression[] exprs = new SqlExpression[member.Association.ThisKey.Count];
- for (int i = 0, n = exprs.Length; i < n; i++) {
- MetaDataMember mm = member.Association.ThisKey[i];
- exprs[i] = sql.Member(item, mm.Member);
- }
- MetaType otherType = member.Association.OtherType;
- return new SqlLink(new object(), otherType, member.Type, typeProvider.From(member.Type), item, member, exprs, null, source);
- }
- else {
- // if not association link is always based on primary key
- MetaType thisType = member.DeclaringType;
- System.Diagnostics.Debug.Assert(thisType.IsEntity);
- List<SqlExpression> exprs = new List<SqlExpression>();
- foreach (MetaDataMember mm in thisType.IdentityMembers) {
- exprs.Add(sql.Member(item, mm.Member));
- }
- SqlExpression expansion = sql.Member(item, member.Member);
- return new SqlLink(new object(), thisType, member.Type, typeProvider.From(member.Type), item, member, exprs, expansion, source);
- }
- }
- internal SqlNode TranslateLink(SqlLink link, bool asExpression) {
- return this.TranslateLink(link, link.KeyExpressions, asExpression);
- }
- /// <summary>
- /// Create an Expression representing the given association and key value expressions.
- /// </summary>
- internal static Expression TranslateAssociation(DataContext context, MetaAssociation association, Expression otherSource, Expression[] keyValues, Expression thisInstance) {
- if (association == null)
- throw Error.ArgumentNull("association");
- if (keyValues == null)
- throw Error.ArgumentNull("keyValues");
- if (context.LoadOptions!=null) {
- LambdaExpression subquery = context.LoadOptions.GetAssociationSubquery(association.ThisMember.Member);
- if (subquery!=null) {
- RelationComposer rc = new RelationComposer(subquery.Parameters[0], association, otherSource, thisInstance);
- return rc.Visit(subquery.Body);
- }
- }
- return WhereClauseFromSourceAndKeys(otherSource, association.OtherKey.ToArray(), keyValues);
- }
- internal static Expression WhereClauseFromSourceAndKeys(Expression source, MetaDataMember[] keyMembers, Expression [] keyValues) {
- Type elementType = TypeSystem.GetElementType(source.Type);
- ParameterExpression p = Expression.Parameter(elementType, "p");
- Expression whereExpression=null;
- for (int i = 0; i < keyMembers.Length; i++) {
- MetaDataMember metaMember = keyMembers[i];
- Expression parameterAsDeclaring = elementType == metaMember.Member.DeclaringType ?
- (Expression)p : (Expression)Expression.Convert(p, metaMember.Member.DeclaringType);
- Expression memberExpression = (metaMember.Member is FieldInfo)
- ? Expression.Field(parameterAsDeclaring, (FieldInfo)metaMember.Member)
- : Expression.Property(parameterAsDeclaring, (PropertyInfo)metaMember.Member);
- Expression keyValue = keyValues[i];
- if (keyValue.Type != memberExpression.Type)
- keyValue = Expression.Convert(keyValue, memberExpression.Type);
- Expression memberEqualityExpression = Expression.Equal(memberExpression, keyValue);
- whereExpression = (whereExpression != null)
- ? Expression.And(whereExpression, memberEqualityExpression)
- : memberEqualityExpression;
- }
- Expression sequenceExpression = Expression.Call(typeof(Enumerable), "Where", new Type[] {p.Type}, source, Expression.Lambda(whereExpression, p));
- return sequenceExpression;
- }
- /// <summary>
- /// Composes a subquery into a linked association.
- /// </summary>
- private class RelationComposer : ExpressionVisitor {
- ParameterExpression parameter;
- MetaAssociation association;
- Expression otherSouce;
- Expression parameterReplacement;
- internal RelationComposer(ParameterExpression parameter, MetaAssociation association, Expression otherSouce, Expression parameterReplacement) {
- if (parameter==null)
- throw Error.ArgumentNull("parameter");
- if (association == null)
- throw Error.ArgumentNull("association");
- if (otherSouce == null)
- throw Error.ArgumentNull("otherSouce");
- if (parameterReplacement==null)
- throw Error.ArgumentNull("parameterReplacement");
- this.parameter = parameter;
- this.association = association;
- this.otherSouce = otherSouce;
- this.parameterReplacement = parameterReplacement;
- }
- internal override Expression VisitParameter(ParameterExpression p) {
- if (p == parameter) {
- return this.parameterReplacement;
- }
- return base.VisitParameter(p);
- }
- private static Expression[] GetKeyValues(Expression expr, ReadOnlyCollection<MetaDataMember> keys) {
- List<Expression> values = new List<Expression>();
- foreach(MetaDataMember key in keys){
- values.Add(Expression.PropertyOrField(expr, key.Name));
- }
- return values.ToArray();
- }
- internal override Expression VisitMemberAccess(MemberExpression m) {
- if (MetaPosition.AreSameMember(m.Member, this.association.ThisMember.Member)) {
- Expression[] keyValues = GetKeyValues(this.Visit(m.Expression), this.association.ThisKey);
- return WhereClauseFromSourceAndKeys(this.otherSouce, this.association.OtherKey.ToArray(), keyValues);
- }
- Expression exp = this.Visit(m.Expression);
- if (exp != m.Expression) {
- if (exp.Type != m.Expression.Type && m.Member.Name == "Count" && TypeSystem.IsSequenceType(exp.Type)) {
- return Expression.Call(typeof(Enumerable), "Count", new Type[] {TypeSystem.GetElementType(exp.Type)}, exp);
- }
- return Expression.MakeMemberAccess(exp, m.Member);
- }
- return m;
- }
- }
- internal SqlNode TranslateLink(SqlLink link, List<SqlExpression> keyExpressions, bool asExpression) {
- MetaDataMember mm = link.Member;
- if (mm.IsAssociation) {
- // Create the row source.
- MetaType otherType = mm.Association.OtherType;
- Type tableType = otherType.InheritanceRoot.Type;
- ITable table = this.services.Context.GetTable(tableType);
- Expression source = new LinkedTableExpression(link, table, typeof(IQueryable<>).MakeGenericType(otherType.Type));
- // Build key expression nodes.
- Expression[] keyExprs = new Expression[keyExpressions.Count];
- for (int i = 0; i < keyExpressions.Count; ++i) {
- MetaDataMember metaMember = mm.Association.OtherKey[i];
- Type memberType = TypeSystem.GetMemberType(metaMember.Member);
- keyExprs[i] = InternalExpression.Known(keyExpressions[i], memberType);
- }
- Expression lex = link.Expression != null
- ? (Expression)InternalExpression.Known(link.Expression)
- : (Expression)Expression.Constant(null, link.Member.Member.DeclaringType);
- Expression expr = TranslateAssociation(this.services.Context, mm.Association, source, keyExprs, lex);
- // Convert
- QueryConverter qc = new QueryConverter(this.services, this.typeProvider, this, this.sql);
- SqlSelect sel = (SqlSelect)qc.ConvertInner(expr, link.SourceExpression);
- // Turn it into an expression is necessary
- SqlNode result = sel;
- if (asExpression) {
- if (mm.Association.IsMany) {
- result = new SqlSubSelect(SqlNodeType.Multiset, link.ClrType, link.SqlType, sel);
- }
- else {
- result = new SqlSubSelect(SqlNodeType.Element, link.ClrType, link.SqlType, sel);
- }
- }
- return result;
- }
- else {
- System.Diagnostics.Debug.Assert(link.Expansion != null);
- System.Diagnostics.Debug.Assert(link.KeyExpressions == keyExpressions);
- // deferred expression already defined...
- return link.Expansion;
- }
- }
- [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification="These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
- internal SqlExpression TranslateEquals(SqlBinary expr) {
- System.Diagnostics.Debug.Assert(
- expr.NodeType == SqlNodeType.EQ || expr.NodeType == SqlNodeType.NE ||
- expr.NodeType == SqlNodeType.EQ2V || expr.NodeType == SqlNodeType.NE2V);
- SqlExpression eLeft = expr.Left;
- SqlExpression eRight = expr.Right;
- if (eRight.NodeType == SqlNodeType.Element) {
- SqlSubSelect sub = (SqlSubSelect)eRight;
- SqlAlias alias = new SqlAlias(sub.Select);
- SqlAliasRef aref = new SqlAliasRef(alias);
- SqlSelect select = new SqlSelect(aref, alias, expr.SourceExpression);
- select.Where = sql.Binary(expr.NodeType, sql.DoNotVisitExpression(eLeft), aref);
- return sql.SubSelect(SqlNodeType.Exists, select);
- }
- else if (eLeft.NodeType == SqlNodeType.Element) {
- SqlSubSelect sub = (SqlSubSelect)eLeft;
- SqlAlias alias = new SqlAlias(sub.Select);
- SqlAliasRef aref = new SqlAliasRef(alias);
- SqlSelect select = new SqlSelect(aref, alias, expr.SourceExpression);
- select.Where = sql.Binary(expr.NodeType, sql.DoNotVisitExpression(eRight), aref);
- return sql.SubSelect(SqlNodeType.Exists, select);
- }
- MetaType mtLeft = TypeSource.GetSourceMetaType(eLeft, this.services.Model);
- MetaType mtRight = TypeSource.GetSourceMetaType(eRight, this.services.Model);
- if (eLeft.NodeType == SqlNodeType.TypeCase) {
- eLeft = BestIdentityNode((SqlTypeCase)eLeft);
- }
- if (eRight.NodeType == SqlNodeType.TypeCase) {
- eRight = BestIdentityNode((SqlTypeCase)eRight);
- }
- if (mtLeft.IsEntity && mtRight.IsEntity && mtLeft.Table != mtRight.Table) {
- throw Error.CannotCompareItemsAssociatedWithDifferentTable();
- }
- // do simple or no translation for non-structural types
- if (!mtLeft.IsEntity && !mtRight.IsEntity &&
- (eLeft.NodeType != SqlNodeType.New || eLeft.SqlType.CanBeColumn) &&
- (eRight.NodeType != SqlNodeType.New || eRight.SqlType.CanBeColumn)) {
- if (expr.NodeType == SqlNodeType.EQ2V || expr.NodeType == SqlNodeType.NE2V) {
- return this.TranslateEqualsOp(expr.NodeType, sql.DoNotVisitExpression(expr.Left), sql.DoNotVisitExpression(expr.Right), false);
- }
- return expr;
- }
- // If the two types are not comparable, we return the predicate "1=0".
- if ((mtLeft != mtRight) && (mtLeft.InheritanceRoot != mtRight.InheritanceRoot)) {
- return sql.Binary(SqlNodeType.EQ, sql.ValueFromObject(0,expr.SourceExpression), sql.ValueFromObject(1,expr.SourceExpression));
- }
- List<SqlExpression> exprs1;
- List<SqlExpression> exprs2;
- SqlLink link1 = eLeft as SqlLink;
- if (link1 != null && link1.Member.IsAssociation && link1.Member.Association.IsForeignKey) {
- exprs1 = link1.KeyExpressions;
- }
- else {
- exprs1 = this.GetIdentityExpressions(mtLeft, sql.DoNotVisitExpression(eLeft));
- }
- SqlLink link2 = eRight as SqlLink;
- if (link2 != null && link2.Member.IsAssociation && link2.Member.Association.IsForeignKey) {
- exprs2 = link2.KeyExpressions;
- }
- else {
- exprs2 = this.GetIdentityExpressions(mtRight, sql.DoNotVisitExpression(eRight));
- }
- System.Diagnostics.Debug.Assert(exprs1.Count > 0);
- System.Diagnostics.Debug.Assert(exprs2.Count > 0);
- System.Diagnostics.Debug.Assert(exprs1.Count == exprs2.Count);
- SqlExpression exp = null;
- SqlNodeType eqKind = (expr.NodeType == SqlNodeType.EQ2V || expr.NodeType == SqlNodeType.NE2V) ? SqlNodeType.EQ2V : SqlNodeType.EQ;
- for (int i = 0, n = exprs1.Count; i < n; i++) {
- SqlExpression eq = this.TranslateEqualsOp(eqKind, exprs1[i], exprs2[i], !mtLeft.IsEntity);
- if (exp == null) {
- exp = eq;
- }
- else {
- exp = sql.Binary(SqlNodeType.And, exp, eq);
- }
- }
- if (expr.NodeType == SqlNodeType.NE || expr.NodeType == SqlNodeType.NE2V) {
- exp = sql.Unary(SqlNodeType.Not, exp, exp.SourceExpression);
- }
- return exp;
- }
- private SqlExpression TranslateEqualsOp(SqlNodeType op, SqlExpression left, SqlExpression right, bool allowExpand) {
- switch (op) {
- case SqlNodeType.EQ:
- case SqlNodeType.NE:
- return sql.Binary(op, left, right);
- case SqlNodeType.EQ2V:
- if (SqlExpressionNullability.CanBeNull(left) != false &&
- SqlExpressionNullability.CanBeNull(right) != false) {
- SqlNodeType eqOp = allowExpand ? SqlNodeType.EQ2V : SqlNodeType.EQ;
- return
- sql.Binary(SqlNodeType.Or,
- sql.Binary(SqlNodeType.And,
- sql.Unary(SqlNodeType.IsNull, (SqlExpression)SqlDuplicator.Copy(left)),
- sql.Unary(SqlNodeType.IsNull, (SqlExpression)SqlDuplicator.Copy(right))
- ),
- sql.Binary(SqlNodeType.And,
- sql.Binary(SqlNodeType.And,
- sql.Unary(SqlNodeType.IsNotNull, (SqlExpression)SqlDuplicator.Copy(left)),
- sql.Unary(SqlNodeType.IsNotNull, (SqlExpression)SqlDuplicator.Copy(right))
- ),
- sql.Binary(eqOp, left, right)
- )
- );
- }
- else {
- SqlNodeType eqOp = allowExpand ? SqlNodeType.EQ2V : SqlNodeType.EQ;
- return sql.Binary(eqOp, left, right);
- }
- case SqlNodeType.NE2V:
- if (SqlExpressionNullability.CanBeNull(left) != false &&
- SqlExpressionNullability.CanBeNull(right) != false) {
- SqlNodeType eqOp = allowExpand ? SqlNodeType.EQ2V : SqlNodeType.EQ;
- return
- sql.Unary(SqlNodeType.Not,
- sql.Binary(SqlNodeType.Or,
- sql.Binary(SqlNodeType.And,
- sql.Unary(SqlNodeType.IsNull, (SqlExpression)SqlDuplicator.Copy(left)),
- sql.Unary(SqlNodeType.IsNull, (SqlExpression)SqlDuplicator.Copy(right))
- ),
- sql.Binary(SqlNodeType.And,
- sql.Binary(SqlNodeType.And,
- sql.Unary(SqlNodeType.IsNotNull, (SqlExpression)SqlDuplicator.Copy(left)),
- sql.Unary(SqlNodeType.IsNotNull, (SqlExpression)SqlDuplicator.Copy(right))
- ),
- sql.Binary(eqOp, left, right)
- )
- )
- );
- }
- else {
- SqlNodeType neOp = allowExpand ? SqlNodeType.NE2V : SqlNodeType.NE;
- return sql.Binary(neOp, left, right);
- }
- default:
- throw Error.UnexpectedNode(op);
- }
- }
- internal SqlExpression TranslateLinkEquals(SqlBinary bo) {
- SqlLink link1 = bo.Left as SqlLink;
- SqlLink link2 = bo.Right as SqlLink;
- if ((link1 != null && link1.Member.IsAssociation && link1.Member.Association.IsForeignKey) ||
- (link2 != null && link2.Member.IsAssociation && link2.Member.Association.IsForeignKey)) {
- return this.TranslateEquals(bo);
- }
- return bo;
- }
- internal SqlExpression TranslateLinkIsNull(SqlUnary expr) {
- System.Diagnostics.Debug.Assert(expr.NodeType == SqlNodeType.IsNull || expr.NodeType == SqlNodeType.IsNotNull);
- SqlLink link = expr.Operand as SqlLink;
- if (!(link != null && link.Member.IsAssociation && link.Member.Association.IsForeignKey)) {
- return expr;
- }
- List<SqlExpression> exprs = link.KeyExpressions;
- System.Diagnostics.Debug.Assert(exprs.Count > 0);
- SqlExpression exp = null;
- SqlNodeType combo = (expr.NodeType == SqlNodeType.IsNull) ? SqlNodeType.Or : SqlNodeType.And;
- for (int i = 0, n = exprs.Count; i < n; i++) {
- SqlExpression compare = sql.Unary(expr.NodeType, sql.DoNotVisitExpression(exprs[i]), expr.SourceExpression);
- if (exp == null) {
- exp = compare;
- }
- else {
- exp = sql.Binary(combo, exp, compare);
- }
- }
- return exp;
- }
- /// <summary>
- /// Find the alternative in type case that will best identify the object.
- /// If there is a SqlNew it is expected to have all the identity fields.
- /// If there is no SqlNew then we must be dealing with all literal NULL alternatives. In this case,
- /// just return the first one.
- /// </summary>
- private static SqlExpression BestIdentityNode(SqlTypeCase tc) {
- foreach (SqlTypeCaseWhen when in tc.Whens) {
- if (when.TypeBinding.NodeType == SqlNodeType.New) {
- return when.TypeBinding;
- }
- }
- return tc.Whens[0].TypeBinding; // There were no SqlNews, take the first alternative
- }
- private static bool IsPublic(MemberInfo mi) {
- FieldInfo fi = mi as FieldInfo;
- if (fi != null) {
- return fi.IsPublic;
- }
- PropertyInfo pi = mi as PropertyInfo;
- if (pi != null) {
- if (pi.CanRead) {
- var gm = pi.GetGetMethod();
- if (gm != null) {
- return gm.IsPublic;
- }
- }
- }
- return false;
- }
- [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")]
- private IEnumerable<MetaDataMember> GetIdentityMembers(MetaType type) {
- if (type.IsEntity) {
- return type.IdentityMembers;
- }
- return type.DataMembers.Where(m => IsPublic(m.Member));
- }
- private List<SqlExpression> GetIdentityExpressions(MetaType type, SqlExpression expr) {
- List<MetaDataMember> members = GetIdentityMembers(type).ToList();
- System.Diagnostics.Debug.Assert(members.Count > 0);
- List<SqlExpression> exprs = new List<SqlExpression>(members.Count);
- foreach (MetaDataMember mm in members) {
- exprs.Add(sql.Member((SqlExpression)SqlDuplicator.Copy(expr), mm));
- }
- return exprs;
- }
- }
- }