2 using System.Collections;
3 using System.Collections.Generic;
4 using System.Collections.ObjectModel;
5 using System.ComponentModel;
7 using System.Data.Common;
8 using System.Data.Linq;
9 using System.Data.Linq.Mapping;
10 using System.Data.Linq.Provider;
11 using System.Data.SqlClient;
12 using System.Diagnostics;
15 using System.Linq.Expressions;
16 using System.Reflection;
18 using System.Globalization;
19 using System.Diagnostics.CodeAnalysis;
20 using Me = System.Data.Linq.SqlClient;
21 using System.Runtime.Versioning;
22 using System.Runtime.CompilerServices;
24 namespace System.Data.Linq.SqlClient {
25 public sealed class Sql2000Provider : SqlProvider {
26 public Sql2000Provider()
27 : base(ProviderMode.Sql2000) {
31 public sealed class Sql2005Provider : SqlProvider {
32 public Sql2005Provider()
33 : base(ProviderMode.Sql2005) {
37 public sealed class Sql2008Provider : SqlProvider {
38 public Sql2008Provider()
39 : base(ProviderMode.Sql2008) {
43 [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification="Unknown reason.")]
44 public class SqlProvider : IReaderProvider, IConnectionUser {
45 private IDataServices services;
46 private SqlConnectionManager conManager;
47 private TypeSystemProvider typeProvider;
48 private SqlFactory sqlFactory;
49 private Translator translator;
50 private IObjectReaderCompiler readerCompiler;
51 private bool disposed;
52 private int commandTimeout;
54 private TextWriter log;
55 string dbName = string.Empty;
58 private int queryCount;
59 private bool checkQueries;
60 private OptimizationFlags optimizationFlags = OptimizationFlags.All;
61 private bool enableCacheLookup = true;
62 private ProviderMode mode = ProviderMode.NotYetDecided;
63 private bool deleted = false;
66 private bool collectPerfInfo;
67 private bool collectPerfInfoInitialized = false;
68 private bool collectQueryPerf;
70 internal bool CollectPerfInfo {
72 if (!collectPerfInfoInitialized) {
73 string s = System.Environment.GetEnvironmentVariable("CollectDLinqPerfInfo");
74 collectPerfInfo = (s != null) && (s == "On");
75 collectPerfInfoInitialized = true;
77 return this.collectPerfInfo;
81 internal bool CollectQueryPerf {
82 get { return this.collectQueryPerf; }
86 internal enum ProviderMode {
94 const string SqlCeProviderInvariantName = "System.Data.SqlServerCe.3.5";
95 const string SqlCeDataReaderTypeName = "System.Data.SqlServerCe.SqlCeDataReader";
96 const string SqlCeConnectionTypeName = "System.Data.SqlServerCe.SqlCeConnection";
97 const string SqlCeTransactionTypeName = "System.Data.SqlServerCe.SqlCeTransaction";
99 internal ProviderMode Mode {
102 this.CheckInitialized();
103 this.InitializeProviderMode();
108 private void InitializeProviderMode() {
109 if (this.mode == ProviderMode.NotYetDecided) {
111 this.mode = ProviderMode.SqlCE;
112 } else if (this.IsServer2KOrEarlier) {
113 this.mode = ProviderMode.Sql2000;
115 else if (this.IsServer2005) {
116 this.mode = ProviderMode.Sql2005;
118 this.mode = ProviderMode.Sql2008;
121 if (this.typeProvider == null) {
123 case ProviderMode.Sql2000:
124 this.typeProvider = SqlTypeSystem.Create2000Provider();
126 case ProviderMode.Sql2005:
127 this.typeProvider = SqlTypeSystem.Create2005Provider();
129 case ProviderMode.Sql2008:
130 this.typeProvider = SqlTypeSystem.Create2008Provider();
132 case ProviderMode.SqlCE:
133 this.typeProvider = SqlTypeSystem.CreateCEProvider();
136 System.Diagnostics.Debug.Assert(false);
140 if (this.sqlFactory == null) {
141 this.sqlFactory = new SqlFactory(this.typeProvider, this.services.Model);
142 this.translator = new Translator(this.services, this.sqlFactory, this.typeProvider);
147 /// Return true if the current connection is SQLCE.
149 private bool IsSqlCe {
151 DbConnection con = conManager.UseConnection(this);
153 if (String.CompareOrdinal(con.GetType().FullName, SqlCeConnectionTypeName) == 0) {
157 conManager.ReleaseConnection(this);
164 /// Return true if this is a 2K (or earlier) server. This may be a round trip to the server.
166 private bool IsServer2KOrEarlier {
168 DbConnection con = conManager.UseConnection(this);
170 string serverVersion = con.ServerVersion;
171 if (serverVersion.StartsWith("06.00.", StringComparison.Ordinal)) {
174 else if (serverVersion.StartsWith("06.50.", StringComparison.Ordinal)) {
177 else if (serverVersion.StartsWith("07.00.", StringComparison.Ordinal)) {
180 else if (serverVersion.StartsWith("08.00.", StringComparison.Ordinal)) {
186 conManager.ReleaseConnection(this);
192 /// Return true if this is a SQL 2005 server. This may be a round trip to the server.
194 private bool IsServer2005 {
196 DbConnection con = conManager.UseConnection(this);
198 string serverVersion = con.ServerVersion;
199 if (serverVersion.StartsWith("09.00.", StringComparison.Ordinal)) {
205 conManager.ReleaseConnection(this);
210 DbConnection IProvider.Connection {
213 this.CheckInitialized();
214 return this.conManager.Connection;
218 TextWriter IProvider.Log {
221 this.CheckInitialized();
226 this.CheckInitialized();
231 DbTransaction IProvider.Transaction {
234 this.CheckInitialized();
235 return this.conManager.Transaction;
239 this.CheckInitialized();
240 this.conManager.Transaction = value;
244 int IProvider.CommandTimeout {
247 return this.commandTimeout;
251 this.commandTimeout = value;
256 /// Expose a test hook which controls which SQL optimizations are executed.
258 internal OptimizationFlags OptimizationFlags {
261 return this.optimizationFlags;
265 this.optimizationFlags = value;
270 /// Validate queries as they are generated.
272 internal bool CheckQueries {
279 checkQueries = value;
283 internal bool EnableCacheLookup {
286 return this.enableCacheLookup;
290 this.enableCacheLookup = value;
294 internal int QueryCount {
297 return this.queryCount;
301 internal int MaxUsers {
304 return this.conManager.MaxUsers;
308 IDataServices IReaderProvider.Services {
309 get { return this.services; }
312 IConnectionManager IReaderProvider.ConnectionManager {
313 get { return this.conManager; }
316 public SqlProvider() {
317 this.mode = ProviderMode.NotYetDecided;
320 internal SqlProvider(ProviderMode mode) {
324 private void CheckInitialized() {
325 if (this.services == null) {
326 throw Error.ContextNotInitialized();
329 private void CheckNotDeleted() {
331 throw Error.DatabaseDeleteThroughContext();
335 [ResourceExposure(ResourceScope.Machine)] // connection parameter may refer to filenames.
336 void IProvider.Initialize(IDataServices dataServices, object connection) {
337 if (dataServices == null) {
338 throw Error.ArgumentNull("dataServices");
340 this.services = dataServices;
343 DbTransaction tx = null;
345 string fileOrServerOrConnectionString = connection as string;
346 if (fileOrServerOrConnectionString != null) {
347 string connectionString = this.GetConnectionString(fileOrServerOrConnectionString);
348 this.dbName = this.GetDatabaseName(connectionString);
349 if (this.dbName.EndsWith(".sdf", StringComparison.OrdinalIgnoreCase)) {
350 this.mode = ProviderMode.SqlCE;
352 if (this.mode == ProviderMode.SqlCE) {
353 DbProviderFactory factory = SqlProvider.GetProvider(SqlCeProviderInvariantName);
354 if (factory == null) {
355 throw Error.ProviderNotInstalled(this.dbName, SqlCeProviderInvariantName);
357 con = factory.CreateConnection();
360 con = new SqlConnection();
362 con.ConnectionString = connectionString;
365 // We only support SqlTransaction and SqlCeTransaction
366 tx = connection as SqlTransaction;
368 // See if it's a SqlCeTransaction
369 if (connection.GetType().FullName == SqlCeTransactionTypeName) {
370 tx = connection as DbTransaction;
374 connection = tx.Connection;
376 con = connection as DbConnection;
378 throw Error.InvalidConnectionArgument("connection");
380 if (con.GetType().FullName == SqlCeConnectionTypeName) {
381 this.mode = ProviderMode.SqlCE;
383 this.dbName = this.GetDatabaseName(con.ConnectionString);
386 // initialize to the default command timeout
387 using (DbCommand c = con.CreateCommand()) {
388 this.commandTimeout = c.CommandTimeout;
391 int maxUsersPerConnection = 1;
392 if (con.ConnectionString.IndexOf("MultipleActiveResultSets", StringComparison.OrdinalIgnoreCase) >= 0) {
393 DbConnectionStringBuilder builder = new DbConnectionStringBuilder();
394 builder.ConnectionString = con.ConnectionString;
395 if (string.Compare((string)builder["MultipleActiveResultSets"], "true", StringComparison.OrdinalIgnoreCase) == 0) {
396 maxUsersPerConnection = 10;
400 // If fileOrServerOrConnectionString != null, that means we just created the connection instance and we have to tell
401 // the SqlConnectionManager that it should dispose the connection when the context is disposed. Otherwise the user owns
402 // the connection and should dispose of it themselves.
403 this.conManager = new SqlConnectionManager(this, con, maxUsersPerConnection, fileOrServerOrConnectionString != null /*disposeConnection*/);
405 this.conManager.Transaction = tx;
409 SqlNode.Formatter = new SqlFormatter();
414 if (this.mode == ProviderMode.SqlCE) {
415 readerType = con.GetType().Module.GetType(SqlCeDataReaderTypeName);
417 else if (con is SqlConnection) {
418 readerType = typeof(SqlDataReader);
421 readerType = typeof(DbDataReader);
423 this.readerCompiler = new ObjectReaderCompiler(readerType, this.services);
425 this.readerCompiler = new ObjectReaderBuilder(this, this.services);
429 private static DbProviderFactory GetProvider(string providerName) {
431 DbProviderFactories.GetFactoryClasses().Rows.OfType<DataRow>()
432 .Select(r => (string)r["InvariantName"])
433 .Contains(providerName, StringComparer.OrdinalIgnoreCase);
435 return DbProviderFactories.GetFactory(providerName);
440 #region Dispose\Finalize
441 public void Dispose() {
442 this.disposed = true;
444 // Technically, calling GC.SuppressFinalize is not required because the class does not
445 // have a finalizer, but it does no harm, protects against the case where a finalizer is added
446 // in the future, and prevents an FxCop warning.
447 GC.SuppressFinalize(this);
450 // Not implementing finalizer here because there are no unmanaged resources
451 // to release. See http://msdnwiki.microsoft.com/en-us/mtpswiki/12afb1ea-3a17-4a3f-a1f0-fcdb853e2359.aspx
453 // The bulk of the clean-up code is implemented in Dispose(bool)
454 protected virtual void Dispose(bool disposing) {
455 // Implemented but empty so that derived contexts can implement
456 // a finalizer that potentially cleans up unmanaged resources.
458 this.services = null;
459 if (this.conManager != null) {
460 this.conManager.DisposeConnection();
462 this.conManager = null;
463 this.typeProvider = null;
464 this.sqlFactory = null;
465 this.translator = null;
466 this.readerCompiler = null;
471 internal void CheckDispose() {
473 throw Error.ProviderCannotBeUsedAfterDispose();
478 private string GetConnectionString(string fileOrServerOrConnectionString) {
479 if (fileOrServerOrConnectionString.IndexOf('=') >= 0) {
480 return fileOrServerOrConnectionString;
483 DbConnectionStringBuilder builder = new DbConnectionStringBuilder();
484 if (fileOrServerOrConnectionString.EndsWith(".mdf", StringComparison.OrdinalIgnoreCase)) {
485 // if just a database file is specified, default to local SqlExpress instance
486 builder.Add("AttachDBFileName", fileOrServerOrConnectionString);
487 builder.Add("Server", "localhost\\sqlexpress");
488 builder.Add("Integrated Security", "SSPI");
489 builder.Add("User Instance", "true");
490 builder.Add("MultipleActiveResultSets", "true");
492 else if (fileOrServerOrConnectionString.EndsWith(".sdf", StringComparison.OrdinalIgnoreCase)) {
493 // A SqlCE database file has been specified
494 builder.Add("Data Source", fileOrServerOrConnectionString);
497 builder.Add("Server", fileOrServerOrConnectionString);
498 builder.Add("Database", this.services.Model.DatabaseName);
499 builder.Add("Integrated Security", "SSPI");
501 return builder.ToString();
505 private string GetDatabaseName(string constr) {
506 DbConnectionStringBuilder builder = new DbConnectionStringBuilder();
507 builder.ConnectionString = constr;
509 if (builder.ContainsKey("Initial Catalog")) {
510 return (string)builder["Initial Catalog"];
512 else if (builder.ContainsKey("Database")) {
513 return (string)builder["Database"];
515 else if (builder.ContainsKey("AttachDBFileName")) {
516 return (string)builder["AttachDBFileName"];
518 else if (builder.ContainsKey("Data Source")
519 && ((string)builder["Data Source"]).EndsWith(".sdf", StringComparison.OrdinalIgnoreCase)) {
520 return (string)builder["Data Source"];
523 return this.services.Model.DatabaseName;
527 [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.")]
528 [ResourceExposure(ResourceScope.None)] // Exposure is via other methods that set dbName.
529 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] // File.Exists method call.
530 void IProvider.CreateDatabase() {
532 this.CheckInitialized();
533 // Don't need to call CheckNotDeleted() here since we allow CreateDatabase after DeleteDatabase
534 // Don't need to call InitializeProviderMode() here since we don't need to know the provider to do this.
535 string catalog = null;
536 string filename = null;
538 DbConnectionStringBuilder builder = new DbConnectionStringBuilder();
539 builder.ConnectionString = this.conManager.Connection.ConnectionString;
541 if (this.conManager.Connection.State == ConnectionState.Closed) {
542 if (this.mode == ProviderMode.SqlCE) {
543 if (!File.Exists(this.dbName)) {
544 Type engineType = this.conManager.Connection.GetType().Module.GetType("System.Data.SqlServerCe.SqlCeEngine");
545 object engine = Activator.CreateInstance(engineType, new object[] { builder.ToString() });
547 engineType.InvokeMember("CreateDatabase", BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod, null, engine, new object[] { }, CultureInfo.InvariantCulture);
549 catch (TargetInvocationException tie) {
550 throw tie.InnerException;
553 IDisposable disp = engine as IDisposable;
560 throw Error.CreateDatabaseFailedBecauseSqlCEDatabaseAlreadyExists(this.dbName);
564 // get connection string w/o reference to new catalog
566 if (builder.TryGetValue("Initial Catalog", out val)) {
567 catalog = val.ToString();
568 builder.Remove("Initial Catalog");
570 if (builder.TryGetValue("Database", out val)) {
571 catalog = val.ToString();
572 builder.Remove("Database");
574 if (builder.TryGetValue("AttachDBFileName", out val)) {
575 filename = val.ToString();
576 builder.Remove("AttachDBFileName");
579 this.conManager.Connection.ConnectionString = builder.ToString();
582 if (this.mode == ProviderMode.SqlCE) {
583 if (File.Exists(this.dbName)) {
584 throw Error.CreateDatabaseFailedBecauseSqlCEDatabaseAlreadyExists(this.dbName);
588 if (builder.TryGetValue("Initial Catalog", out val)) {
589 catalog = val.ToString();
591 if (builder.TryGetValue("Database", out val)) {
592 catalog = val.ToString();
594 if (builder.TryGetValue("AttachDBFileName", out val)) {
595 filename = val.ToString();
599 if (String.IsNullOrEmpty(catalog)) {
600 if (!String.IsNullOrEmpty(filename)) {
601 catalog = Path.GetFullPath(filename);
603 else if (!String.IsNullOrEmpty(this.dbName)) {
604 catalog = this.dbName;
607 throw Error.CouldNotDetermineCatalogName();
611 this.conManager.UseConnection(this);
612 this.conManager.AutoClose = false;
615 if (this.services.Model.GetTables().FirstOrDefault() == null) {
616 // we have no tables to create
617 throw Error.CreateDatabaseFailedBecauseOfContextWithNoTables(this.services.Model.DatabaseName);
620 this.deleted = false;
623 if (this.mode == ProviderMode.SqlCE) {
626 foreach (MetaTable table in this.services.Model.GetTables()) {
627 string command = SqlBuilder.GetCreateTableCommand(table);
628 if (!String.IsNullOrEmpty(command)) {
629 this.ExecuteCommand(command);
632 // create all foreign keys after all tables are defined
633 foreach (MetaTable table in this.services.Model.GetTables()) {
634 foreach (string command in SqlBuilder.GetCreateForeignKeyCommands(table)) {
635 if (!String.IsNullOrEmpty(command)) {
636 this.ExecuteCommand(command);
642 string createdb = SqlBuilder.GetCreateDatabaseCommand(catalog, filename, Path.ChangeExtension(filename, ".ldf"));
643 this.ExecuteCommand(createdb);
644 this.conManager.Connection.ChangeDatabase(catalog);
646 // create the schemas that our tables will need
647 // cannot be batched together with the rest of the CREATE TABLES
648 if (this.mode == ProviderMode.Sql2005 || this.mode == ProviderMode.Sql2008) {
649 HashSet<string> schemaCommands = new HashSet<string>();
651 foreach (MetaTable table in this.services.Model.GetTables()) {
652 string schemaCommand = SqlBuilder.GetCreateSchemaForTableCommand(table);
653 if (!string.IsNullOrEmpty(schemaCommand)) {
654 schemaCommands.Add(schemaCommand);
658 foreach (string schemaCommand in schemaCommands) {
659 this.ExecuteCommand(schemaCommand);
663 StringBuilder sb = new StringBuilder();
666 foreach (MetaTable table in this.services.Model.GetTables()) {
667 string createTable = SqlBuilder.GetCreateTableCommand(table);
668 if (!string.IsNullOrEmpty(createTable)) {
669 sb.AppendLine(createTable);
673 // create all foreign keys after all tables are defined
674 foreach (MetaTable table in this.services.Model.GetTables()) {
675 foreach (string createFK in SqlBuilder.GetCreateForeignKeyCommands(table)) {
676 if (!string.IsNullOrEmpty(createFK)) {
677 sb.AppendLine(createFK);
683 // must be on when creating indexes on computed columns
684 sb.Insert(0, "SET ARITHABORT ON" + Environment.NewLine);
685 this.ExecuteCommand(sb.ToString());
690 this.conManager.ReleaseConnection(this);
691 if (this.conManager.Connection is SqlConnection) {
692 SqlConnection.ClearAllPools();
697 [ResourceExposure(ResourceScope.None)] // Exposure is via other methods that set dbName.
698 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] // File.Delete method call.
699 void IProvider.DeleteDatabase() {
701 this.CheckInitialized();
702 // Don't need to call InitializeProviderMode() here since we don't need to know the provider to do this.
704 // 2nd delete is no-op.
708 if (this.mode == ProviderMode.SqlCE) {
709 ((IProvider)this).ClearConnection();
710 System.Diagnostics.Debug.Assert(this.conManager.Connection.State == ConnectionState.Closed);
711 File.Delete(this.dbName);
715 string holdConnStr = conManager.Connection.ConnectionString;
716 DbConnection con = this.conManager.UseConnection(this);
718 con.ChangeDatabase("master");
719 if (con is SqlConnection) {
720 SqlConnection.ClearAllPools();
722 if (this.log != null) {
723 this.log.WriteLine(Strings.LogAttemptingToDeleteDatabase(this.dbName));
725 this.ExecuteCommand(SqlBuilder.GetDropDatabaseCommand(this.dbName));
729 this.conManager.ReleaseConnection(this);
730 if (conManager.Connection.State == ConnectionState.Closed &&
731 string.Compare(conManager.Connection.ConnectionString, holdConnStr, StringComparison.Ordinal) != 0) {
732 // Credential information may have been stripped from the connection
733 // string as a result of opening the connection. Restore the full
734 // connection string.
735 conManager.Connection.ConnectionString = holdConnStr;
741 [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification="Microsoft: Code needs to return false regarless of exception.")]
742 [ResourceExposure(ResourceScope.None)] // Exposure is via other methods that set dbName.
743 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] // File.Exists method call.
744 bool IProvider.DatabaseExists() {
746 this.CheckInitialized();
750 // Don't need to call InitializeProviderMode() here since we don't need to know the provider to do this.
753 if (this.mode == ProviderMode.SqlCE) {
754 exists = File.Exists(this.dbName);
757 string holdConnStr = conManager.Connection.ConnectionString;
759 // If no database name is explicitly specified on the connection,
760 // UseConnection will connect to 'Master', which is why after connecting
761 // we call ChangeDatabase to verify that the database actually exists.
762 this.conManager.UseConnection(this);
763 this.conManager.Connection.ChangeDatabase(this.dbName);
764 this.conManager.ReleaseConnection(this);
766 } catch (Exception) {
768 if (conManager.Connection.State == ConnectionState.Closed &&
769 string.Compare(conManager.Connection.ConnectionString, holdConnStr, StringComparison.Ordinal) != 0) {
770 // Credential information may have been stripped from the connection
771 // string as a result of opening the connection. Restore the full
772 // connection string.
773 conManager.Connection.ConnectionString = holdConnStr;
780 void IConnectionUser.CompleteUse() {
783 void IProvider.ClearConnection() {
785 this.CheckInitialized();
786 this.conManager.ClearConnection();
789 private void ExecuteCommand(string command) {
790 if (this.log != null) {
791 this.log.WriteLine(command);
792 this.log.WriteLine();
794 IDbCommand cmd = this.conManager.Connection.CreateCommand();
795 cmd.CommandTimeout = this.commandTimeout;
796 cmd.Transaction = this.conManager.Transaction;
797 cmd.CommandText = command;
798 cmd.ExecuteNonQuery();
801 ICompiledQuery IProvider.Compile(Expression query) {
803 this.CheckInitialized();
805 throw Error.ArgumentNull("query");
807 this.InitializeProviderMode();
809 SqlNodeAnnotations annotations = new SqlNodeAnnotations();
810 QueryInfo[] qis = this.BuildQuery(query, annotations);
811 CheckSqlCompatibility(qis, annotations);
813 LambdaExpression lambda = query as LambdaExpression;
814 if (lambda != null) {
818 IObjectReaderFactory factory = null;
819 ICompiledSubQuery[] subQueries = null;
820 QueryInfo qi = qis[qis.Length - 1];
821 if (qi.ResultShape == ResultShape.Singleton) {
822 subQueries = this.CompileSubQueries(qi.Query);
823 factory = this.GetReaderFactory(qi.Query, qi.ResultType);
825 else if (qi.ResultShape == ResultShape.Sequence) {
826 subQueries = this.CompileSubQueries(qi.Query);
827 factory = this.GetReaderFactory(qi.Query, TypeSystem.GetElementType(qi.ResultType));
830 return new CompiledQuery(this, query, qis, factory, subQueries);
833 private ICompiledSubQuery CompileSubQuery(SqlNode query, Type elementType, ReadOnlyCollection<Me.SqlParameter> parameters) {
834 query = SqlDuplicator.Copy(query);
835 SqlNodeAnnotations annotations = new SqlNodeAnnotations();
837 QueryInfo[] qis = this.BuildQuery(ResultShape.Sequence, TypeSystem.GetSequenceType(elementType), query, parameters, annotations);
838 System.Diagnostics.Debug.Assert(qis.Length == 1);
839 QueryInfo qi = qis[0];
840 ICompiledSubQuery[] subQueries = this.CompileSubQueries(qi.Query);
841 IObjectReaderFactory factory = this.GetReaderFactory(qi.Query, elementType);
843 CheckSqlCompatibility(qis, annotations);
845 return new CompiledSubQuery(qi, factory, parameters, subQueries);
848 IExecuteResult IProvider.Execute(Expression query) {
850 this.CheckInitialized();
851 this.CheckNotDeleted();
853 throw Error.ArgumentNull("query");
855 this.InitializeProviderMode();
857 #if PERFORMANCE_BUILD
858 PerformanceCounter pcBuildQuery = null, bpcBuildQuery = null, pcExecQuery = null, bpcExecQuery = null,
859 pcSession = null, bpcSession = null;
860 PerfTimer timerAll = null, timer = null;
861 if (this.CollectPerfInfo) {
862 string s = System.Environment.GetEnvironmentVariable("EnableDLinqQueryPerf");
863 collectQueryPerf = (s != null && s == "On");
865 if (collectQueryPerf) {
866 pcBuildQuery = new PerformanceCounter("DLinq", "BuildQueryElapsedTime", false);
867 bpcBuildQuery = new PerformanceCounter("DLinq", "BuildQueryElapsedTimeBase", false);
868 pcExecQuery = new PerformanceCounter("DLinq", "ExecuteQueryElapsedTime", false);
869 bpcExecQuery = new PerformanceCounter("DLinq", "ExecuteQueryElapsedTimeBase", false);
870 pcSession = new PerformanceCounter("DLinq", "SessionExecuteQueryElapsedTime", false);
871 bpcSession = new PerformanceCounter("DLinq", "SessionExecuteQueryElapsedTimeBase", false);
872 timerAll = new PerfTimer();
873 timer = new PerfTimer();
877 query = Funcletizer.Funcletize(query);
879 if (this.EnableCacheLookup) {
880 IExecuteResult cached = this.GetCachedResult(query);
881 if (cached != null) {
886 #if PERFORMANCE_BUILD
887 if (collectQueryPerf) {
891 SqlNodeAnnotations annotations = new SqlNodeAnnotations();
892 QueryInfo[] qis = this.BuildQuery(query, annotations);
893 CheckSqlCompatibility(qis, annotations);
895 LambdaExpression lambda = query as LambdaExpression;
896 if (lambda != null) {
900 IObjectReaderFactory factory = null;
901 ICompiledSubQuery[] subQueries = null;
902 QueryInfo qi = qis[qis.Length - 1];
903 if (qi.ResultShape == ResultShape.Singleton) {
904 subQueries = this.CompileSubQueries(qi.Query);
905 factory = this.GetReaderFactory(qi.Query, qi.ResultType);
907 else if (qi.ResultShape == ResultShape.Sequence) {
908 subQueries = this.CompileSubQueries(qi.Query);
909 factory = this.GetReaderFactory(qi.Query, TypeSystem.GetElementType(qi.ResultType));
912 #if PERFORMANCE_BUILD
913 if (collectQueryPerf) {
915 pcBuildQuery.IncrementBy(timer.Duration);
916 bpcBuildQuery.Increment();
920 #if PERFORMANCE_BUILD
921 if (collectQueryPerf) {
926 IExecuteResult result = this.ExecuteAll(query, qis, factory, null, subQueries);
928 #if PERFORMANCE_BUILD
929 if (collectQueryPerf) {
931 pcSession.IncrementBy(timer.Duration);
932 bpcSession.Increment();
934 pcExecQuery.IncrementBy(timerAll.Duration);
935 bpcExecQuery.Increment();
941 private ICompiledSubQuery[] CompileSubQueries(SqlNode query) {
942 return new SubQueryCompiler(this).Compile(query);
945 class SubQueryCompiler : SqlVisitor {
946 SqlProvider provider;
947 List<ICompiledSubQuery> subQueries;
949 internal SubQueryCompiler(SqlProvider provider) {
950 this.provider = provider;
953 internal ICompiledSubQuery[] Compile(SqlNode node) {
954 this.subQueries = new List<ICompiledSubQuery>();
956 return this.subQueries.ToArray();
959 internal override SqlSelect VisitSelect(SqlSelect select) {
960 this.Visit(select.Selection);
964 internal override SqlExpression VisitSubSelect(SqlSubSelect ss) {
968 internal override SqlExpression VisitClientQuery(SqlClientQuery cq) {
969 Type clientElementType = cq.Query.NodeType == SqlNodeType.Multiset ? TypeSystem.GetElementType(cq.ClrType) : cq.ClrType;
970 ICompiledSubQuery c = this.provider.CompileSubQuery(cq.Query.Select, clientElementType, cq.Parameters.AsReadOnly());
971 cq.Ordinal = this.subQueries.Count;
972 this.subQueries.Add(c);
978 /// Look for compatibility annotations for the set of providers we
979 /// add annotations for.
981 private void CheckSqlCompatibility(QueryInfo[] queries, SqlNodeAnnotations annotations) {
982 if (this.Mode == ProviderMode.Sql2000 ||
983 this.Mode == ProviderMode.SqlCE) {
984 for (int i = 0, n = queries.Length; i < n; i++) {
985 SqlServerCompatibilityCheck.ThrowIfUnsupported(queries[i].Query, annotations, this.Mode);
990 private IExecuteResult ExecuteAll(Expression query, QueryInfo[] queryInfos, IObjectReaderFactory factory, object[] userArguments, ICompiledSubQuery[] subQueries) {
991 IExecuteResult result = null;
992 object lastResult = null;
993 for (int i = 0, n = queryInfos.Length; i < n; i++) {
995 result = this.Execute(query, queryInfos[i], null, null, userArguments, subQueries, lastResult);
998 result = this.Execute(query, queryInfos[i], factory, null, userArguments, subQueries, lastResult);
1000 if (queryInfos[i].ResultShape == ResultShape.Return) {
1001 lastResult = result.ReturnValue;
1007 [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
1008 private IExecuteResult GetCachedResult(Expression query) {
1009 object obj = this.services.GetCachedObject(query);
1011 switch (this.GetResultShape(query)) {
1012 case ResultShape.Singleton:
1013 return new ExecuteResult(null, null, null, obj);
1014 case ResultShape.Sequence:
1015 return new ExecuteResult(null, null, null,
1016 Activator.CreateInstance(
1017 typeof(SequenceOfOne<>).MakeGenericType(TypeSystem.GetElementType(this.GetResultType(query))),
1018 BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { obj }, null
1025 [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification="Unknown reason.")]
1026 [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
1027 private IExecuteResult Execute(Expression query, QueryInfo queryInfo, IObjectReaderFactory factory, object[] parentArgs, object[] userArgs, ICompiledSubQuery[] subQueries, object lastResult) {
1028 this.InitializeProviderMode();
1030 DbConnection con = this.conManager.UseConnection(this);
1032 DbCommand cmd = con.CreateCommand();
1033 cmd.CommandText = queryInfo.CommandText;
1034 cmd.Transaction = this.conManager.Transaction;
1035 cmd.CommandTimeout = this.commandTimeout;
1036 AssignParameters(cmd, queryInfo.Parameters, userArgs, lastResult);
1037 LogCommand(this.log, cmd);
1038 this.queryCount += 1;
1040 switch (queryInfo.ResultShape) {
1042 case ResultShape.Return: {
1043 return new ExecuteResult(cmd, queryInfo.Parameters, null, cmd.ExecuteNonQuery(), true);
1045 case ResultShape.Singleton: {
1046 DbDataReader reader = cmd.ExecuteReader();
1047 IObjectReader objReader = factory.Create(reader, true, this, parentArgs, userArgs, subQueries);
1048 this.conManager.UseConnection(objReader.Session);
1050 IEnumerable sequence = (IEnumerable)Activator.CreateInstance(
1051 typeof(OneTimeEnumerable<>).MakeGenericType(queryInfo.ResultType),
1052 BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null,
1053 new object[] { objReader }, null
1055 object value = null;
1056 MethodCallExpression mce = query as MethodCallExpression;
1057 MethodInfo sequenceMethod = null;
1058 if (mce != null && (
1059 mce.Method.DeclaringType == typeof(Queryable) ||
1060 mce.Method.DeclaringType == typeof(Enumerable))
1062 switch (mce.Method.Name) {
1064 case "FirstOrDefault":
1065 case "SingleOrDefault":
1066 sequenceMethod = TypeSystem.FindSequenceMethod(mce.Method.Name, sequence);
1070 sequenceMethod = TypeSystem.FindSequenceMethod("Single", sequence);
1075 sequenceMethod = TypeSystem.FindSequenceMethod("SingleOrDefault", sequence);
1078 // When dynamically invoking the sequence method, we want to
1079 // return the inner exception if the invocation fails
1080 if (sequenceMethod != null) {
1082 value = sequenceMethod.Invoke(null, new object[] { sequence });
1084 catch (TargetInvocationException tie) {
1085 if (tie.InnerException != null) {
1086 throw tie.InnerException;
1092 return new ExecuteResult(cmd, queryInfo.Parameters, objReader.Session, value);
1095 objReader.Dispose();
1098 case ResultShape.Sequence: {
1099 DbDataReader reader = cmd.ExecuteReader();
1100 IObjectReader objReader = factory.Create(reader, true, this, parentArgs, userArgs, subQueries);
1101 this.conManager.UseConnection(objReader.Session);
1102 IEnumerable sequence = (IEnumerable)Activator.CreateInstance(
1103 typeof(OneTimeEnumerable<>).MakeGenericType(TypeSystem.GetElementType(queryInfo.ResultType)),
1104 BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null,
1105 new object[] { objReader }, null
1107 if (typeof(IQueryable).IsAssignableFrom(queryInfo.ResultType)) {
1108 sequence = sequence.AsQueryable();
1110 ExecuteResult result = new ExecuteResult(cmd, queryInfo.Parameters, objReader.Session);
1111 MetaFunction function = this.GetFunction(query);
1112 if (function != null && !function.IsComposable) {
1113 sequence = (IEnumerable)Activator.CreateInstance(
1114 typeof(SingleResult<>).MakeGenericType(TypeSystem.GetElementType(queryInfo.ResultType)),
1115 BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null,
1116 new object[] { sequence, result, this.services.Context }, null
1119 result.ReturnValue = sequence;
1122 case ResultShape.MultipleResults: {
1123 DbDataReader reader = cmd.ExecuteReader();
1124 IObjectReaderSession session = this.readerCompiler.CreateSession(reader, this, parentArgs, userArgs, subQueries);
1125 this.conManager.UseConnection(session);
1126 MetaFunction function = this.GetFunction(query);
1127 ExecuteResult result = new ExecuteResult(cmd, queryInfo.Parameters, session);
1128 result.ReturnValue = new MultipleResults(this, function, session, result);
1134 this.conManager.ReleaseConnection(this);
1138 private MetaFunction GetFunction(Expression query) {
1139 LambdaExpression lambda = query as LambdaExpression;
1140 if (lambda != null) {
1141 query = lambda.Body;
1143 MethodCallExpression mc = query as MethodCallExpression;
1144 if (mc != null && typeof(DataContext).IsAssignableFrom(mc.Method.DeclaringType)) {
1145 return this.services.Model.GetFunction(mc.Method);
1150 private void LogCommand(TextWriter writer, DbCommand cmd) {
1151 if (writer != null) {
1152 writer.WriteLine(cmd.CommandText);
1153 foreach (DbParameter p in cmd.Parameters) {
1156 PropertyInfo piPrecision = p.GetType().GetProperty("Precision");
1157 if (piPrecision != null) {
1158 prec = (int)Convert.ChangeType(piPrecision.GetValue(p, null), typeof(int), CultureInfo.InvariantCulture);
1160 PropertyInfo piScale = p.GetType().GetProperty("Scale");
1161 if (piScale != null) {
1162 scale = (int)Convert.ChangeType(piScale.GetValue(p, null), typeof(int), CultureInfo.InvariantCulture);
1164 var sp = p as System.Data.SqlClient.SqlParameter;
1165 writer.WriteLine("-- {0}: {1} {2} (Size = {3}; Prec = {4}; Scale = {5}) [{6}]",
1168 sp == null ? p.DbType.ToString() : sp.SqlDbType.ToString(),
1169 p.Size.ToString(System.Globalization.CultureInfo.CurrentCulture),
1172 sp == null ? p.Value : sp.SqlValue);
1174 writer.WriteLine("-- Context: {0}({1}) Model: {2} Build: {3}", this.GetType().Name, this.Mode, this.services.Model.GetType().Name, ThisAssembly.InformationalVersion);
1179 private void AssignParameters(DbCommand cmd, ReadOnlyCollection<SqlParameterInfo> parms, object[] userArguments, object lastResult) {
1180 if (parms != null) {
1181 foreach (SqlParameterInfo pi in parms) {
1182 DbParameter p = cmd.CreateParameter();
1183 p.ParameterName = pi.Parameter.Name;
1184 p.Direction = pi.Parameter.Direction;
1185 if (pi.Parameter.Direction == ParameterDirection.Input ||
1186 pi.Parameter.Direction == ParameterDirection.InputOutput) {
1187 object value = pi.Value;
1189 case SqlParameterType.UserArgument:
1191 value = pi.Accessor.DynamicInvoke(new object[] { userArguments });
1192 } catch (System.Reflection.TargetInvocationException e) {
1193 throw e.InnerException;
1196 case SqlParameterType.PreviousResult:
1200 this.typeProvider.InitializeParameter(pi.Parameter.SqlType, p, value);
1203 this.typeProvider.InitializeParameter(pi.Parameter.SqlType, p, null);
1205 cmd.Parameters.Add(p);
1210 [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
1211 IEnumerable IProvider.Translate(Type elementType, DbDataReader reader) {
1212 this.CheckDispose();
1213 this.CheckInitialized();
1214 this.InitializeProviderMode();
1215 if (elementType == null) {
1216 throw Error.ArgumentNull("elementType");
1218 if (reader == null) {
1219 throw Error.ArgumentNull("reader");
1221 MetaType rowType = services.Model.GetMetaType(elementType);
1222 IObjectReaderFactory factory = this.GetDefaultFactory(rowType);
1223 IEnumerator e = factory.Create(reader, true, this, null, null, null);
1224 Type enumerableType = typeof(OneTimeEnumerable<>).MakeGenericType(elementType);
1225 return (IEnumerable)Activator.CreateInstance(enumerableType, BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { e }, null);
1228 IMultipleResults IProvider.Translate(DbDataReader reader) {
1229 this.CheckDispose();
1230 this.CheckInitialized();
1231 this.InitializeProviderMode();
1232 if (reader == null) {
1233 throw Error.ArgumentNull("reader");
1235 IObjectReaderSession session = this.readerCompiler.CreateSession(reader, this, null, null, null);
1236 return new MultipleResults(this, null, session, null);
1239 string IProvider.GetQueryText(Expression query) {
1240 this.CheckDispose();
1241 this.CheckInitialized();
1242 if (query == null) {
1243 throw Error.ArgumentNull("query");
1245 this.InitializeProviderMode();
1246 SqlNodeAnnotations annotations = new SqlNodeAnnotations();
1247 QueryInfo[] qis = this.BuildQuery(query, annotations);
1249 StringBuilder sb = new StringBuilder();
1250 for (int i = 0, n = qis.Length; i < n; i++) {
1251 QueryInfo qi = qis[i];
1253 StringWriter writer = new StringWriter(System.Globalization.CultureInfo.InvariantCulture);
1254 DbCommand cmd = this.conManager.Connection.CreateCommand();
1255 cmd.CommandText = qi.CommandText;
1256 AssignParameters(cmd, qi.Parameters, null, null);
1257 LogCommand(writer, cmd);
1258 sb.Append(writer.ToString());
1260 sb.Append(qi.CommandText);
1264 return sb.ToString();
1267 DbCommand IProvider.GetCommand(Expression query) {
1268 this.CheckDispose();
1269 this.CheckInitialized();
1270 if (query == null) {
1271 throw Error.ArgumentNull("query");
1273 this.InitializeProviderMode();
1274 SqlNodeAnnotations annotations = new SqlNodeAnnotations();
1275 QueryInfo[] qis = this.BuildQuery(query, annotations);
1276 QueryInfo qi = qis[qis.Length - 1];
1277 DbCommand cmd = this.conManager.Connection.CreateCommand();
1278 cmd.CommandText = qi.CommandText;
1279 cmd.Transaction = this.conManager.Transaction;
1280 cmd.CommandTimeout = this.commandTimeout;
1281 AssignParameters(cmd, qi.Parameters, null, null);
1285 internal class QueryInfo {
1288 ReadOnlyCollection<SqlParameterInfo> parameters;
1289 ResultShape resultShape;
1292 internal QueryInfo(SqlNode query, string commandText, ReadOnlyCollection<SqlParameterInfo> parameters, ResultShape resultShape, Type resultType) {
1294 this.commandText = commandText;
1295 this.parameters = parameters;
1296 this.resultShape = resultShape;
1297 this.resultType = resultType;
1299 internal SqlNode Query {
1300 get { return this.query; }
1302 internal string CommandText {
1303 get { return this.commandText; }
1305 internal ReadOnlyCollection<SqlParameterInfo> Parameters {
1306 get { return this.parameters; }
1308 internal ResultShape ResultShape {
1309 get { return this.resultShape; }
1311 internal Type ResultType {
1312 get { return this.resultType; }
1316 internal enum ResultShape {
1323 private ResultShape GetResultShape(Expression query) {
1324 LambdaExpression lambda = query as LambdaExpression;
1325 if (lambda != null) {
1326 query = lambda.Body;
1329 if (query.Type == typeof(void)) {
1330 return ResultShape.Return;
1332 else if (query.Type == typeof(IMultipleResults)) {
1333 return ResultShape.MultipleResults;
1336 bool isSequence = typeof(IEnumerable).IsAssignableFrom(query.Type);
1337 ProviderType pt = this.typeProvider.From(query.Type);
1338 bool isScalar = !pt.IsRuntimeOnlyType && !pt.IsApplicationType;
1339 bool isSingleton = isScalar || !isSequence;
1341 MethodCallExpression mce = query as MethodCallExpression;
1344 if (mce.Method.DeclaringType == typeof(Queryable) ||
1345 mce.Method.DeclaringType == typeof(Enumerable)) {
1346 switch (mce.Method.Name) {
1347 // methods known to produce singletons
1349 case "FirstOrDefault":
1351 case "SingleOrDefault":
1356 else if (mce.Method.DeclaringType == typeof(DataContext)) {
1357 if (mce.Method.Name == "ExecuteCommand") {
1358 return ResultShape.Return;
1361 else if (mce.Method.DeclaringType.IsSubclassOf(typeof(DataContext))) {
1362 MetaFunction f = this.GetFunction(query);
1364 if (!f.IsComposable) {
1365 isSingleton = false;
1367 else if (isScalar) {
1372 else if (mce.Method.DeclaringType == typeof(DataManipulation) && mce.Method.ReturnType == typeof(int)) {
1373 return ResultShape.Return;
1378 return ResultShape.Singleton;
1380 else if (isScalar) {
1381 return ResultShape.Return;
1384 return ResultShape.Sequence;
1388 [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")]
1389 private Type GetResultType(Expression query) {
1390 LambdaExpression lambda = query as LambdaExpression;
1391 if (lambda != null) {
1392 query = lambda.Body;
1397 internal QueryInfo[] BuildQuery(Expression query, SqlNodeAnnotations annotations) {
1398 this.CheckDispose();
1400 // apply maximal funcletization
1401 query = Funcletizer.Funcletize(query);
1403 // convert query nodes into sql nodes
1404 QueryConverter converter = new QueryConverter(this.services, this.typeProvider, this.translator, this.sqlFactory);
1405 switch (this.Mode) {
1406 case ProviderMode.Sql2000:
1407 converter.ConverterStrategy =
1408 ConverterStrategy.CanUseScopeIdentity |
1409 ConverterStrategy.CanUseJoinOn |
1410 ConverterStrategy.CanUseRowStatus;
1412 case ProviderMode.Sql2005:
1413 case ProviderMode.Sql2008:
1414 converter.ConverterStrategy =
1415 ConverterStrategy.CanUseScopeIdentity |
1416 ConverterStrategy.SkipWithRowNumber |
1417 ConverterStrategy.CanUseRowStatus |
1418 ConverterStrategy.CanUseJoinOn |
1419 ConverterStrategy.CanUseOuterApply |
1420 ConverterStrategy.CanOutputFromInsert;
1422 case ProviderMode.SqlCE:
1423 converter.ConverterStrategy = ConverterStrategy.CanUseOuterApply;
1424 // Can't set ConverterStrategy.CanUseJoinOn because scalar subqueries in the ON clause
1425 // can't be converted into anything.
1428 SqlNode node = converter.ConvertOuter(query);
1430 return this.BuildQuery(this.GetResultShape(query), this.GetResultType(query), node, null, annotations);
1433 [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", 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.")]
1434 private QueryInfo[] BuildQuery(ResultShape resultShape, Type resultType, SqlNode node, ReadOnlyCollection<Me.SqlParameter> parentParameters, SqlNodeAnnotations annotations) {
1435 System.Diagnostics.Debug.Assert(resultType != null);
1436 System.Diagnostics.Debug.Assert(node != null);
1438 SqlSupersetValidator validator = new SqlSupersetValidator();
1440 // These are the rules that apply to every SQL tree.
1441 if (this.checkQueries) {
1442 validator.AddValidator(new ColumnTypeValidator()); /* Column CLR Type must agree with its Expressions CLR Type */
1443 validator.AddValidator(new LiteralValidator()); /* Constrain literal Types */
1446 validator.Validate(node);
1448 SqlColumnizer columnizer = new SqlColumnizer();
1450 // resolve member references
1451 bool canUseOuterApply = (this.Mode == ProviderMode.Sql2005 || this.Mode == ProviderMode.Sql2008 || this.Mode == ProviderMode.SqlCE);
1452 SqlBinder binder = new SqlBinder(this.translator, this.sqlFactory, this.services.Model, this.services.Context.LoadOptions, columnizer, canUseOuterApply);
1453 binder.OptimizeLinkExpansions = (optimizationFlags & OptimizationFlags.OptimizeLinkExpansions) != 0;
1454 binder.SimplifyCaseStatements = (optimizationFlags & OptimizationFlags.SimplifyCaseStatements) != 0;
1455 binder.PreBinder = delegate(SqlNode n) {
1456 // convert methods into known reversable operators
1457 return PreBindDotNetConverter.Convert(n, this.sqlFactory, this.services.Model);
1459 node = binder.Bind(node);
1460 if (this.checkQueries) {
1461 validator.AddValidator(new ExpectNoAliasRefs());
1462 validator.AddValidator(new ExpectNoSharedExpressions());
1464 validator.Validate(node);
1466 node = PostBindDotNetConverter.Convert(node, this.sqlFactory, this.Mode);
1468 // identify true flow of sql data types
1469 SqlRetyper retyper = new SqlRetyper(this.typeProvider, this.services.Model);
1470 node = retyper.Retype(node);
1471 validator.Validate(node);
1473 // change CONVERT to special conversions like UNICODE,CHAR,...
1474 SqlTypeConverter converter = new SqlTypeConverter(this.sqlFactory);
1475 node = converter.Visit(node);
1476 validator.Validate(node);
1478 // transform type-sensitive methods such as LEN (to DATALENGTH), ...
1479 SqlMethodTransformer methodTransformer = new SqlMethodTransformer(this.sqlFactory);
1480 node = methodTransformer.Visit(node);
1481 validator.Validate(node);
1483 // convert multisets into separate queries
1484 SqlMultiplexer.Options options = (this.Mode == ProviderMode.Sql2008 ||
1485 this.Mode == ProviderMode.Sql2005 ||
1486 this.Mode == ProviderMode.SqlCE)
1487 ? SqlMultiplexer.Options.EnableBigJoin : SqlMultiplexer.Options.None;
1488 SqlMultiplexer mux = new SqlMultiplexer(options, parentParameters, this.sqlFactory);
1489 node = mux.Multiplex(node);
1490 validator.Validate(node);
1492 // convert object construction expressions into flat row projections
1493 SqlFlattener flattener = new SqlFlattener(this.sqlFactory, columnizer);
1494 node = flattener.Flatten(node);
1495 validator.Validate(node);
1497 if (this.mode == ProviderMode.SqlCE) {
1498 SqlRewriteScalarSubqueries rss = new SqlRewriteScalarSubqueries(this.sqlFactory);
1499 node = rss.Rewrite(node);
1502 // Simplify case statements where all alternatives map to the same thing.
1503 // Doing this before deflator because the simplified results may lead to
1504 // more deflation opportunities.
1505 // Doing this before booleanizer because it may convert CASE statements (non-predicates) into
1506 // predicate expressions.
1507 // Doing this before reorderer because it may reduce some orders to constant nodes which should not
1508 // be passed onto ROW_NUMBER.
1509 node = SqlCaseSimplifier.Simplify(node, this.sqlFactory);
1511 // Rewrite order-by clauses so that they only occur at the top-most select
1512 // or in selects with TOP
1513 SqlReorderer reorderer = new SqlReorderer(this.typeProvider, this.sqlFactory);
1514 node = reorderer.Reorder(node);
1515 validator.Validate(node);
1517 // Inject code to turn predicates into bits, and bits into predicates where necessary
1518 node = SqlBooleanizer.Rationalize(node, this.typeProvider, this.services.Model);
1519 if (this.checkQueries) {
1520 validator.AddValidator(new ExpectRationalizedBooleans()); /* From now on all boolean expressions should remain rationalized. */
1522 validator.Validate(node);
1524 if (this.checkQueries) {
1525 validator.AddValidator(new ExpectNoFloatingColumns());
1528 // turning predicates into bits/ints can change Sql types, propagate changes
1529 node = retyper.Retype(node);
1530 validator.Validate(node);
1532 // assign aliases to columns
1533 // we need to do this now so that the sql2k lifters will work
1534 SqlAliaser aliaser = new SqlAliaser();
1535 node = aliaser.AssociateColumnsWithAliases(node);
1536 validator.Validate(node);
1539 node = SqlLiftWhereClauses.Lift(node, this.typeProvider, this.services.Model);
1540 node = SqlLiftIndependentRowExpressions.Lift(node);
1541 node = SqlOuterApplyReducer.Reduce(node, this.sqlFactory, annotations);
1542 node = SqlTopReducer.Reduce(node, annotations, this.sqlFactory);
1544 // resolve references to columns in other scopes by adding them
1545 // to the intermediate selects
1546 SqlResolver resolver = new SqlResolver();
1547 node = resolver.Resolve(node);
1548 validator.Validate(node);
1550 // re-assign aliases after resolving (new columns may have been added)
1551 node = aliaser.AssociateColumnsWithAliases(node);
1552 validator.Validate(node);
1554 // fixup union projections
1555 node = SqlUnionizer.Unionize(node);
1557 // remove order-by of literals
1558 node = SqlRemoveConstantOrderBy.Remove(node);
1560 // throw out unused columns and redundant sub-queries...
1561 SqlDeflator deflator = new SqlDeflator();
1562 node = deflator.Deflate(node);
1563 validator.Validate(node);
1565 // Positioning after deflator because it may remove unnecessary columns
1566 // from SELECT projection lists and allow more CROSS APPLYs to be reduced
1568 node = SqlCrossApplyToCrossJoin.Reduce(node, annotations);
1570 // fixup names for aliases, columns, locals, etc..
1571 SqlNamer namer = new SqlNamer();
1572 node = namer.AssignNames(node);
1573 validator.Validate(node);
1575 // Convert [N]Text,Image to [N]VarChar(MAX),VarBinary(MAX) where necessary.
1576 // These new types do not exist on SQL2k, so add annotations.
1577 LongTypeConverter longTypeConverter = new LongTypeConverter(this.sqlFactory);
1578 node = longTypeConverter.AddConversions(node, annotations);
1581 validator.AddValidator(new ExpectNoMethodCalls());
1582 validator.AddValidator(new ValidateNoInvalidComparison());
1583 validator.Validate(node);
1585 SqlParameterizer parameterizer = new SqlParameterizer(this.typeProvider, annotations);
1586 SqlFormatter formatter = new SqlFormatter();
1587 if (this.mode == ProviderMode.SqlCE ||
1588 this.mode == ProviderMode.Sql2005 ||
1589 this.mode == ProviderMode.Sql2008) {
1590 formatter.ParenthesizeTop = true;
1593 SqlBlock block = node as SqlBlock;
1594 if (block != null && this.mode == ProviderMode.SqlCE) {
1595 // SQLCE cannot batch multiple statements.
1596 ReadOnlyCollection<ReadOnlyCollection<SqlParameterInfo>> parameters = parameterizer.ParameterizeBlock(block);
1597 string[] commands = formatter.FormatBlock(block, false);
1598 QueryInfo[] queries = new QueryInfo[commands.Length];
1599 for (int i = 0, n = commands.Length; i < n; i++) {
1600 queries[i] = new QueryInfo(
1601 block.Statements[i],
1604 (i < n - 1) ? ResultShape.Return : resultShape,
1605 (i < n - 1) ? typeof(int) : resultType
1611 // build only one result
1612 ReadOnlyCollection<SqlParameterInfo> parameters = parameterizer.Parameterize(node);
1613 string commandText = formatter.Format(node);
1614 return new QueryInfo[] {
1615 new QueryInfo(node, commandText, parameters, resultShape, resultType)
1620 private SqlSelect GetFinalSelect(SqlNode node) {
1621 switch (node.NodeType) {
1622 case SqlNodeType.Select:
1623 return (SqlSelect)node;
1624 case SqlNodeType.Block: {
1625 SqlBlock b = (SqlBlock)node;
1626 return GetFinalSelect(b.Statements[b.Statements.Count - 1]);
1632 private IObjectReaderFactory GetReaderFactory(SqlNode node, Type elemType) {
1633 SqlSelect sel = node as SqlSelect;
1634 SqlExpression projection = null;
1635 if (sel == null && node.NodeType == SqlNodeType.Block) {
1636 sel = this.GetFinalSelect(node);
1639 projection = sel.Selection;
1642 SqlUserQuery suq = node as SqlUserQuery;
1643 if (suq != null && suq.Projection != null) {
1644 projection = suq.Projection;
1647 IObjectReaderFactory factory;
1648 if (projection != null) {
1649 factory = this.readerCompiler.Compile(projection, elemType);
1652 return this.GetDefaultFactory(services.Model.GetMetaType(elemType));
1657 private IObjectReaderFactory GetDefaultFactory(MetaType rowType) {
1658 if (rowType == null) {
1659 throw Error.ArgumentNull("rowType");
1661 SqlNodeAnnotations annotations = new SqlNodeAnnotations();
1662 Expression tmp = Expression.Constant(null);
1663 SqlUserQuery suq = new SqlUserQuery(string.Empty, null, null, tmp);
1664 if (TypeSystem.IsSimpleType(rowType.Type)) {
1665 // if the element type is a simple type (int, bool, etc.) we create
1666 // a single column binding
1667 SqlUserColumn col = new SqlUserColumn(rowType.Type, typeProvider.From(rowType.Type), suq, "", false, suq.SourceExpression);
1668 suq.Columns.Add(col);
1669 suq.Projection = col;
1672 // ... otherwise we generate a default projection
1673 SqlUserRow rowExp = new SqlUserRow(rowType.InheritanceRoot, this.typeProvider.GetApplicationType((int)ConverterSpecialTypes.Row), suq, tmp);
1674 suq.Projection = this.translator.BuildProjection(rowExp, rowType, true, null, tmp);
1676 Type resultType = TypeSystem.GetSequenceType(rowType.Type);
1677 QueryInfo[] qis = this.BuildQuery(ResultShape.Sequence, resultType, suq, null, annotations);
1678 return this.GetReaderFactory(qis[qis.Length - 1].Query, rowType.Type);
1681 class CompiledQuery : ICompiledQuery {
1682 DataLoadOptions originalShape;
1684 QueryInfo[] queryInfos;
1685 IObjectReaderFactory factory;
1686 ICompiledSubQuery[] subQueries;
1688 internal CompiledQuery(SqlProvider provider, Expression query, QueryInfo[] queryInfos, IObjectReaderFactory factory, ICompiledSubQuery[] subQueries) {
1689 this.originalShape = provider.services.Context.LoadOptions;
1691 this.queryInfos = queryInfos;
1692 this.factory = factory;
1693 this.subQueries = subQueries;
1696 public IExecuteResult Execute(IProvider provider, object[] arguments) {
1697 if (provider == null) {
1698 throw Error.ArgumentNull("provider");
1701 SqlProvider sqlProvider = provider as SqlProvider;
1702 if (sqlProvider == null) {
1703 throw Error.ArgumentTypeMismatch("provider");
1706 // verify shape is compatibile with original.
1707 if (!AreEquivalentShapes(this.originalShape, sqlProvider.services.Context.LoadOptions)) {
1708 throw Error.CompiledQueryAgainstMultipleShapesNotSupported();
1711 // execute query (only last query produces results)
1712 return sqlProvider.ExecuteAll(this.query, this.queryInfos, this.factory, arguments, subQueries);
1715 private static bool AreEquivalentShapes(DataLoadOptions shape1, DataLoadOptions shape2) {
1716 if (shape1 == shape2) {
1719 else if (shape1 == null) {
1720 return shape2.IsEmpty;
1722 else if (shape2 == null) {
1723 return shape1.IsEmpty;
1725 else if (shape1.IsEmpty && shape2.IsEmpty) {
1732 class CompiledSubQuery : ICompiledSubQuery {
1733 QueryInfo queryInfo;
1734 IObjectReaderFactory factory;
1735 ReadOnlyCollection<Me.SqlParameter> parameters;
1736 ICompiledSubQuery[] subQueries;
1738 internal CompiledSubQuery(QueryInfo queryInfo, IObjectReaderFactory factory, ReadOnlyCollection<Me.SqlParameter> parameters, ICompiledSubQuery[] subQueries) {
1739 this.queryInfo = queryInfo;
1740 this.factory = factory;
1741 this.parameters = parameters;
1742 this.subQueries = subQueries;
1745 public IExecuteResult Execute(IProvider provider, object[] parentArgs, object[] userArgs) {
1746 if (parentArgs == null && !(this.parameters == null || this.parameters.Count == 0)) {
1747 throw Error.ArgumentNull("arguments");
1750 SqlProvider sqlProvider = provider as SqlProvider;
1751 if (sqlProvider == null) {
1752 throw Error.ArgumentTypeMismatch("provider");
1755 // construct new copy of query info
1756 List<SqlParameterInfo> spis = new List<SqlParameterInfo>(this.queryInfo.Parameters);
1758 // add call arguments
1759 for (int i = 0, n = this.parameters.Count; i < n; i++) {
1760 spis.Add(new SqlParameterInfo(this.parameters[i], parentArgs[i]));
1763 QueryInfo qi = new QueryInfo(
1764 this.queryInfo.Query,
1765 this.queryInfo.CommandText,
1767 this.queryInfo.ResultShape,
1768 this.queryInfo.ResultType
1772 return sqlProvider.Execute(null, qi, this.factory, parentArgs, userArgs, subQueries, null);
1776 class ExecuteResult : IExecuteResult, IDisposable {
1778 ReadOnlyCollection<SqlParameterInfo> parameters;
1779 IObjectReaderSession session;
1780 int iReturnParameter = -1;
1782 [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Microsoft: used in an assert in ReturnValue.set")]
1783 bool useReturnValue;
1786 internal ExecuteResult(DbCommand command, ReadOnlyCollection<SqlParameterInfo> parameters, IObjectReaderSession session, object value, bool useReturnValue)
1787 : this(command, parameters, session) {
1789 this.useReturnValue = useReturnValue;
1790 if (this.command != null && this.parameters != null && useReturnValue) {
1791 iReturnParameter = GetParameterIndex("@RETURN_VALUE");
1795 internal ExecuteResult(DbCommand command, ReadOnlyCollection<SqlParameterInfo> parameters, IObjectReaderSession session) {
1796 this.command = command;
1797 this.parameters = parameters;
1798 this.session = session;
1801 internal ExecuteResult(DbCommand command, ReadOnlyCollection<SqlParameterInfo> parameters, IObjectReaderSession session, object value)
1802 : this(command, parameters, session, value, false) {
1805 [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "value", Justification="FxCop Error -- False positive during code analysis")]
1806 public object ReturnValue {
1808 if (this.iReturnParameter >= 0) {
1809 return this.GetParameterValue(this.iReturnParameter);
1814 Debug.Assert(!useReturnValue);
1819 private int GetParameterIndex(string paramName) {
1821 for (int i = 0, n = this.parameters.Count; i < n; i++) {
1822 if (string.Compare(parameters[i].Parameter.Name, paramName, StringComparison.OrdinalIgnoreCase) == 0) {
1830 internal object GetParameterValue(string paramName) {
1831 int idx = GetParameterIndex(paramName);
1833 return GetParameterValue(idx);
1838 public object GetParameterValue(int parameterIndex) {
1839 if (this.parameters == null || parameterIndex < 0 || parameterIndex > this.parameters.Count) {
1840 throw Error.ArgumentOutOfRange("parameterIndex");
1843 // SQL server requires all results to be read before output parameters are visible
1844 if (this.session != null && !this.session.IsBuffered) {
1845 this.session.Buffer();
1848 SqlParameterInfo pi = this.parameters[parameterIndex];
1849 object parameterValue = this.command.Parameters[parameterIndex].Value;
1850 if (parameterValue == DBNull.Value) parameterValue = null;
1851 if (parameterValue != null && parameterValue.GetType() != pi.Parameter.ClrType) {
1852 return DBConvert.ChangeType(parameterValue, pi.Parameter.ClrType);
1855 return parameterValue;
1858 public void Dispose() {
1859 if (!this.isDisposed) {
1860 // Technically, calling GC.SuppressFinalize is not required because the class does not
1861 // have a finalizer, but it does no harm, protects against the case where a finalizer is added
1862 // in the future, and prevents an FxCop warning.
1863 GC.SuppressFinalize(this);
1864 this.isDisposed = true;
1865 if (this.session!=null) {
1866 this.session.Dispose();
1872 class SequenceOfOne<T> : IEnumerable<T>, IEnumerable {
1874 internal SequenceOfOne(T value) {
1875 this.sequence = new T[] { value };
1877 public IEnumerator<T> GetEnumerator() {
1878 return ((IEnumerable<T>)this.sequence).GetEnumerator();
1881 IEnumerator IEnumerable.GetEnumerator() {
1882 return this.GetEnumerator();
1886 class OneTimeEnumerable<T> : IEnumerable<T>, IEnumerable {
1887 IEnumerator<T> enumerator;
1889 internal OneTimeEnumerable(IEnumerator<T> enumerator) {
1890 System.Diagnostics.Debug.Assert(enumerator != null);
1891 this.enumerator = enumerator;
1894 public IEnumerator<T> GetEnumerator() {
1895 if (this.enumerator == null) {
1896 throw Error.CannotEnumerateResultsMoreThanOnce();
1898 IEnumerator<T> e = this.enumerator;
1899 this.enumerator = null;
1903 IEnumerator IEnumerable.GetEnumerator() {
1904 return this.GetEnumerator();
1909 /// Result type for single rowset returning stored procedures.
1911 class SingleResult<T> : ISingleResult<T>, IDisposable, IListSource {
1912 private IEnumerable<T> enumerable;
1913 private ExecuteResult executeResult;
1914 private DataContext context;
1915 private IBindingList cachedList;
1917 internal SingleResult(IEnumerable<T> enumerable, ExecuteResult executeResult, DataContext context) {
1918 System.Diagnostics.Debug.Assert(enumerable != null);
1919 System.Diagnostics.Debug.Assert(executeResult != null);
1920 this.enumerable = enumerable;
1921 this.executeResult = executeResult;
1922 this.context = context;
1925 public IEnumerator<T> GetEnumerator() {
1926 return enumerable.GetEnumerator();
1929 IEnumerator IEnumerable.GetEnumerator() {
1930 return this.GetEnumerator();
1933 public object ReturnValue {
1935 return executeResult.GetParameterValue("@RETURN_VALUE");
1939 public void Dispose() {
1940 // Technically, calling GC.SuppressFinalize is not required because the class does not
1941 // have a finalizer, but it does no harm, protects against the case where a finalizer is added
1942 // in the future, and prevents an FxCop warning.
1943 GC.SuppressFinalize(this);
1944 this.executeResult.Dispose();
1947 IList IListSource.GetList() {
1948 if (this.cachedList == null) {
1949 this.cachedList = BindingList.Create<T>(this.context, this);
1951 return this.cachedList;
1954 bool IListSource.ContainsListCollection {
1955 get { return false; }
1959 class MultipleResults : IMultipleResults, IDisposable {
1960 SqlProvider provider;
1961 MetaFunction function;
1962 IObjectReaderSession session;
1964 private ExecuteResult executeResult;
1966 internal MultipleResults(SqlProvider provider, MetaFunction function, IObjectReaderSession session, ExecuteResult executeResult) {
1967 this.provider = provider;
1968 this.function = function;
1969 this.session = session;
1970 this.executeResult = executeResult;
1973 public IEnumerable<T> GetResult<T>() {
1974 MetaType metaType = null;
1975 // Check the inheritance hierarchy of each mapped result row type
1976 // for the function.
1977 if (this.function != null) {
1978 foreach (MetaType mt in function.ResultRowTypes) {
1979 metaType = mt.InheritanceTypes.SingleOrDefault(it => it.Type == typeof(T));
1980 if (metaType != null) {
1985 if (metaType == null) {
1986 metaType = this.provider.services.Model.GetMetaType(typeof(T));
1988 IObjectReaderFactory factory = this.provider.GetDefaultFactory(metaType);
1989 IObjectReader objReader = factory.GetNextResult(this.session, false);
1990 if (objReader == null) {
1994 return new SingleResult<T>(new OneTimeEnumerable<T>((IEnumerator<T>)objReader), this.executeResult, this.provider.services.Context);
1997 public void Dispose() {
1998 if (!this.isDisposed) {
1999 // Technically, calling GC.SuppressFinalize is not required because the class does not
2000 // have a finalizer, but it does no harm, protects against the case where a finalizer is added
2001 // in the future, and prevents an FxCop warning.
2002 GC.SuppressFinalize(this);
2003 this.isDisposed = true;
2004 if (this.executeResult != null) {
2005 this.executeResult.Dispose();
2008 this.session.Dispose();
2013 public object ReturnValue {
2015 if (this.executeResult != null) {
2016 return executeResult.GetParameterValue("@RETURN_VALUE");