536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
2025 lines
88 KiB
C#
2025 lines
88 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using System.Data;
|
|
using System.Data.Common;
|
|
using System.Data.Linq;
|
|
using System.Data.Linq.Mapping;
|
|
using System.Data.Linq.Provider;
|
|
using System.Data.SqlClient;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Linq.Expressions;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Globalization;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using Me = System.Data.Linq.SqlClient;
|
|
using System.Runtime.Versioning;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace System.Data.Linq.SqlClient {
|
|
public sealed class Sql2000Provider : SqlProvider {
|
|
public Sql2000Provider()
|
|
: base(ProviderMode.Sql2000) {
|
|
}
|
|
}
|
|
|
|
public sealed class Sql2005Provider : SqlProvider {
|
|
public Sql2005Provider()
|
|
: base(ProviderMode.Sql2005) {
|
|
}
|
|
}
|
|
|
|
public sealed class Sql2008Provider : SqlProvider {
|
|
public Sql2008Provider()
|
|
: base(ProviderMode.Sql2008) {
|
|
}
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification="Unknown reason.")]
|
|
public class SqlProvider : IReaderProvider, IConnectionUser {
|
|
private IDataServices services;
|
|
private SqlConnectionManager conManager;
|
|
private TypeSystemProvider typeProvider;
|
|
private SqlFactory sqlFactory;
|
|
private Translator translator;
|
|
private IObjectReaderCompiler readerCompiler;
|
|
private bool disposed;
|
|
private int commandTimeout;
|
|
|
|
private TextWriter log;
|
|
string dbName = string.Empty;
|
|
|
|
// stats and flags
|
|
private int queryCount;
|
|
private bool checkQueries;
|
|
private OptimizationFlags optimizationFlags = OptimizationFlags.All;
|
|
private bool enableCacheLookup = true;
|
|
private ProviderMode mode = ProviderMode.NotYetDecided;
|
|
private bool deleted = false;
|
|
|
|
#if PERFORMANCE_BUILD
|
|
private bool collectPerfInfo;
|
|
private bool collectPerfInfoInitialized = false;
|
|
private bool collectQueryPerf;
|
|
|
|
internal bool CollectPerfInfo {
|
|
get {
|
|
if (!collectPerfInfoInitialized) {
|
|
string s = System.Environment.GetEnvironmentVariable("CollectDLinqPerfInfo");
|
|
collectPerfInfo = (s != null) && (s == "On");
|
|
collectPerfInfoInitialized = true;
|
|
}
|
|
return this.collectPerfInfo;
|
|
}
|
|
}
|
|
|
|
internal bool CollectQueryPerf {
|
|
get { return this.collectQueryPerf; }
|
|
}
|
|
#endif
|
|
|
|
internal enum ProviderMode {
|
|
NotYetDecided,
|
|
Sql2000,
|
|
Sql2005,
|
|
Sql2008,
|
|
SqlCE
|
|
}
|
|
|
|
const string SqlCeProviderInvariantName = "System.Data.SqlServerCe.3.5";
|
|
const string SqlCeDataReaderTypeName = "System.Data.SqlServerCe.SqlCeDataReader";
|
|
const string SqlCeConnectionTypeName = "System.Data.SqlServerCe.SqlCeConnection";
|
|
const string SqlCeTransactionTypeName = "System.Data.SqlServerCe.SqlCeTransaction";
|
|
|
|
internal ProviderMode Mode {
|
|
get {
|
|
this.CheckDispose();
|
|
this.CheckInitialized();
|
|
this.InitializeProviderMode();
|
|
return this.mode;
|
|
}
|
|
}
|
|
|
|
private void InitializeProviderMode() {
|
|
if (this.mode == ProviderMode.NotYetDecided) {
|
|
if (this.IsSqlCe) {
|
|
this.mode = ProviderMode.SqlCE;
|
|
} else if (this.IsServer2KOrEarlier) {
|
|
this.mode = ProviderMode.Sql2000;
|
|
}
|
|
else if (this.IsServer2005) {
|
|
this.mode = ProviderMode.Sql2005;
|
|
} else {
|
|
this.mode = ProviderMode.Sql2008;
|
|
}
|
|
}
|
|
if (this.typeProvider == null) {
|
|
switch (this.mode) {
|
|
case ProviderMode.Sql2000:
|
|
this.typeProvider = SqlTypeSystem.Create2000Provider();
|
|
break;
|
|
case ProviderMode.Sql2005:
|
|
this.typeProvider = SqlTypeSystem.Create2005Provider();
|
|
break;
|
|
case ProviderMode.Sql2008:
|
|
this.typeProvider = SqlTypeSystem.Create2008Provider();
|
|
break;
|
|
case ProviderMode.SqlCE:
|
|
this.typeProvider = SqlTypeSystem.CreateCEProvider();
|
|
break;
|
|
default:
|
|
System.Diagnostics.Debug.Assert(false);
|
|
break;
|
|
}
|
|
}
|
|
if (this.sqlFactory == null) {
|
|
this.sqlFactory = new SqlFactory(this.typeProvider, this.services.Model);
|
|
this.translator = new Translator(this.services, this.sqlFactory, this.typeProvider);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return true if the current connection is SQLCE.
|
|
/// </summary>
|
|
private bool IsSqlCe {
|
|
get {
|
|
DbConnection con = conManager.UseConnection(this);
|
|
try {
|
|
if (String.CompareOrdinal(con.GetType().FullName, SqlCeConnectionTypeName) == 0) {
|
|
return true;
|
|
}
|
|
} finally {
|
|
conManager.ReleaseConnection(this);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return true if this is a 2K (or earlier) server. This may be a round trip to the server.
|
|
/// </summary>
|
|
private bool IsServer2KOrEarlier {
|
|
get {
|
|
DbConnection con = conManager.UseConnection(this);
|
|
try {
|
|
string serverVersion = con.ServerVersion;
|
|
if (serverVersion.StartsWith("06.00.", StringComparison.Ordinal)) {
|
|
return true;
|
|
}
|
|
else if (serverVersion.StartsWith("06.50.", StringComparison.Ordinal)) {
|
|
return true;
|
|
}
|
|
else if (serverVersion.StartsWith("07.00.", StringComparison.Ordinal)) {
|
|
return true;
|
|
}
|
|
else if (serverVersion.StartsWith("08.00.", StringComparison.Ordinal)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
finally {
|
|
conManager.ReleaseConnection(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return true if this is a SQL 2005 server. This may be a round trip to the server.
|
|
/// </summary>
|
|
private bool IsServer2005 {
|
|
get {
|
|
DbConnection con = conManager.UseConnection(this);
|
|
try {
|
|
string serverVersion = con.ServerVersion;
|
|
if (serverVersion.StartsWith("09.00.", StringComparison.Ordinal)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
finally {
|
|
conManager.ReleaseConnection(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
DbConnection IProvider.Connection {
|
|
get {
|
|
this.CheckDispose();
|
|
this.CheckInitialized();
|
|
return this.conManager.Connection;
|
|
}
|
|
}
|
|
|
|
TextWriter IProvider.Log {
|
|
get {
|
|
this.CheckDispose();
|
|
this.CheckInitialized();
|
|
return this.log;
|
|
}
|
|
set {
|
|
this.CheckDispose();
|
|
this.CheckInitialized();
|
|
this.log = value;
|
|
}
|
|
}
|
|
|
|
DbTransaction IProvider.Transaction {
|
|
get {
|
|
this.CheckDispose();
|
|
this.CheckInitialized();
|
|
return this.conManager.Transaction;
|
|
}
|
|
set {
|
|
this.CheckDispose();
|
|
this.CheckInitialized();
|
|
this.conManager.Transaction = value;
|
|
}
|
|
}
|
|
|
|
int IProvider.CommandTimeout {
|
|
get {
|
|
this.CheckDispose();
|
|
return this.commandTimeout;
|
|
}
|
|
set {
|
|
this.CheckDispose();
|
|
this.commandTimeout = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Expose a test hook which controls which SQL optimizations are executed.
|
|
/// </summary>
|
|
internal OptimizationFlags OptimizationFlags {
|
|
get {
|
|
CheckDispose();
|
|
return this.optimizationFlags;
|
|
}
|
|
set {
|
|
CheckDispose();
|
|
this.optimizationFlags = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate queries as they are generated.
|
|
/// </summary>
|
|
internal bool CheckQueries {
|
|
get {
|
|
CheckDispose();
|
|
return checkQueries;
|
|
}
|
|
set {
|
|
CheckDispose();
|
|
checkQueries = value;
|
|
}
|
|
}
|
|
|
|
internal bool EnableCacheLookup {
|
|
get {
|
|
CheckDispose();
|
|
return this.enableCacheLookup;
|
|
}
|
|
set {
|
|
CheckDispose();
|
|
this.enableCacheLookup = value;
|
|
}
|
|
}
|
|
|
|
internal int QueryCount {
|
|
get {
|
|
CheckDispose();
|
|
return this.queryCount;
|
|
}
|
|
}
|
|
|
|
internal int MaxUsers {
|
|
get {
|
|
CheckDispose();
|
|
return this.conManager.MaxUsers;
|
|
}
|
|
}
|
|
|
|
IDataServices IReaderProvider.Services {
|
|
get { return this.services; }
|
|
}
|
|
|
|
IConnectionManager IReaderProvider.ConnectionManager {
|
|
get { return this.conManager; }
|
|
}
|
|
|
|
public SqlProvider() {
|
|
this.mode = ProviderMode.NotYetDecided;
|
|
}
|
|
|
|
internal SqlProvider(ProviderMode mode) {
|
|
this.mode = mode;
|
|
}
|
|
|
|
private void CheckInitialized() {
|
|
if (this.services == null) {
|
|
throw Error.ContextNotInitialized();
|
|
}
|
|
}
|
|
private void CheckNotDeleted() {
|
|
if (this.deleted) {
|
|
throw Error.DatabaseDeleteThroughContext();
|
|
}
|
|
}
|
|
|
|
[ResourceExposure(ResourceScope.Machine)] // connection parameter may refer to filenames.
|
|
void IProvider.Initialize(IDataServices dataServices, object connection) {
|
|
if (dataServices == null) {
|
|
throw Error.ArgumentNull("dataServices");
|
|
}
|
|
this.services = dataServices;
|
|
|
|
DbConnection con;
|
|
DbTransaction tx = null;
|
|
|
|
string fileOrServerOrConnectionString = connection as string;
|
|
if (fileOrServerOrConnectionString != null) {
|
|
string connectionString = this.GetConnectionString(fileOrServerOrConnectionString);
|
|
this.dbName = this.GetDatabaseName(connectionString);
|
|
if (this.dbName.EndsWith(".sdf", StringComparison.OrdinalIgnoreCase)) {
|
|
this.mode = ProviderMode.SqlCE;
|
|
}
|
|
if (this.mode == ProviderMode.SqlCE) {
|
|
DbProviderFactory factory = SqlProvider.GetProvider(SqlCeProviderInvariantName);
|
|
if (factory == null) {
|
|
throw Error.ProviderNotInstalled(this.dbName, SqlCeProviderInvariantName);
|
|
}
|
|
con = factory.CreateConnection();
|
|
}
|
|
else {
|
|
con = new SqlConnection();
|
|
}
|
|
con.ConnectionString = connectionString;
|
|
}
|
|
else {
|
|
// We only support SqlTransaction and SqlCeTransaction
|
|
tx = connection as SqlTransaction;
|
|
if (tx == null) {
|
|
// See if it's a SqlCeTransaction
|
|
if (connection.GetType().FullName == SqlCeTransactionTypeName) {
|
|
tx = connection as DbTransaction;
|
|
}
|
|
}
|
|
if (tx != null) {
|
|
connection = tx.Connection;
|
|
}
|
|
con = connection as DbConnection;
|
|
if (con == null) {
|
|
throw Error.InvalidConnectionArgument("connection");
|
|
}
|
|
if (con.GetType().FullName == SqlCeConnectionTypeName) {
|
|
this.mode = ProviderMode.SqlCE;
|
|
}
|
|
this.dbName = this.GetDatabaseName(con.ConnectionString);
|
|
}
|
|
|
|
// initialize to the default command timeout
|
|
using (DbCommand c = con.CreateCommand()) {
|
|
this.commandTimeout = c.CommandTimeout;
|
|
}
|
|
|
|
int maxUsersPerConnection = 1;
|
|
if (con.ConnectionString.IndexOf("MultipleActiveResultSets", StringComparison.OrdinalIgnoreCase) >= 0) {
|
|
DbConnectionStringBuilder builder = new DbConnectionStringBuilder();
|
|
builder.ConnectionString = con.ConnectionString;
|
|
if (string.Compare((string)builder["MultipleActiveResultSets"], "true", StringComparison.OrdinalIgnoreCase) == 0) {
|
|
maxUsersPerConnection = 10;
|
|
}
|
|
}
|
|
|
|
// If fileOrServerOrConnectionString != null, that means we just created the connection instance and we have to tell
|
|
// the SqlConnectionManager that it should dispose the connection when the context is disposed. Otherwise the user owns
|
|
// the connection and should dispose of it themselves.
|
|
this.conManager = new SqlConnectionManager(this, con, maxUsersPerConnection, fileOrServerOrConnectionString != null /*disposeConnection*/);
|
|
if (tx != null) {
|
|
this.conManager.Transaction = tx;
|
|
}
|
|
|
|
#if DEBUG
|
|
SqlNode.Formatter = new SqlFormatter();
|
|
#endif
|
|
|
|
#if ILGEN
|
|
Type readerType;
|
|
if (this.mode == ProviderMode.SqlCE) {
|
|
readerType = con.GetType().Module.GetType(SqlCeDataReaderTypeName);
|
|
}
|
|
else if (con is SqlConnection) {
|
|
readerType = typeof(SqlDataReader);
|
|
}
|
|
else {
|
|
readerType = typeof(DbDataReader);
|
|
}
|
|
this.readerCompiler = new ObjectReaderCompiler(readerType, this.services);
|
|
#else
|
|
this.readerCompiler = new ObjectReaderBuilder(this, this.services);
|
|
#endif
|
|
}
|
|
|
|
private static DbProviderFactory GetProvider(string providerName) {
|
|
bool hasProvider =
|
|
DbProviderFactories.GetFactoryClasses().Rows.OfType<DataRow>()
|
|
.Select(r => (string)r["InvariantName"])
|
|
.Contains(providerName, StringComparer.OrdinalIgnoreCase);
|
|
if (hasProvider) {
|
|
return DbProviderFactories.GetFactory(providerName);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
#region Dispose\Finalize
|
|
public void Dispose() {
|
|
this.disposed = true;
|
|
Dispose(true);
|
|
// Technically, calling GC.SuppressFinalize is not required because the class does not
|
|
// have a finalizer, but it does no harm, protects against the case where a finalizer is added
|
|
// in the future, and prevents an FxCop warning.
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
// Not implementing finalizer here because there are no unmanaged resources
|
|
// to release. See http://msdnwiki.microsoft.com/en-us/mtpswiki/12afb1ea-3a17-4a3f-a1f0-fcdb853e2359.aspx
|
|
|
|
// The bulk of the clean-up code is implemented in Dispose(bool)
|
|
protected virtual void Dispose(bool disposing) {
|
|
// Implemented but empty so that derived contexts can implement
|
|
// a finalizer that potentially cleans up unmanaged resources.
|
|
if (disposing) {
|
|
this.services = null;
|
|
if (this.conManager != null) {
|
|
this.conManager.DisposeConnection();
|
|
}
|
|
this.conManager = null;
|
|
this.typeProvider = null;
|
|
this.sqlFactory = null;
|
|
this.translator = null;
|
|
this.readerCompiler = null;
|
|
this.log = null;
|
|
}
|
|
}
|
|
|
|
internal void CheckDispose() {
|
|
if (this.disposed) {
|
|
throw Error.ProviderCannotBeUsedAfterDispose();
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
private string GetConnectionString(string fileOrServerOrConnectionString) {
|
|
if (fileOrServerOrConnectionString.IndexOf('=') >= 0) {
|
|
return fileOrServerOrConnectionString;
|
|
}
|
|
else {
|
|
DbConnectionStringBuilder builder = new DbConnectionStringBuilder();
|
|
if (fileOrServerOrConnectionString.EndsWith(".mdf", StringComparison.OrdinalIgnoreCase)) {
|
|
// if just a database file is specified, default to local SqlExpress instance
|
|
builder.Add("AttachDBFileName", fileOrServerOrConnectionString);
|
|
builder.Add("Server", "localhost\\sqlexpress");
|
|
builder.Add("Integrated Security", "SSPI");
|
|
builder.Add("User Instance", "true");
|
|
builder.Add("MultipleActiveResultSets", "true");
|
|
}
|
|
else if (fileOrServerOrConnectionString.EndsWith(".sdf", StringComparison.OrdinalIgnoreCase)) {
|
|
// A SqlCE database file has been specified
|
|
builder.Add("Data Source", fileOrServerOrConnectionString);
|
|
}
|
|
else {
|
|
builder.Add("Server", fileOrServerOrConnectionString);
|
|
builder.Add("Database", this.services.Model.DatabaseName);
|
|
builder.Add("Integrated Security", "SSPI");
|
|
}
|
|
return builder.ToString();
|
|
}
|
|
}
|
|
|
|
private string GetDatabaseName(string constr) {
|
|
DbConnectionStringBuilder builder = new DbConnectionStringBuilder();
|
|
builder.ConnectionString = constr;
|
|
|
|
if (builder.ContainsKey("Initial Catalog")) {
|
|
return (string)builder["Initial Catalog"];
|
|
}
|
|
else if (builder.ContainsKey("Database")) {
|
|
return (string)builder["Database"];
|
|
}
|
|
else if (builder.ContainsKey("AttachDBFileName")) {
|
|
return (string)builder["AttachDBFileName"];
|
|
}
|
|
else if (builder.ContainsKey("Data Source")
|
|
&& ((string)builder["Data Source"]).EndsWith(".sdf", StringComparison.OrdinalIgnoreCase)) {
|
|
return (string)builder["Data Source"];
|
|
}
|
|
else {
|
|
return this.services.Model.DatabaseName;
|
|
}
|
|
}
|
|
|
|
[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.")]
|
|
[ResourceExposure(ResourceScope.None)] // Exposure is via other methods that set dbName.
|
|
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] // File.Exists method call.
|
|
void IProvider.CreateDatabase() {
|
|
this.CheckDispose();
|
|
this.CheckInitialized();
|
|
// Don't need to call CheckNotDeleted() here since we allow CreateDatabase after DeleteDatabase
|
|
// Don't need to call InitializeProviderMode() here since we don't need to know the provider to do this.
|
|
string catalog = null;
|
|
string filename = null;
|
|
|
|
DbConnectionStringBuilder builder = new DbConnectionStringBuilder();
|
|
builder.ConnectionString = this.conManager.Connection.ConnectionString;
|
|
|
|
if (this.conManager.Connection.State == ConnectionState.Closed) {
|
|
if (this.mode == ProviderMode.SqlCE) {
|
|
if (!File.Exists(this.dbName)) {
|
|
Type engineType = this.conManager.Connection.GetType().Module.GetType("System.Data.SqlServerCe.SqlCeEngine");
|
|
object engine = Activator.CreateInstance(engineType, new object[] { builder.ToString() });
|
|
try {
|
|
engineType.InvokeMember("CreateDatabase", BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod, null, engine, new object[] { }, CultureInfo.InvariantCulture);
|
|
}
|
|
catch (TargetInvocationException tie) {
|
|
throw tie.InnerException;
|
|
}
|
|
finally {
|
|
IDisposable disp = engine as IDisposable;
|
|
if (disp != null) {
|
|
disp.Dispose();
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
throw Error.CreateDatabaseFailedBecauseSqlCEDatabaseAlreadyExists(this.dbName);
|
|
}
|
|
}
|
|
else {
|
|
// get connection string w/o reference to new catalog
|
|
object val;
|
|
if (builder.TryGetValue("Initial Catalog", out val)) {
|
|
catalog = val.ToString();
|
|
builder.Remove("Initial Catalog");
|
|
}
|
|
if (builder.TryGetValue("Database", out val)) {
|
|
catalog = val.ToString();
|
|
builder.Remove("Database");
|
|
}
|
|
if (builder.TryGetValue("AttachDBFileName", out val)) {
|
|
filename = val.ToString();
|
|
builder.Remove("AttachDBFileName");
|
|
}
|
|
}
|
|
this.conManager.Connection.ConnectionString = builder.ToString();
|
|
}
|
|
else {
|
|
if (this.mode == ProviderMode.SqlCE) {
|
|
if (File.Exists(this.dbName)) {
|
|
throw Error.CreateDatabaseFailedBecauseSqlCEDatabaseAlreadyExists(this.dbName);
|
|
}
|
|
}
|
|
object val;
|
|
if (builder.TryGetValue("Initial Catalog", out val)) {
|
|
catalog = val.ToString();
|
|
}
|
|
if (builder.TryGetValue("Database", out val)) {
|
|
catalog = val.ToString();
|
|
}
|
|
if (builder.TryGetValue("AttachDBFileName", out val)) {
|
|
filename = val.ToString();
|
|
}
|
|
}
|
|
|
|
if (String.IsNullOrEmpty(catalog)) {
|
|
if (!String.IsNullOrEmpty(filename)) {
|
|
catalog = Path.GetFullPath(filename);
|
|
}
|
|
else if (!String.IsNullOrEmpty(this.dbName)) {
|
|
catalog = this.dbName;
|
|
}
|
|
else {
|
|
throw Error.CouldNotDetermineCatalogName();
|
|
}
|
|
}
|
|
|
|
this.conManager.UseConnection(this);
|
|
this.conManager.AutoClose = false;
|
|
|
|
try {
|
|
if (this.services.Model.GetTables().FirstOrDefault() == null) {
|
|
// we have no tables to create
|
|
throw Error.CreateDatabaseFailedBecauseOfContextWithNoTables(this.services.Model.DatabaseName);
|
|
}
|
|
|
|
this.deleted = false;
|
|
|
|
// create database
|
|
if (this.mode == ProviderMode.SqlCE) {
|
|
|
|
// create tables
|
|
foreach (MetaTable table in this.services.Model.GetTables()) {
|
|
string command = SqlBuilder.GetCreateTableCommand(table);
|
|
if (!String.IsNullOrEmpty(command)) {
|
|
this.ExecuteCommand(command);
|
|
}
|
|
}
|
|
// create all foreign keys after all tables are defined
|
|
foreach (MetaTable table in this.services.Model.GetTables()) {
|
|
foreach (string command in SqlBuilder.GetCreateForeignKeyCommands(table)) {
|
|
if (!String.IsNullOrEmpty(command)) {
|
|
this.ExecuteCommand(command);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
string createdb = SqlBuilder.GetCreateDatabaseCommand(catalog, filename, Path.ChangeExtension(filename, ".ldf"));
|
|
this.ExecuteCommand(createdb);
|
|
this.conManager.Connection.ChangeDatabase(catalog);
|
|
|
|
// create the schemas that our tables will need
|
|
// cannot be batched together with the rest of the CREATE TABLES
|
|
if (this.mode == ProviderMode.Sql2005 || this.mode == ProviderMode.Sql2008) {
|
|
HashSet<string> schemaCommands = new HashSet<string>();
|
|
|
|
foreach (MetaTable table in this.services.Model.GetTables()) {
|
|
string schemaCommand = SqlBuilder.GetCreateSchemaForTableCommand(table);
|
|
if (!string.IsNullOrEmpty(schemaCommand)) {
|
|
schemaCommands.Add(schemaCommand);
|
|
}
|
|
}
|
|
|
|
foreach (string schemaCommand in schemaCommands) {
|
|
this.ExecuteCommand(schemaCommand);
|
|
}
|
|
}
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
// create tables
|
|
foreach (MetaTable table in this.services.Model.GetTables()) {
|
|
string createTable = SqlBuilder.GetCreateTableCommand(table);
|
|
if (!string.IsNullOrEmpty(createTable)) {
|
|
sb.AppendLine(createTable);
|
|
}
|
|
}
|
|
|
|
// create all foreign keys after all tables are defined
|
|
foreach (MetaTable table in this.services.Model.GetTables()) {
|
|
foreach (string createFK in SqlBuilder.GetCreateForeignKeyCommands(table)) {
|
|
if (!string.IsNullOrEmpty(createFK)) {
|
|
sb.AppendLine(createFK);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sb.Length > 0) {
|
|
// must be on when creating indexes on computed columns
|
|
sb.Insert(0, "SET ARITHABORT ON" + Environment.NewLine);
|
|
this.ExecuteCommand(sb.ToString());
|
|
}
|
|
}
|
|
}
|
|
finally {
|
|
this.conManager.ReleaseConnection(this);
|
|
if (this.conManager.Connection is SqlConnection) {
|
|
SqlConnection.ClearAllPools();
|
|
}
|
|
}
|
|
}
|
|
|
|
[ResourceExposure(ResourceScope.None)] // Exposure is via other methods that set dbName.
|
|
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] // File.Delete method call.
|
|
void IProvider.DeleteDatabase() {
|
|
this.CheckDispose();
|
|
this.CheckInitialized();
|
|
// Don't need to call InitializeProviderMode() here since we don't need to know the provider to do this.
|
|
if (this.deleted) {
|
|
// 2nd delete is no-op.
|
|
return;
|
|
}
|
|
|
|
if (this.mode == ProviderMode.SqlCE) {
|
|
((IProvider)this).ClearConnection();
|
|
System.Diagnostics.Debug.Assert(this.conManager.Connection.State == ConnectionState.Closed);
|
|
File.Delete(this.dbName);
|
|
this.deleted = true;
|
|
}
|
|
else {
|
|
string holdConnStr = conManager.Connection.ConnectionString;
|
|
DbConnection con = this.conManager.UseConnection(this);
|
|
try {
|
|
con.ChangeDatabase("master");
|
|
if (con is SqlConnection) {
|
|
SqlConnection.ClearAllPools();
|
|
}
|
|
if (this.log != null) {
|
|
this.log.WriteLine(Strings.LogAttemptingToDeleteDatabase(this.dbName));
|
|
}
|
|
this.ExecuteCommand(SqlBuilder.GetDropDatabaseCommand(this.dbName));
|
|
this.deleted = true;
|
|
}
|
|
finally {
|
|
this.conManager.ReleaseConnection(this);
|
|
if (conManager.Connection.State == ConnectionState.Closed &&
|
|
string.Compare(conManager.Connection.ConnectionString, holdConnStr, StringComparison.Ordinal) != 0) {
|
|
// Credential information may have been stripped from the connection
|
|
// string as a result of opening the connection. Restore the full
|
|
// connection string.
|
|
conManager.Connection.ConnectionString = holdConnStr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification="Microsoft: Code needs to return false regarless of exception.")]
|
|
[ResourceExposure(ResourceScope.None)] // Exposure is via other methods that set dbName.
|
|
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] // File.Exists method call.
|
|
bool IProvider.DatabaseExists() {
|
|
this.CheckDispose();
|
|
this.CheckInitialized();
|
|
if (this.deleted) {
|
|
return false;
|
|
}
|
|
// Don't need to call InitializeProviderMode() here since we don't need to know the provider to do this.
|
|
|
|
bool exists = false;
|
|
if (this.mode == ProviderMode.SqlCE) {
|
|
exists = File.Exists(this.dbName);
|
|
}
|
|
else {
|
|
string holdConnStr = conManager.Connection.ConnectionString;
|
|
try {
|
|
// If no database name is explicitly specified on the connection,
|
|
// UseConnection will connect to 'Master', which is why after connecting
|
|
// we call ChangeDatabase to verify that the database actually exists.
|
|
this.conManager.UseConnection(this);
|
|
this.conManager.Connection.ChangeDatabase(this.dbName);
|
|
this.conManager.ReleaseConnection(this);
|
|
exists = true;
|
|
} catch (Exception) {
|
|
} finally {
|
|
if (conManager.Connection.State == ConnectionState.Closed &&
|
|
string.Compare(conManager.Connection.ConnectionString, holdConnStr, StringComparison.Ordinal) != 0) {
|
|
// Credential information may have been stripped from the connection
|
|
// string as a result of opening the connection. Restore the full
|
|
// connection string.
|
|
conManager.Connection.ConnectionString = holdConnStr;
|
|
}
|
|
}
|
|
}
|
|
return exists;
|
|
}
|
|
|
|
void IConnectionUser.CompleteUse() {
|
|
}
|
|
|
|
void IProvider.ClearConnection() {
|
|
this.CheckDispose();
|
|
this.CheckInitialized();
|
|
this.conManager.ClearConnection();
|
|
}
|
|
|
|
private void ExecuteCommand(string command) {
|
|
if (this.log != null) {
|
|
this.log.WriteLine(command);
|
|
this.log.WriteLine();
|
|
}
|
|
IDbCommand cmd = this.conManager.Connection.CreateCommand();
|
|
cmd.CommandTimeout = this.commandTimeout;
|
|
cmd.Transaction = this.conManager.Transaction;
|
|
cmd.CommandText = command;
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
ICompiledQuery IProvider.Compile(Expression query) {
|
|
this.CheckDispose();
|
|
this.CheckInitialized();
|
|
if (query == null) {
|
|
throw Error.ArgumentNull("query");
|
|
}
|
|
this.InitializeProviderMode();
|
|
|
|
SqlNodeAnnotations annotations = new SqlNodeAnnotations();
|
|
QueryInfo[] qis = this.BuildQuery(query, annotations);
|
|
CheckSqlCompatibility(qis, annotations);
|
|
|
|
LambdaExpression lambda = query as LambdaExpression;
|
|
if (lambda != null) {
|
|
query = lambda.Body;
|
|
}
|
|
|
|
IObjectReaderFactory factory = null;
|
|
ICompiledSubQuery[] subQueries = null;
|
|
QueryInfo qi = qis[qis.Length - 1];
|
|
if (qi.ResultShape == ResultShape.Singleton) {
|
|
subQueries = this.CompileSubQueries(qi.Query);
|
|
factory = this.GetReaderFactory(qi.Query, qi.ResultType);
|
|
}
|
|
else if (qi.ResultShape == ResultShape.Sequence) {
|
|
subQueries = this.CompileSubQueries(qi.Query);
|
|
factory = this.GetReaderFactory(qi.Query, TypeSystem.GetElementType(qi.ResultType));
|
|
}
|
|
|
|
return new CompiledQuery(this, query, qis, factory, subQueries);
|
|
}
|
|
|
|
private ICompiledSubQuery CompileSubQuery(SqlNode query, Type elementType, ReadOnlyCollection<Me.SqlParameter> parameters) {
|
|
query = SqlDuplicator.Copy(query);
|
|
SqlNodeAnnotations annotations = new SqlNodeAnnotations();
|
|
|
|
QueryInfo[] qis = this.BuildQuery(ResultShape.Sequence, TypeSystem.GetSequenceType(elementType), query, parameters, annotations);
|
|
System.Diagnostics.Debug.Assert(qis.Length == 1);
|
|
QueryInfo qi = qis[0];
|
|
ICompiledSubQuery[] subQueries = this.CompileSubQueries(qi.Query);
|
|
IObjectReaderFactory factory = this.GetReaderFactory(qi.Query, elementType);
|
|
|
|
CheckSqlCompatibility(qis, annotations);
|
|
|
|
return new CompiledSubQuery(qi, factory, parameters, subQueries);
|
|
}
|
|
|
|
IExecuteResult IProvider.Execute(Expression query) {
|
|
this.CheckDispose();
|
|
this.CheckInitialized();
|
|
this.CheckNotDeleted();
|
|
if (query == null) {
|
|
throw Error.ArgumentNull("query");
|
|
}
|
|
this.InitializeProviderMode();
|
|
|
|
#if PERFORMANCE_BUILD
|
|
PerformanceCounter pcBuildQuery = null, bpcBuildQuery = null, pcExecQuery = null, bpcExecQuery = null,
|
|
pcSession = null, bpcSession = null;
|
|
PerfTimer timerAll = null, timer = null;
|
|
if (this.CollectPerfInfo) {
|
|
string s = System.Environment.GetEnvironmentVariable("EnableDLinqQueryPerf");
|
|
collectQueryPerf = (s != null && s == "On");
|
|
}
|
|
if (collectQueryPerf) {
|
|
pcBuildQuery = new PerformanceCounter("DLinq", "BuildQueryElapsedTime", false);
|
|
bpcBuildQuery = new PerformanceCounter("DLinq", "BuildQueryElapsedTimeBase", false);
|
|
pcExecQuery = new PerformanceCounter("DLinq", "ExecuteQueryElapsedTime", false);
|
|
bpcExecQuery = new PerformanceCounter("DLinq", "ExecuteQueryElapsedTimeBase", false);
|
|
pcSession = new PerformanceCounter("DLinq", "SessionExecuteQueryElapsedTime", false);
|
|
bpcSession = new PerformanceCounter("DLinq", "SessionExecuteQueryElapsedTimeBase", false);
|
|
timerAll = new PerfTimer();
|
|
timer = new PerfTimer();
|
|
timerAll.Start();
|
|
}
|
|
#endif
|
|
query = Funcletizer.Funcletize(query);
|
|
|
|
if (this.EnableCacheLookup) {
|
|
IExecuteResult cached = this.GetCachedResult(query);
|
|
if (cached != null) {
|
|
return cached;
|
|
}
|
|
}
|
|
|
|
#if PERFORMANCE_BUILD
|
|
if (collectQueryPerf) {
|
|
timer.Start();
|
|
}
|
|
#endif
|
|
SqlNodeAnnotations annotations = new SqlNodeAnnotations();
|
|
QueryInfo[] qis = this.BuildQuery(query, annotations);
|
|
CheckSqlCompatibility(qis, annotations);
|
|
|
|
LambdaExpression lambda = query as LambdaExpression;
|
|
if (lambda != null) {
|
|
query = lambda.Body;
|
|
}
|
|
|
|
IObjectReaderFactory factory = null;
|
|
ICompiledSubQuery[] subQueries = null;
|
|
QueryInfo qi = qis[qis.Length - 1];
|
|
if (qi.ResultShape == ResultShape.Singleton) {
|
|
subQueries = this.CompileSubQueries(qi.Query);
|
|
factory = this.GetReaderFactory(qi.Query, qi.ResultType);
|
|
}
|
|
else if (qi.ResultShape == ResultShape.Sequence) {
|
|
subQueries = this.CompileSubQueries(qi.Query);
|
|
factory = this.GetReaderFactory(qi.Query, TypeSystem.GetElementType(qi.ResultType));
|
|
}
|
|
|
|
#if PERFORMANCE_BUILD
|
|
if (collectQueryPerf) {
|
|
timer.Stop();
|
|
pcBuildQuery.IncrementBy(timer.Duration);
|
|
bpcBuildQuery.Increment();
|
|
}
|
|
#endif
|
|
|
|
#if PERFORMANCE_BUILD
|
|
if (collectQueryPerf) {
|
|
timer.Start();
|
|
}
|
|
|
|
#endif
|
|
IExecuteResult result = this.ExecuteAll(query, qis, factory, null, subQueries);
|
|
|
|
#if PERFORMANCE_BUILD
|
|
if (collectQueryPerf) {
|
|
timer.Stop();
|
|
pcSession.IncrementBy(timer.Duration);
|
|
bpcSession.Increment();
|
|
timerAll.Stop();
|
|
pcExecQuery.IncrementBy(timerAll.Duration);
|
|
bpcExecQuery.Increment();
|
|
}
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
private ICompiledSubQuery[] CompileSubQueries(SqlNode query) {
|
|
return new SubQueryCompiler(this).Compile(query);
|
|
}
|
|
|
|
class SubQueryCompiler : SqlVisitor {
|
|
SqlProvider provider;
|
|
List<ICompiledSubQuery> subQueries;
|
|
|
|
internal SubQueryCompiler(SqlProvider provider) {
|
|
this.provider = provider;
|
|
}
|
|
|
|
internal ICompiledSubQuery[] Compile(SqlNode node) {
|
|
this.subQueries = new List<ICompiledSubQuery>();
|
|
this.Visit(node);
|
|
return this.subQueries.ToArray();
|
|
}
|
|
|
|
internal override SqlSelect VisitSelect(SqlSelect select) {
|
|
this.Visit(select.Selection);
|
|
return select;
|
|
}
|
|
|
|
internal override SqlExpression VisitSubSelect(SqlSubSelect ss) {
|
|
return ss;
|
|
}
|
|
|
|
internal override SqlExpression VisitClientQuery(SqlClientQuery cq) {
|
|
Type clientElementType = cq.Query.NodeType == SqlNodeType.Multiset ? TypeSystem.GetElementType(cq.ClrType) : cq.ClrType;
|
|
ICompiledSubQuery c = this.provider.CompileSubQuery(cq.Query.Select, clientElementType, cq.Parameters.AsReadOnly());
|
|
cq.Ordinal = this.subQueries.Count;
|
|
this.subQueries.Add(c);
|
|
return cq;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Look for compatibility annotations for the set of providers we
|
|
/// add annotations for.
|
|
/// </summary>
|
|
private void CheckSqlCompatibility(QueryInfo[] queries, SqlNodeAnnotations annotations) {
|
|
if (this.Mode == ProviderMode.Sql2000 ||
|
|
this.Mode == ProviderMode.SqlCE) {
|
|
for (int i = 0, n = queries.Length; i < n; i++) {
|
|
SqlServerCompatibilityCheck.ThrowIfUnsupported(queries[i].Query, annotations, this.Mode);
|
|
}
|
|
}
|
|
}
|
|
|
|
private IExecuteResult ExecuteAll(Expression query, QueryInfo[] queryInfos, IObjectReaderFactory factory, object[] userArguments, ICompiledSubQuery[] subQueries) {
|
|
IExecuteResult result = null;
|
|
object lastResult = null;
|
|
for (int i = 0, n = queryInfos.Length; i < n; i++) {
|
|
if (i < n - 1) {
|
|
result = this.Execute(query, queryInfos[i], null, null, userArguments, subQueries, lastResult);
|
|
}
|
|
else {
|
|
result = this.Execute(query, queryInfos[i], factory, null, userArguments, subQueries, lastResult);
|
|
}
|
|
if (queryInfos[i].ResultShape == ResultShape.Return) {
|
|
lastResult = result.ReturnValue;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
|
|
private IExecuteResult GetCachedResult(Expression query) {
|
|
object obj = this.services.GetCachedObject(query);
|
|
if (obj != null) {
|
|
switch (this.GetResultShape(query)) {
|
|
case ResultShape.Singleton:
|
|
return new ExecuteResult(null, null, null, obj);
|
|
case ResultShape.Sequence:
|
|
return new ExecuteResult(null, null, null,
|
|
Activator.CreateInstance(
|
|
typeof(SequenceOfOne<>).MakeGenericType(TypeSystem.GetElementType(this.GetResultType(query))),
|
|
BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { obj }, null
|
|
));
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification="Unknown reason.")]
|
|
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
|
|
private IExecuteResult Execute(Expression query, QueryInfo queryInfo, IObjectReaderFactory factory, object[] parentArgs, object[] userArgs, ICompiledSubQuery[] subQueries, object lastResult) {
|
|
this.InitializeProviderMode();
|
|
|
|
DbConnection con = this.conManager.UseConnection(this);
|
|
try {
|
|
DbCommand cmd = con.CreateCommand();
|
|
cmd.CommandText = queryInfo.CommandText;
|
|
cmd.Transaction = this.conManager.Transaction;
|
|
cmd.CommandTimeout = this.commandTimeout;
|
|
AssignParameters(cmd, queryInfo.Parameters, userArgs, lastResult);
|
|
LogCommand(this.log, cmd);
|
|
this.queryCount += 1;
|
|
|
|
switch (queryInfo.ResultShape) {
|
|
default:
|
|
case ResultShape.Return: {
|
|
return new ExecuteResult(cmd, queryInfo.Parameters, null, cmd.ExecuteNonQuery(), true);
|
|
}
|
|
case ResultShape.Singleton: {
|
|
DbDataReader reader = cmd.ExecuteReader();
|
|
IObjectReader objReader = factory.Create(reader, true, this, parentArgs, userArgs, subQueries);
|
|
this.conManager.UseConnection(objReader.Session);
|
|
try {
|
|
IEnumerable sequence = (IEnumerable)Activator.CreateInstance(
|
|
typeof(OneTimeEnumerable<>).MakeGenericType(queryInfo.ResultType),
|
|
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null,
|
|
new object[] { objReader }, null
|
|
);
|
|
object value = null;
|
|
MethodCallExpression mce = query as MethodCallExpression;
|
|
MethodInfo sequenceMethod = null;
|
|
if (mce != null && (
|
|
mce.Method.DeclaringType == typeof(Queryable) ||
|
|
mce.Method.DeclaringType == typeof(Enumerable))
|
|
) {
|
|
switch (mce.Method.Name) {
|
|
case "First":
|
|
case "FirstOrDefault":
|
|
case "SingleOrDefault":
|
|
sequenceMethod = TypeSystem.FindSequenceMethod(mce.Method.Name, sequence);
|
|
break;
|
|
case "Single":
|
|
default:
|
|
sequenceMethod = TypeSystem.FindSequenceMethod("Single", sequence);
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
sequenceMethod = TypeSystem.FindSequenceMethod("SingleOrDefault", sequence);
|
|
}
|
|
|
|
// When dynamically invoking the sequence method, we want to
|
|
// return the inner exception if the invocation fails
|
|
if (sequenceMethod != null) {
|
|
try {
|
|
value = sequenceMethod.Invoke(null, new object[] { sequence });
|
|
}
|
|
catch (TargetInvocationException tie) {
|
|
if (tie.InnerException != null) {
|
|
throw tie.InnerException;
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
|
|
return new ExecuteResult(cmd, queryInfo.Parameters, objReader.Session, value);
|
|
}
|
|
finally {
|
|
objReader.Dispose();
|
|
}
|
|
}
|
|
case ResultShape.Sequence: {
|
|
DbDataReader reader = cmd.ExecuteReader();
|
|
IObjectReader objReader = factory.Create(reader, true, this, parentArgs, userArgs, subQueries);
|
|
this.conManager.UseConnection(objReader.Session);
|
|
IEnumerable sequence = (IEnumerable)Activator.CreateInstance(
|
|
typeof(OneTimeEnumerable<>).MakeGenericType(TypeSystem.GetElementType(queryInfo.ResultType)),
|
|
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null,
|
|
new object[] { objReader }, null
|
|
);
|
|
if (typeof(IQueryable).IsAssignableFrom(queryInfo.ResultType)) {
|
|
sequence = sequence.AsQueryable();
|
|
}
|
|
ExecuteResult result = new ExecuteResult(cmd, queryInfo.Parameters, objReader.Session);
|
|
MetaFunction function = this.GetFunction(query);
|
|
if (function != null && !function.IsComposable) {
|
|
sequence = (IEnumerable)Activator.CreateInstance(
|
|
typeof(SingleResult<>).MakeGenericType(TypeSystem.GetElementType(queryInfo.ResultType)),
|
|
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null,
|
|
new object[] { sequence, result, this.services.Context }, null
|
|
);
|
|
}
|
|
result.ReturnValue = sequence;
|
|
return result;
|
|
}
|
|
case ResultShape.MultipleResults: {
|
|
DbDataReader reader = cmd.ExecuteReader();
|
|
IObjectReaderSession session = this.readerCompiler.CreateSession(reader, this, parentArgs, userArgs, subQueries);
|
|
this.conManager.UseConnection(session);
|
|
MetaFunction function = this.GetFunction(query);
|
|
ExecuteResult result = new ExecuteResult(cmd, queryInfo.Parameters, session);
|
|
result.ReturnValue = new MultipleResults(this, function, session, result);
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
finally {
|
|
this.conManager.ReleaseConnection(this);
|
|
}
|
|
}
|
|
|
|
private MetaFunction GetFunction(Expression query) {
|
|
LambdaExpression lambda = query as LambdaExpression;
|
|
if (lambda != null) {
|
|
query = lambda.Body;
|
|
}
|
|
MethodCallExpression mc = query as MethodCallExpression;
|
|
if (mc != null && typeof(DataContext).IsAssignableFrom(mc.Method.DeclaringType)) {
|
|
return this.services.Model.GetFunction(mc.Method);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private void LogCommand(TextWriter writer, DbCommand cmd) {
|
|
if (writer != null) {
|
|
writer.WriteLine(cmd.CommandText);
|
|
foreach (DbParameter p in cmd.Parameters) {
|
|
int prec = 0;
|
|
int scale = 0;
|
|
PropertyInfo piPrecision = p.GetType().GetProperty("Precision");
|
|
if (piPrecision != null) {
|
|
prec = (int)Convert.ChangeType(piPrecision.GetValue(p, null), typeof(int), CultureInfo.InvariantCulture);
|
|
}
|
|
PropertyInfo piScale = p.GetType().GetProperty("Scale");
|
|
if (piScale != null) {
|
|
scale = (int)Convert.ChangeType(piScale.GetValue(p, null), typeof(int), CultureInfo.InvariantCulture);
|
|
}
|
|
var sp = p as System.Data.SqlClient.SqlParameter;
|
|
writer.WriteLine("-- {0}: {1} {2} (Size = {3}; Prec = {4}; Scale = {5}) [{6}]",
|
|
p.ParameterName,
|
|
p.Direction,
|
|
sp == null ? p.DbType.ToString() : sp.SqlDbType.ToString(),
|
|
p.Size.ToString(System.Globalization.CultureInfo.CurrentCulture),
|
|
prec,
|
|
scale,
|
|
sp == null ? p.Value : sp.SqlValue);
|
|
}
|
|
writer.WriteLine("-- Context: {0}({1}) Model: {2} Build: {3}", this.GetType().Name, this.Mode, this.services.Model.GetType().Name, ThisAssembly.InformationalVersion);
|
|
writer.WriteLine();
|
|
}
|
|
}
|
|
|
|
private void AssignParameters(DbCommand cmd, ReadOnlyCollection<SqlParameterInfo> parms, object[] userArguments, object lastResult) {
|
|
if (parms != null) {
|
|
foreach (SqlParameterInfo pi in parms) {
|
|
DbParameter p = cmd.CreateParameter();
|
|
p.ParameterName = pi.Parameter.Name;
|
|
p.Direction = pi.Parameter.Direction;
|
|
if (pi.Parameter.Direction == ParameterDirection.Input ||
|
|
pi.Parameter.Direction == ParameterDirection.InputOutput) {
|
|
object value = pi.Value;
|
|
switch (pi.Type) {
|
|
case SqlParameterType.UserArgument:
|
|
try {
|
|
value = pi.Accessor.DynamicInvoke(new object[] { userArguments });
|
|
} catch (System.Reflection.TargetInvocationException e) {
|
|
throw e.InnerException;
|
|
}
|
|
break;
|
|
case SqlParameterType.PreviousResult:
|
|
value = lastResult;
|
|
break;
|
|
}
|
|
this.typeProvider.InitializeParameter(pi.Parameter.SqlType, p, value);
|
|
}
|
|
else {
|
|
this.typeProvider.InitializeParameter(pi.Parameter.SqlType, p, null);
|
|
}
|
|
cmd.Parameters.Add(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
|
|
IEnumerable IProvider.Translate(Type elementType, DbDataReader reader) {
|
|
this.CheckDispose();
|
|
this.CheckInitialized();
|
|
this.InitializeProviderMode();
|
|
if (elementType == null) {
|
|
throw Error.ArgumentNull("elementType");
|
|
}
|
|
if (reader == null) {
|
|
throw Error.ArgumentNull("reader");
|
|
}
|
|
MetaType rowType = services.Model.GetMetaType(elementType);
|
|
IObjectReaderFactory factory = this.GetDefaultFactory(rowType);
|
|
IEnumerator e = factory.Create(reader, true, this, null, null, null);
|
|
Type enumerableType = typeof(OneTimeEnumerable<>).MakeGenericType(elementType);
|
|
return (IEnumerable)Activator.CreateInstance(enumerableType, BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { e }, null);
|
|
}
|
|
|
|
IMultipleResults IProvider.Translate(DbDataReader reader) {
|
|
this.CheckDispose();
|
|
this.CheckInitialized();
|
|
this.InitializeProviderMode();
|
|
if (reader == null) {
|
|
throw Error.ArgumentNull("reader");
|
|
}
|
|
IObjectReaderSession session = this.readerCompiler.CreateSession(reader, this, null, null, null);
|
|
return new MultipleResults(this, null, session, null);
|
|
}
|
|
|
|
string IProvider.GetQueryText(Expression query) {
|
|
this.CheckDispose();
|
|
this.CheckInitialized();
|
|
if (query == null) {
|
|
throw Error.ArgumentNull("query");
|
|
}
|
|
this.InitializeProviderMode();
|
|
SqlNodeAnnotations annotations = new SqlNodeAnnotations();
|
|
QueryInfo[] qis = this.BuildQuery(query, annotations);
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
for (int i = 0, n = qis.Length; i < n; i++) {
|
|
QueryInfo qi = qis[i];
|
|
#if DEBUG
|
|
StringWriter writer = new StringWriter(System.Globalization.CultureInfo.InvariantCulture);
|
|
DbCommand cmd = this.conManager.Connection.CreateCommand();
|
|
cmd.CommandText = qi.CommandText;
|
|
AssignParameters(cmd, qi.Parameters, null, null);
|
|
LogCommand(writer, cmd);
|
|
sb.Append(writer.ToString());
|
|
#else
|
|
sb.Append(qi.CommandText);
|
|
sb.AppendLine();
|
|
#endif
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
|
|
DbCommand IProvider.GetCommand(Expression query) {
|
|
this.CheckDispose();
|
|
this.CheckInitialized();
|
|
if (query == null) {
|
|
throw Error.ArgumentNull("query");
|
|
}
|
|
this.InitializeProviderMode();
|
|
SqlNodeAnnotations annotations = new SqlNodeAnnotations();
|
|
QueryInfo[] qis = this.BuildQuery(query, annotations);
|
|
QueryInfo qi = qis[qis.Length - 1];
|
|
DbCommand cmd = this.conManager.Connection.CreateCommand();
|
|
cmd.CommandText = qi.CommandText;
|
|
cmd.Transaction = this.conManager.Transaction;
|
|
cmd.CommandTimeout = this.commandTimeout;
|
|
AssignParameters(cmd, qi.Parameters, null, null);
|
|
return cmd;
|
|
}
|
|
|
|
internal class QueryInfo {
|
|
SqlNode query;
|
|
string commandText;
|
|
ReadOnlyCollection<SqlParameterInfo> parameters;
|
|
ResultShape resultShape;
|
|
Type resultType;
|
|
|
|
internal QueryInfo(SqlNode query, string commandText, ReadOnlyCollection<SqlParameterInfo> parameters, ResultShape resultShape, Type resultType) {
|
|
this.query = query;
|
|
this.commandText = commandText;
|
|
this.parameters = parameters;
|
|
this.resultShape = resultShape;
|
|
this.resultType = resultType;
|
|
}
|
|
internal SqlNode Query {
|
|
get { return this.query; }
|
|
}
|
|
internal string CommandText {
|
|
get { return this.commandText; }
|
|
}
|
|
internal ReadOnlyCollection<SqlParameterInfo> Parameters {
|
|
get { return this.parameters; }
|
|
}
|
|
internal ResultShape ResultShape {
|
|
get { return this.resultShape; }
|
|
}
|
|
internal Type ResultType {
|
|
get { return this.resultType; }
|
|
}
|
|
}
|
|
|
|
internal enum ResultShape {
|
|
Return,
|
|
Singleton,
|
|
Sequence,
|
|
MultipleResults
|
|
}
|
|
|
|
private ResultShape GetResultShape(Expression query) {
|
|
LambdaExpression lambda = query as LambdaExpression;
|
|
if (lambda != null) {
|
|
query = lambda.Body;
|
|
}
|
|
|
|
if (query.Type == typeof(void)) {
|
|
return ResultShape.Return;
|
|
}
|
|
else if (query.Type == typeof(IMultipleResults)) {
|
|
return ResultShape.MultipleResults;
|
|
}
|
|
|
|
bool isSequence = typeof(IEnumerable).IsAssignableFrom(query.Type);
|
|
ProviderType pt = this.typeProvider.From(query.Type);
|
|
bool isScalar = !pt.IsRuntimeOnlyType && !pt.IsApplicationType;
|
|
bool isSingleton = isScalar || !isSequence;
|
|
|
|
MethodCallExpression mce = query as MethodCallExpression;
|
|
if (mce != null) {
|
|
// query operators
|
|
if (mce.Method.DeclaringType == typeof(Queryable) ||
|
|
mce.Method.DeclaringType == typeof(Enumerable)) {
|
|
switch (mce.Method.Name) {
|
|
// methods known to produce singletons
|
|
case "First":
|
|
case "FirstOrDefault":
|
|
case "Single":
|
|
case "SingleOrDefault":
|
|
isSingleton = true;
|
|
break;
|
|
}
|
|
}
|
|
else if (mce.Method.DeclaringType == typeof(DataContext)) {
|
|
if (mce.Method.Name == "ExecuteCommand") {
|
|
return ResultShape.Return;
|
|
}
|
|
}
|
|
else if (mce.Method.DeclaringType.IsSubclassOf(typeof(DataContext))) {
|
|
MetaFunction f = this.GetFunction(query);
|
|
if (f != null) {
|
|
if (!f.IsComposable) {
|
|
isSingleton = false;
|
|
}
|
|
else if (isScalar) {
|
|
isSingleton = true;
|
|
}
|
|
}
|
|
}
|
|
else if (mce.Method.DeclaringType == typeof(DataManipulation) && mce.Method.ReturnType == typeof(int)) {
|
|
return ResultShape.Return;
|
|
}
|
|
}
|
|
|
|
if (isSingleton) {
|
|
return ResultShape.Singleton;
|
|
}
|
|
else if (isScalar) {
|
|
return ResultShape.Return;
|
|
}
|
|
else {
|
|
return ResultShape.Sequence;
|
|
}
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")]
|
|
private Type GetResultType(Expression query) {
|
|
LambdaExpression lambda = query as LambdaExpression;
|
|
if (lambda != null) {
|
|
query = lambda.Body;
|
|
}
|
|
return query.Type;
|
|
}
|
|
|
|
internal QueryInfo[] BuildQuery(Expression query, SqlNodeAnnotations annotations) {
|
|
this.CheckDispose();
|
|
|
|
// apply maximal funcletization
|
|
query = Funcletizer.Funcletize(query);
|
|
|
|
// convert query nodes into sql nodes
|
|
QueryConverter converter = new QueryConverter(this.services, this.typeProvider, this.translator, this.sqlFactory);
|
|
switch (this.Mode) {
|
|
case ProviderMode.Sql2000:
|
|
converter.ConverterStrategy =
|
|
ConverterStrategy.CanUseScopeIdentity |
|
|
ConverterStrategy.CanUseJoinOn |
|
|
ConverterStrategy.CanUseRowStatus;
|
|
break;
|
|
case ProviderMode.Sql2005:
|
|
case ProviderMode.Sql2008:
|
|
converter.ConverterStrategy =
|
|
ConverterStrategy.CanUseScopeIdentity |
|
|
ConverterStrategy.SkipWithRowNumber |
|
|
ConverterStrategy.CanUseRowStatus |
|
|
ConverterStrategy.CanUseJoinOn |
|
|
ConverterStrategy.CanUseOuterApply |
|
|
ConverterStrategy.CanOutputFromInsert;
|
|
break;
|
|
case ProviderMode.SqlCE:
|
|
converter.ConverterStrategy = ConverterStrategy.CanUseOuterApply;
|
|
// Can't set ConverterStrategy.CanUseJoinOn because scalar subqueries in the ON clause
|
|
// can't be converted into anything.
|
|
break;
|
|
}
|
|
SqlNode node = converter.ConvertOuter(query);
|
|
|
|
return this.BuildQuery(this.GetResultShape(query), this.GetResultType(query), node, null, annotations);
|
|
}
|
|
|
|
[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.")]
|
|
private QueryInfo[] BuildQuery(ResultShape resultShape, Type resultType, SqlNode node, ReadOnlyCollection<Me.SqlParameter> parentParameters, SqlNodeAnnotations annotations) {
|
|
System.Diagnostics.Debug.Assert(resultType != null);
|
|
System.Diagnostics.Debug.Assert(node != null);
|
|
|
|
SqlSupersetValidator validator = new SqlSupersetValidator();
|
|
|
|
// These are the rules that apply to every SQL tree.
|
|
if (this.checkQueries) {
|
|
validator.AddValidator(new ColumnTypeValidator()); /* Column CLR Type must agree with its Expressions CLR Type */
|
|
validator.AddValidator(new LiteralValidator()); /* Constrain literal Types */
|
|
}
|
|
|
|
validator.Validate(node);
|
|
|
|
SqlColumnizer columnizer = new SqlColumnizer();
|
|
|
|
// resolve member references
|
|
bool canUseOuterApply = (this.Mode == ProviderMode.Sql2005 || this.Mode == ProviderMode.Sql2008 || this.Mode == ProviderMode.SqlCE);
|
|
SqlBinder binder = new SqlBinder(this.translator, this.sqlFactory, this.services.Model, this.services.Context.LoadOptions, columnizer, canUseOuterApply);
|
|
binder.OptimizeLinkExpansions = (optimizationFlags & OptimizationFlags.OptimizeLinkExpansions) != 0;
|
|
binder.SimplifyCaseStatements = (optimizationFlags & OptimizationFlags.SimplifyCaseStatements) != 0;
|
|
binder.PreBinder = delegate(SqlNode n) {
|
|
// convert methods into known reversable operators
|
|
return PreBindDotNetConverter.Convert(n, this.sqlFactory, this.services.Model);
|
|
};
|
|
node = binder.Bind(node);
|
|
if (this.checkQueries) {
|
|
validator.AddValidator(new ExpectNoAliasRefs());
|
|
validator.AddValidator(new ExpectNoSharedExpressions());
|
|
}
|
|
validator.Validate(node);
|
|
|
|
node = PostBindDotNetConverter.Convert(node, this.sqlFactory, this.Mode);
|
|
|
|
// identify true flow of sql data types
|
|
SqlRetyper retyper = new SqlRetyper(this.typeProvider, this.services.Model);
|
|
node = retyper.Retype(node);
|
|
validator.Validate(node);
|
|
|
|
// change CONVERT to special conversions like UNICODE,CHAR,...
|
|
SqlTypeConverter converter = new SqlTypeConverter(this.sqlFactory);
|
|
node = converter.Visit(node);
|
|
validator.Validate(node);
|
|
|
|
// transform type-sensitive methods such as LEN (to DATALENGTH), ...
|
|
SqlMethodTransformer methodTransformer = new SqlMethodTransformer(this.sqlFactory);
|
|
node = methodTransformer.Visit(node);
|
|
validator.Validate(node);
|
|
|
|
// convert multisets into separate queries
|
|
SqlMultiplexer.Options options = (this.Mode == ProviderMode.Sql2008 ||
|
|
this.Mode == ProviderMode.Sql2005 ||
|
|
this.Mode == ProviderMode.SqlCE)
|
|
? SqlMultiplexer.Options.EnableBigJoin : SqlMultiplexer.Options.None;
|
|
SqlMultiplexer mux = new SqlMultiplexer(options, parentParameters, this.sqlFactory);
|
|
node = mux.Multiplex(node);
|
|
validator.Validate(node);
|
|
|
|
// convert object construction expressions into flat row projections
|
|
SqlFlattener flattener = new SqlFlattener(this.sqlFactory, columnizer);
|
|
node = flattener.Flatten(node);
|
|
validator.Validate(node);
|
|
|
|
if (this.mode == ProviderMode.SqlCE) {
|
|
SqlRewriteScalarSubqueries rss = new SqlRewriteScalarSubqueries(this.sqlFactory);
|
|
node = rss.Rewrite(node);
|
|
}
|
|
|
|
// Simplify case statements where all alternatives map to the same thing.
|
|
// Doing this before deflator because the simplified results may lead to
|
|
// more deflation opportunities.
|
|
// Doing this before booleanizer because it may convert CASE statements (non-predicates) into
|
|
// predicate expressions.
|
|
// Doing this before reorderer because it may reduce some orders to constant nodes which should not
|
|
// be passed onto ROW_NUMBER.
|
|
node = SqlCaseSimplifier.Simplify(node, this.sqlFactory);
|
|
|
|
// Rewrite order-by clauses so that they only occur at the top-most select
|
|
// or in selects with TOP
|
|
SqlReorderer reorderer = new SqlReorderer(this.typeProvider, this.sqlFactory);
|
|
node = reorderer.Reorder(node);
|
|
validator.Validate(node);
|
|
|
|
// Inject code to turn predicates into bits, and bits into predicates where necessary
|
|
node = SqlBooleanizer.Rationalize(node, this.typeProvider, this.services.Model);
|
|
if (this.checkQueries) {
|
|
validator.AddValidator(new ExpectRationalizedBooleans()); /* From now on all boolean expressions should remain rationalized. */
|
|
}
|
|
validator.Validate(node);
|
|
|
|
if (this.checkQueries) {
|
|
validator.AddValidator(new ExpectNoFloatingColumns());
|
|
}
|
|
|
|
// turning predicates into bits/ints can change Sql types, propagate changes
|
|
node = retyper.Retype(node);
|
|
validator.Validate(node);
|
|
|
|
// assign aliases to columns
|
|
// we need to do this now so that the sql2k lifters will work
|
|
SqlAliaser aliaser = new SqlAliaser();
|
|
node = aliaser.AssociateColumnsWithAliases(node);
|
|
validator.Validate(node);
|
|
|
|
// SQL2K enablers.
|
|
node = SqlLiftWhereClauses.Lift(node, this.typeProvider, this.services.Model);
|
|
node = SqlLiftIndependentRowExpressions.Lift(node);
|
|
node = SqlOuterApplyReducer.Reduce(node, this.sqlFactory, annotations);
|
|
node = SqlTopReducer.Reduce(node, annotations, this.sqlFactory);
|
|
|
|
// resolve references to columns in other scopes by adding them
|
|
// to the intermediate selects
|
|
SqlResolver resolver = new SqlResolver();
|
|
node = resolver.Resolve(node);
|
|
validator.Validate(node);
|
|
|
|
// re-assign aliases after resolving (new columns may have been added)
|
|
node = aliaser.AssociateColumnsWithAliases(node);
|
|
validator.Validate(node);
|
|
|
|
// fixup union projections
|
|
node = SqlUnionizer.Unionize(node);
|
|
|
|
// remove order-by of literals
|
|
node = SqlRemoveConstantOrderBy.Remove(node);
|
|
|
|
// throw out unused columns and redundant sub-queries...
|
|
SqlDeflator deflator = new SqlDeflator();
|
|
node = deflator.Deflate(node);
|
|
validator.Validate(node);
|
|
|
|
// Positioning after deflator because it may remove unnecessary columns
|
|
// from SELECT projection lists and allow more CROSS APPLYs to be reduced
|
|
// to CROSS JOINs.
|
|
node = SqlCrossApplyToCrossJoin.Reduce(node, annotations);
|
|
|
|
// fixup names for aliases, columns, locals, etc..
|
|
SqlNamer namer = new SqlNamer();
|
|
node = namer.AssignNames(node);
|
|
validator.Validate(node);
|
|
|
|
// Convert [N]Text,Image to [N]VarChar(MAX),VarBinary(MAX) where necessary.
|
|
// These new types do not exist on SQL2k, so add annotations.
|
|
LongTypeConverter longTypeConverter = new LongTypeConverter(this.sqlFactory);
|
|
node = longTypeConverter.AddConversions(node, annotations);
|
|
|
|
// final validation
|
|
validator.AddValidator(new ExpectNoMethodCalls());
|
|
validator.AddValidator(new ValidateNoInvalidComparison());
|
|
validator.Validate(node);
|
|
|
|
SqlParameterizer parameterizer = new SqlParameterizer(this.typeProvider, annotations);
|
|
SqlFormatter formatter = new SqlFormatter();
|
|
if (this.mode == ProviderMode.SqlCE ||
|
|
this.mode == ProviderMode.Sql2005 ||
|
|
this.mode == ProviderMode.Sql2008) {
|
|
formatter.ParenthesizeTop = true;
|
|
}
|
|
|
|
SqlBlock block = node as SqlBlock;
|
|
if (block != null && this.mode == ProviderMode.SqlCE) {
|
|
// SQLCE cannot batch multiple statements.
|
|
ReadOnlyCollection<ReadOnlyCollection<SqlParameterInfo>> parameters = parameterizer.ParameterizeBlock(block);
|
|
string[] commands = formatter.FormatBlock(block, false);
|
|
QueryInfo[] queries = new QueryInfo[commands.Length];
|
|
for (int i = 0, n = commands.Length; i < n; i++) {
|
|
queries[i] = new QueryInfo(
|
|
block.Statements[i],
|
|
commands[i],
|
|
parameters[i],
|
|
(i < n - 1) ? ResultShape.Return : resultShape,
|
|
(i < n - 1) ? typeof(int) : resultType
|
|
);
|
|
}
|
|
return queries;
|
|
}
|
|
else {
|
|
// build only one result
|
|
ReadOnlyCollection<SqlParameterInfo> parameters = parameterizer.Parameterize(node);
|
|
string commandText = formatter.Format(node);
|
|
return new QueryInfo[] {
|
|
new QueryInfo(node, commandText, parameters, resultShape, resultType)
|
|
};
|
|
}
|
|
}
|
|
|
|
private SqlSelect GetFinalSelect(SqlNode node) {
|
|
switch (node.NodeType) {
|
|
case SqlNodeType.Select:
|
|
return (SqlSelect)node;
|
|
case SqlNodeType.Block: {
|
|
SqlBlock b = (SqlBlock)node;
|
|
return GetFinalSelect(b.Statements[b.Statements.Count - 1]);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private IObjectReaderFactory GetReaderFactory(SqlNode node, Type elemType) {
|
|
SqlSelect sel = node as SqlSelect;
|
|
SqlExpression projection = null;
|
|
if (sel == null && node.NodeType == SqlNodeType.Block) {
|
|
sel = this.GetFinalSelect(node);
|
|
}
|
|
if (sel != null) {
|
|
projection = sel.Selection;
|
|
}
|
|
else {
|
|
SqlUserQuery suq = node as SqlUserQuery;
|
|
if (suq != null && suq.Projection != null) {
|
|
projection = suq.Projection;
|
|
}
|
|
}
|
|
IObjectReaderFactory factory;
|
|
if (projection != null) {
|
|
factory = this.readerCompiler.Compile(projection, elemType);
|
|
}
|
|
else {
|
|
return this.GetDefaultFactory(services.Model.GetMetaType(elemType));
|
|
}
|
|
return factory;
|
|
}
|
|
|
|
private IObjectReaderFactory GetDefaultFactory(MetaType rowType) {
|
|
if (rowType == null) {
|
|
throw Error.ArgumentNull("rowType");
|
|
}
|
|
SqlNodeAnnotations annotations = new SqlNodeAnnotations();
|
|
Expression tmp = Expression.Constant(null);
|
|
SqlUserQuery suq = new SqlUserQuery(string.Empty, null, null, tmp);
|
|
if (TypeSystem.IsSimpleType(rowType.Type)) {
|
|
// if the element type is a simple type (int, bool, etc.) we create
|
|
// a single column binding
|
|
SqlUserColumn col = new SqlUserColumn(rowType.Type, typeProvider.From(rowType.Type), suq, "", false, suq.SourceExpression);
|
|
suq.Columns.Add(col);
|
|
suq.Projection = col;
|
|
}
|
|
else {
|
|
// ... otherwise we generate a default projection
|
|
SqlUserRow rowExp = new SqlUserRow(rowType.InheritanceRoot, this.typeProvider.GetApplicationType((int)ConverterSpecialTypes.Row), suq, tmp);
|
|
suq.Projection = this.translator.BuildProjection(rowExp, rowType, true, null, tmp);
|
|
}
|
|
Type resultType = TypeSystem.GetSequenceType(rowType.Type);
|
|
QueryInfo[] qis = this.BuildQuery(ResultShape.Sequence, resultType, suq, null, annotations);
|
|
return this.GetReaderFactory(qis[qis.Length - 1].Query, rowType.Type);
|
|
}
|
|
|
|
class CompiledQuery : ICompiledQuery {
|
|
DataLoadOptions originalShape;
|
|
Expression query;
|
|
QueryInfo[] queryInfos;
|
|
IObjectReaderFactory factory;
|
|
ICompiledSubQuery[] subQueries;
|
|
|
|
internal CompiledQuery(SqlProvider provider, Expression query, QueryInfo[] queryInfos, IObjectReaderFactory factory, ICompiledSubQuery[] subQueries) {
|
|
this.originalShape = provider.services.Context.LoadOptions;
|
|
this.query = query;
|
|
this.queryInfos = queryInfos;
|
|
this.factory = factory;
|
|
this.subQueries = subQueries;
|
|
}
|
|
|
|
public IExecuteResult Execute(IProvider provider, object[] arguments) {
|
|
if (provider == null) {
|
|
throw Error.ArgumentNull("provider");
|
|
}
|
|
|
|
SqlProvider sqlProvider = provider as SqlProvider;
|
|
if (sqlProvider == null) {
|
|
throw Error.ArgumentTypeMismatch("provider");
|
|
}
|
|
|
|
// verify shape is compatibile with original.
|
|
if (!AreEquivalentShapes(this.originalShape, sqlProvider.services.Context.LoadOptions)) {
|
|
throw Error.CompiledQueryAgainstMultipleShapesNotSupported();
|
|
}
|
|
|
|
// execute query (only last query produces results)
|
|
return sqlProvider.ExecuteAll(this.query, this.queryInfos, this.factory, arguments, subQueries);
|
|
}
|
|
|
|
private static bool AreEquivalentShapes(DataLoadOptions shape1, DataLoadOptions shape2) {
|
|
if (shape1 == shape2) {
|
|
return true;
|
|
}
|
|
else if (shape1 == null) {
|
|
return shape2.IsEmpty;
|
|
}
|
|
else if (shape2 == null) {
|
|
return shape1.IsEmpty;
|
|
}
|
|
else if (shape1.IsEmpty && shape2.IsEmpty) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
class CompiledSubQuery : ICompiledSubQuery {
|
|
QueryInfo queryInfo;
|
|
IObjectReaderFactory factory;
|
|
ReadOnlyCollection<Me.SqlParameter> parameters;
|
|
ICompiledSubQuery[] subQueries;
|
|
|
|
internal CompiledSubQuery(QueryInfo queryInfo, IObjectReaderFactory factory, ReadOnlyCollection<Me.SqlParameter> parameters, ICompiledSubQuery[] subQueries) {
|
|
this.queryInfo = queryInfo;
|
|
this.factory = factory;
|
|
this.parameters = parameters;
|
|
this.subQueries = subQueries;
|
|
}
|
|
|
|
public IExecuteResult Execute(IProvider provider, object[] parentArgs, object[] userArgs) {
|
|
if (parentArgs == null && !(this.parameters == null || this.parameters.Count == 0)) {
|
|
throw Error.ArgumentNull("arguments");
|
|
}
|
|
|
|
SqlProvider sqlProvider = provider as SqlProvider;
|
|
if (sqlProvider == null) {
|
|
throw Error.ArgumentTypeMismatch("provider");
|
|
}
|
|
|
|
// construct new copy of query info
|
|
List<SqlParameterInfo> spis = new List<SqlParameterInfo>(this.queryInfo.Parameters);
|
|
|
|
// add call arguments
|
|
for (int i = 0, n = this.parameters.Count; i < n; i++) {
|
|
spis.Add(new SqlParameterInfo(this.parameters[i], parentArgs[i]));
|
|
}
|
|
|
|
QueryInfo qi = new QueryInfo(
|
|
this.queryInfo.Query,
|
|
this.queryInfo.CommandText,
|
|
spis.AsReadOnly(),
|
|
this.queryInfo.ResultShape,
|
|
this.queryInfo.ResultType
|
|
);
|
|
|
|
// execute query
|
|
return sqlProvider.Execute(null, qi, this.factory, parentArgs, userArgs, subQueries, null);
|
|
}
|
|
}
|
|
|
|
class ExecuteResult : IExecuteResult, IDisposable {
|
|
DbCommand command;
|
|
ReadOnlyCollection<SqlParameterInfo> parameters;
|
|
IObjectReaderSession session;
|
|
int iReturnParameter = -1;
|
|
object value;
|
|
[SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Microsoft: used in an assert in ReturnValue.set")]
|
|
bool useReturnValue;
|
|
bool isDisposed;
|
|
|
|
internal ExecuteResult(DbCommand command, ReadOnlyCollection<SqlParameterInfo> parameters, IObjectReaderSession session, object value, bool useReturnValue)
|
|
: this(command, parameters, session) {
|
|
this.value = value;
|
|
this.useReturnValue = useReturnValue;
|
|
if (this.command != null && this.parameters != null && useReturnValue) {
|
|
iReturnParameter = GetParameterIndex("@RETURN_VALUE");
|
|
}
|
|
}
|
|
|
|
internal ExecuteResult(DbCommand command, ReadOnlyCollection<SqlParameterInfo> parameters, IObjectReaderSession session) {
|
|
this.command = command;
|
|
this.parameters = parameters;
|
|
this.session = session;
|
|
}
|
|
|
|
internal ExecuteResult(DbCommand command, ReadOnlyCollection<SqlParameterInfo> parameters, IObjectReaderSession session, object value)
|
|
: this(command, parameters, session, value, false) {
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "value", Justification="FxCop Error -- False positive during code analysis")]
|
|
public object ReturnValue {
|
|
get {
|
|
if (this.iReturnParameter >= 0) {
|
|
return this.GetParameterValue(this.iReturnParameter);
|
|
}
|
|
return this.value;
|
|
}
|
|
internal set {
|
|
Debug.Assert(!useReturnValue);
|
|
this.value = value;
|
|
}
|
|
}
|
|
|
|
private int GetParameterIndex(string paramName) {
|
|
int idx = -1;
|
|
for (int i = 0, n = this.parameters.Count; i < n; i++) {
|
|
if (string.Compare(parameters[i].Parameter.Name, paramName, StringComparison.OrdinalIgnoreCase) == 0) {
|
|
idx = i;
|
|
break;
|
|
}
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
internal object GetParameterValue(string paramName) {
|
|
int idx = GetParameterIndex(paramName);
|
|
if (idx >= 0) {
|
|
return GetParameterValue(idx);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public object GetParameterValue(int parameterIndex) {
|
|
if (this.parameters == null || parameterIndex < 0 || parameterIndex > this.parameters.Count) {
|
|
throw Error.ArgumentOutOfRange("parameterIndex");
|
|
}
|
|
|
|
// SQL server requires all results to be read before output parameters are visible
|
|
if (this.session != null && !this.session.IsBuffered) {
|
|
this.session.Buffer();
|
|
}
|
|
|
|
SqlParameterInfo pi = this.parameters[parameterIndex];
|
|
object parameterValue = this.command.Parameters[parameterIndex].Value;
|
|
if (parameterValue == DBNull.Value) parameterValue = null;
|
|
if (parameterValue != null && parameterValue.GetType() != pi.Parameter.ClrType) {
|
|
return DBConvert.ChangeType(parameterValue, pi.Parameter.ClrType);
|
|
}
|
|
|
|
return parameterValue;
|
|
}
|
|
|
|
public void Dispose() {
|
|
if (!this.isDisposed) {
|
|
// Technically, calling GC.SuppressFinalize is not required because the class does not
|
|
// have a finalizer, but it does no harm, protects against the case where a finalizer is added
|
|
// in the future, and prevents an FxCop warning.
|
|
GC.SuppressFinalize(this);
|
|
this.isDisposed = true;
|
|
if (this.session!=null) {
|
|
this.session.Dispose();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class SequenceOfOne<T> : IEnumerable<T>, IEnumerable {
|
|
T[] sequence;
|
|
internal SequenceOfOne(T value) {
|
|
this.sequence = new T[] { value };
|
|
}
|
|
public IEnumerator<T> GetEnumerator() {
|
|
return ((IEnumerable<T>)this.sequence).GetEnumerator();
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator() {
|
|
return this.GetEnumerator();
|
|
}
|
|
}
|
|
|
|
class OneTimeEnumerable<T> : IEnumerable<T>, IEnumerable {
|
|
IEnumerator<T> enumerator;
|
|
|
|
internal OneTimeEnumerable(IEnumerator<T> enumerator) {
|
|
System.Diagnostics.Debug.Assert(enumerator != null);
|
|
this.enumerator = enumerator;
|
|
}
|
|
|
|
public IEnumerator<T> GetEnumerator() {
|
|
if (this.enumerator == null) {
|
|
throw Error.CannotEnumerateResultsMoreThanOnce();
|
|
}
|
|
IEnumerator<T> e = this.enumerator;
|
|
this.enumerator = null;
|
|
return e;
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator() {
|
|
return this.GetEnumerator();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Result type for single rowset returning stored procedures.
|
|
/// </summary>
|
|
class SingleResult<T> : ISingleResult<T>, IDisposable, IListSource {
|
|
private IEnumerable<T> enumerable;
|
|
private ExecuteResult executeResult;
|
|
private DataContext context;
|
|
private IBindingList cachedList;
|
|
|
|
internal SingleResult(IEnumerable<T> enumerable, ExecuteResult executeResult, DataContext context) {
|
|
System.Diagnostics.Debug.Assert(enumerable != null);
|
|
System.Diagnostics.Debug.Assert(executeResult != null);
|
|
this.enumerable = enumerable;
|
|
this.executeResult = executeResult;
|
|
this.context = context;
|
|
}
|
|
|
|
public IEnumerator<T> GetEnumerator() {
|
|
return enumerable.GetEnumerator();
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator() {
|
|
return this.GetEnumerator();
|
|
}
|
|
|
|
public object ReturnValue {
|
|
get {
|
|
return executeResult.GetParameterValue("@RETURN_VALUE");
|
|
}
|
|
}
|
|
|
|
public void Dispose() {
|
|
// Technically, calling GC.SuppressFinalize is not required because the class does not
|
|
// have a finalizer, but it does no harm, protects against the case where a finalizer is added
|
|
// in the future, and prevents an FxCop warning.
|
|
GC.SuppressFinalize(this);
|
|
this.executeResult.Dispose();
|
|
}
|
|
|
|
IList IListSource.GetList() {
|
|
if (this.cachedList == null) {
|
|
this.cachedList = BindingList.Create<T>(this.context, this);
|
|
}
|
|
return this.cachedList;
|
|
}
|
|
|
|
bool IListSource.ContainsListCollection {
|
|
get { return false; }
|
|
}
|
|
}
|
|
|
|
class MultipleResults : IMultipleResults, IDisposable {
|
|
SqlProvider provider;
|
|
MetaFunction function;
|
|
IObjectReaderSession session;
|
|
bool isDisposed;
|
|
private ExecuteResult executeResult;
|
|
|
|
internal MultipleResults(SqlProvider provider, MetaFunction function, IObjectReaderSession session, ExecuteResult executeResult) {
|
|
this.provider = provider;
|
|
this.function = function;
|
|
this.session = session;
|
|
this.executeResult = executeResult;
|
|
}
|
|
|
|
public IEnumerable<T> GetResult<T>() {
|
|
MetaType metaType = null;
|
|
// Check the inheritance hierarchy of each mapped result row type
|
|
// for the function.
|
|
if (this.function != null) {
|
|
foreach (MetaType mt in function.ResultRowTypes) {
|
|
metaType = mt.InheritanceTypes.SingleOrDefault(it => it.Type == typeof(T));
|
|
if (metaType != null) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (metaType == null) {
|
|
metaType = this.provider.services.Model.GetMetaType(typeof(T));
|
|
}
|
|
IObjectReaderFactory factory = this.provider.GetDefaultFactory(metaType);
|
|
IObjectReader objReader = factory.GetNextResult(this.session, false);
|
|
if (objReader == null) {
|
|
this.Dispose();
|
|
return null;
|
|
}
|
|
return new SingleResult<T>(new OneTimeEnumerable<T>((IEnumerator<T>)objReader), this.executeResult, this.provider.services.Context);
|
|
}
|
|
|
|
public void Dispose() {
|
|
if (!this.isDisposed) {
|
|
// Technically, calling GC.SuppressFinalize is not required because the class does not
|
|
// have a finalizer, but it does no harm, protects against the case where a finalizer is added
|
|
// in the future, and prevents an FxCop warning.
|
|
GC.SuppressFinalize(this);
|
|
this.isDisposed = true;
|
|
if (this.executeResult != null) {
|
|
this.executeResult.Dispose();
|
|
}
|
|
else {
|
|
this.session.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
public object ReturnValue {
|
|
get {
|
|
if (this.executeResult != null) {
|
|
return executeResult.GetParameterValue("@RETURN_VALUE");
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|