File: DataContext.cs
Project: ndp\fx\src\DLinq\Dlinq\System.Data.Linq.csproj (System.Data.Linq)
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Data.Common;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Transactions;
using System.Xml;
using System.Runtime.CompilerServices;
 
namespace System.Data.Linq {
    using System.Data.Linq.Mapping;
    using System.Data.Linq.Provider;
    using System.Diagnostics.CodeAnalysis;
 
    /// <summary>
    /// Used to specify how a submit should behave when one
    /// or more updates fail due to optimistic concurrency
    /// conflicts.
    /// </summary>
    public enum ConflictMode {
        /// <summary>
        /// Fail immediately when the first change conflict is encountered.
        /// </summary>
        FailOnFirstConflict,
        /// <summary>
        /// Only fail after all changes have been attempted.
        /// </summary>
        ContinueOnConflict
    }
 
    /// <summary>
    /// Used to specify a value synchronization strategy. 
    /// </summary>
    public enum RefreshMode {
        /// <summary>
        /// Keep the current values.
        /// </summary>
        KeepCurrentValues,
        /// <summary>
        /// Current values that have been changed are not modified, but
        /// any unchanged values are updated with the current database
        /// values.  No changes are lost in this merge.
        /// </summary>
        KeepChanges,
        /// <summary>
        /// All current values are overwritten with current database values,
        /// regardless of whether they have been changed.
        /// </summary>
        OverwriteCurrentValues
    }
 
    /// <summary>
    /// The DataContext is the source of all entities mapped over a database connection.
    /// It tracks changes made to all retrieved entities and maintains an 'identity cache' 
    /// that guarantees that entities retrieved more than once are represented using the 
    /// same object instance.
    /// </summary>
    public class DataContext : IDisposable {
        CommonDataServices services;
        IProvider provider;
        Dictionary<MetaTable, ITable> tables;
        bool objectTrackingEnabled = true;
        bool deferredLoadingEnabled = true;
        bool disposed;
        bool isInSubmitChanges;
        DataLoadOptions loadOptions;
        ChangeConflictCollection conflicts;
 
        private DataContext() {
        }
 
        public DataContext(string fileOrServerOrConnection) {
            if (fileOrServerOrConnection == null) {
                throw Error.ArgumentNull("fileOrServerOrConnection");
            }
            this.InitWithDefaultMapping(fileOrServerOrConnection);
        }
 
        public DataContext(string fileOrServerOrConnection, MappingSource mapping) {
            if (fileOrServerOrConnection == null) {
                throw Error.ArgumentNull("fileOrServerOrConnection");
            }
            if (mapping == null) {
                throw Error.ArgumentNull("mapping");
            }
            this.Init(fileOrServerOrConnection, mapping);
        }
 
        public DataContext(IDbConnection connection) {
            if (connection == null) {
                throw Error.ArgumentNull("connection");
            }
            this.InitWithDefaultMapping(connection);
        }
 
        public DataContext(IDbConnection connection, MappingSource mapping) {
            if (connection == null) {
                throw Error.ArgumentNull("connection");
            }
            if (mapping == null) {
                throw Error.ArgumentNull("mapping");
            }
            this.Init(connection, mapping);
        }
 
        internal DataContext(DataContext context) {
            if (context == null) {
                throw Error.ArgumentNull("context");
            }
            this.Init(context.Connection, context.Mapping.MappingSource);
            this.LoadOptions = context.LoadOptions;
            this.Transaction = context.Transaction;
            this.Log = context.Log;
            this.CommandTimeout = context.CommandTimeout;
        }
 
        #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) {
                if (this.provider != null) {
                    this.provider.Dispose();
                    this.provider = null;
                }
                this.services = null;
                this.tables = null;
                this.loadOptions = null;
            }
        }
 
        internal void CheckDispose() {
            if (this.disposed) {
                throw Error.DataContextCannotBeUsedAfterDispose();
            }
        }
        #endregion
 
        private void InitWithDefaultMapping(object connection) {
            this.Init(connection, new AttributeMappingSource());
        }
 
        internal object Clone() {
            CheckDispose();
            return Activator.CreateInstance(this.GetType(), new object[] { this.Connection, this.Mapping.MappingSource });
        }
 
        private void Init(object connection, MappingSource mapping) {
            MetaModel model = mapping.GetModel(this.GetType());
            this.services = new CommonDataServices(this, model);
            this.conflicts = new ChangeConflictCollection();
 
            // determine provider
            Type providerType;
            if (model.ProviderType != null) {
                providerType = model.ProviderType;
            }
            else {
                throw Error.ProviderTypeNull();
            }
 
            if (!typeof(IProvider).IsAssignableFrom(providerType)) {
                throw Error.ProviderDoesNotImplementRequiredInterface(providerType, typeof(IProvider));
            }
 
            this.provider = (IProvider)Activator.CreateInstance(providerType);
            this.provider.Initialize(this.services, connection);
 
            this.tables = new Dictionary<MetaTable, ITable>();
            this.InitTables(this);
        }
 
        internal void ClearCache() {
            CheckDispose();
            this.services.ResetServices();
        }
 
        internal CommonDataServices Services {
            get { 
                CheckDispose();
                return this.services; 
            }
        }
 
        /// <summary>
        /// The connection object used by this DataContext when executing queries and commands.
        /// </summary>
        public DbConnection Connection {
            get {
                CheckDispose();
                return this.provider.Connection;
            }
        }
 
        /// <summary>
        /// The transaction object used by this DataContext when executing queries and commands.
        /// </summary>
        public DbTransaction Transaction {
            get {
                CheckDispose();
                return this.provider.Transaction;
            }
            set {
                CheckDispose();
                this.provider.Transaction = value;
            }
        }
 
        /// <summary>
        /// The command timeout to use when executing commands.
        /// </summary>
        public int CommandTimeout {
            get {
                CheckDispose();
                return this.provider.CommandTimeout;
            }
            set {
                CheckDispose();
                this.provider.CommandTimeout = value;
            }
        }
 
        /// <summary>
        /// A text writer used by this DataContext to output information such as query and commands
        /// being executed.
        /// </summary>
        public TextWriter Log {
            get {
                CheckDispose();
                return this.provider.Log;
            }
            set {
                CheckDispose();
                this.provider.Log = value;
            }
        }
 
        /// <summary>
        /// True if object tracking is enabled, false otherwise.  Object tracking
        /// includes identity caching and change tracking.  If tracking is turned off, 
        /// SubmitChanges and related functionality is disabled.  DeferredLoading is
        /// also disabled when object tracking is disabled.
        /// </summary>
        public bool ObjectTrackingEnabled {
            get {
                CheckDispose();
                return objectTrackingEnabled;
            }
            set {
                CheckDispose();
                if (Services.HasCachedObjects) {
                    throw Error.OptionsCannotBeModifiedAfterQuery();
                }
                objectTrackingEnabled = value;
                if (!objectTrackingEnabled) {
                    deferredLoadingEnabled = false;
                }
                // force reinitialization of cache/tracking objects
                services.ResetServices();
            }
        }
 
        /// <summary>
        /// True if deferred loading is enabled, false otherwise.  With deferred
        /// loading disabled, association members return default values and are 
        /// not defer loaded.
        /// </summary>
        public bool DeferredLoadingEnabled {
            get {
                CheckDispose();
                return deferredLoadingEnabled;
            }
            set {
                CheckDispose();
                if (Services.HasCachedObjects) {
                    throw Error.OptionsCannotBeModifiedAfterQuery();
                }
                // can't have tracking disabled and deferred loading enabled
                if (!ObjectTrackingEnabled && value) {
                    throw Error.DeferredLoadingRequiresObjectTracking();
                }
                deferredLoadingEnabled = value;
            }
        }
 
        /// <summary>
        /// The mapping model used to describe the entities
        /// </summary>
        public MetaModel Mapping {
            get {
                CheckDispose();
                return this.services.Model;
            }
        }
 
        /// <summary>
        /// Verify that change tracking is enabled, and throw an exception
        /// if it is not.
        /// </summary>
        internal void VerifyTrackingEnabled() {
            CheckDispose();
            if (!ObjectTrackingEnabled) {
                throw Error.ObjectTrackingRequired();
            }
        }
 
        /// <summary>
        /// Verify that submit changes is not occurring
        /// </summary>
        internal void CheckNotInSubmitChanges() {
            CheckDispose();
            if (this.isInSubmitChanges) {
                throw Error.CannotPerformOperationDuringSubmitChanges();
            }
        }
 
        /// <summary>
        /// Verify that submit changes is occurring
        /// </summary>
        internal void CheckInSubmitChanges() {
            CheckDispose();
            if (!this.isInSubmitChanges) {
                throw Error.CannotPerformOperationOutsideSubmitChanges();
            }
        }
 
        /// <summary>
        /// Returns the strongly-typed Table object representing a collection of persistent entities.  
        /// Use this collection as the starting point for queries.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity objects. In case of a persistent hierarchy
        /// the entity specified must be the base type of the hierarchy.</typeparam>
        /// <returns></returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Microsoft: Generic parameters are required for strong-typing of the return type.")]
        public Table<TEntity> GetTable<TEntity>() where TEntity : class {
            CheckDispose();
            MetaTable metaTable = this.services.Model.GetTable(typeof(TEntity));
            if (metaTable == null) {
                throw Error.TypeIsNotMarkedAsTable(typeof(TEntity));
            }
            ITable table = this.GetTable(metaTable);
            if (table.ElementType != typeof(TEntity)) {
                throw Error.CouldNotGetTableForSubtype(typeof(TEntity), metaTable.RowType.Type);
            }
            return (Table<TEntity>)table;
        }
       
        /// <summary>
        /// Returns the weakly-typed ITable object representing a collection of persistent entities. 
        /// Use this collection as the starting point for dynamic/runtime-computed queries.
        /// </summary>
        /// <param name="type">The type of the entity objects. In case of a persistent hierarchy
        /// the entity specified must be the base type of the hierarchy.</param>
        /// <returns></returns>
        public ITable GetTable(Type type) {
            CheckDispose();
            if (type == null) {
                throw Error.ArgumentNull("type");
            }
            MetaTable metaTable = this.services.Model.GetTable(type);
            if (metaTable == null) {
                throw Error.TypeIsNotMarkedAsTable(type);
            }
            if (metaTable.RowType.Type != type) {
                throw Error.CouldNotGetTableForSubtype(type, metaTable.RowType.Type);
            }
            return this.GetTable(metaTable);
        }
 
        private ITable GetTable(MetaTable metaTable) {
            System.Diagnostics.Debug.Assert(metaTable != null);
            ITable tb;
            if (!this.tables.TryGetValue(metaTable, out tb)) {
                ValidateTable(metaTable);
                Type tbType = typeof(Table<>).MakeGenericType(metaTable.RowType.Type);
                tb = (ITable)Activator.CreateInstance(tbType, BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic, null, new object[] { this, metaTable }, null);
                this.tables.Add(metaTable, tb);
            }
            return tb;
        }
 
        private static void ValidateTable(MetaTable metaTable) {
            // Associations can only be between entities - verify both that both ends of all
            // associations are entities.
            foreach(MetaAssociation assoc in metaTable.RowType.Associations) {
                if(!assoc.ThisMember.DeclaringType.IsEntity) {
                    throw Error.NonEntityAssociationMapping(assoc.ThisMember.DeclaringType.Type, assoc.ThisMember.Name, assoc.ThisMember.DeclaringType.Type);
                }
                if(!assoc.OtherType.IsEntity) {
                    throw Error.NonEntityAssociationMapping(assoc.ThisMember.DeclaringType.Type, assoc.ThisMember.Name, assoc.OtherType.Type);
                }
            }
        }
 
        private void InitTables(object schema) {
            FieldInfo[] fields = schema.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
            foreach (FieldInfo fi in fields) {
                Type ft = fi.FieldType;
                if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(Table<>)) {
                    ITable tb = (ITable)fi.GetValue(schema);
                    if (tb == null) {
                        Type rowType = ft.GetGenericArguments()[0];
                        tb = this.GetTable(rowType);
                        fi.SetValue(schema, tb);
                    }
                }     
            }
        }
 
        /// <summary>
        /// Internal method that can be accessed by tests to retrieve the provider
        /// The IProvider result can then be cast to the actual provider to call debug methods like
        ///   CheckQueries, QueryCount, EnableCacheLookup
        /// </summary>
        internal IProvider Provider {
            get { 
                CheckDispose();
                return this.provider; 
            }
        }
 
        /// <summary>
        /// Returns true if the database specified by the connection object exists.
        /// </summary>
        /// <returns></returns>
        public bool DatabaseExists() {
            CheckDispose();
            return this.provider.DatabaseExists();
        }
 
        /// <summary>
        /// Creates a new database instance (catalog or file) at the location specified by the connection
        /// using the metadata encoded within the entities or mapping file.
        /// </summary>
        public void CreateDatabase() {
            CheckDispose();
            this.provider.CreateDatabase();
        }
 
        /// <summary>
        /// Deletes the database instance at the location specified by the connection.
        /// </summary>
        public void DeleteDatabase() {
            CheckDispose();
            this.provider.DeleteDatabase();
        }
 
        /// <summary>
        /// Submits one or more commands to the database reflecting the changes made to the retreived entities.
        /// If a transaction is not already specified one will be created for the duration of this operation.
        /// If a change conflict is encountered a ChangeConflictException will be thrown.
        /// </summary>
        public void SubmitChanges() {
            CheckDispose();
            SubmitChanges(ConflictMode.FailOnFirstConflict);
        }
 
        /// <summary>
        /// Submits one or more commands to the database reflecting the changes made to the retreived entities.
        /// If a transaction is not already specified one will be created for the duration of this operation.
        /// If a change conflict is encountered a ChangeConflictException will be thrown.  
        /// You can override this method to implement common conflict resolution behaviors.
        /// </summary>
        /// <param name="failureMode">Determines how SubmitChanges handles conflicts.</param>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Microsoft: In the middle of attempting to rollback a transaction, outer transaction is thrown.")]
        public virtual void SubmitChanges(ConflictMode failureMode) {
            CheckDispose();
            CheckNotInSubmitChanges();
            VerifyTrackingEnabled();
            this.conflicts.Clear();
 
            try {
                this.isInSubmitChanges = true;
 
                if (System.Transactions.Transaction.Current == null && this.provider.Transaction == null) {
                    bool openedConnection = false;
                    DbTransaction transaction = null;
                    try {
                        if (this.provider.Connection.State == ConnectionState.Open) {
                            this.provider.ClearConnection();
                        }
                        if (this.provider.Connection.State == ConnectionState.Closed) {
                            this.provider.Connection.Open();
                            openedConnection = true;
                        }
                        transaction = this.provider.Connection.BeginTransaction(IsolationLevel.ReadCommitted);
                        this.provider.Transaction = transaction;
                        new ChangeProcessor(this.services, this).SubmitChanges(failureMode);
                        this.AcceptChanges();
 
                        // to commit a transaction, there can be no open readers
                        // on the connection.
                        this.provider.ClearConnection();
 
                        transaction.Commit();
                    }
                    catch {
                        if (transaction != null) {
                            transaction.Rollback();
                        }
                        throw;
                    }
                    finally {
                        this.provider.Transaction = null;
                        if (openedConnection) {
                            this.provider.Connection.Close();
                        }
                    }
                }
                else {
                    new ChangeProcessor(services, this).SubmitChanges(failureMode);
                    this.AcceptChanges();
                }
            }
            finally {
                this.isInSubmitChanges = false;
            }
        }
 
        /// <summary>
        /// Refresh the specified object using the mode specified.  If the refresh
        /// cannot be performed (for example if the object no longer exists in the
        /// database) an InvalidOperationException is thrown.
        /// </summary>
        /// <param name="mode">How the refresh should be performed.</param>
        /// <param name="entity">The object to refresh.  The object must be
        /// the result of a previous query.</param>
        public void Refresh(RefreshMode mode, object entity)
        {
            CheckDispose();
            CheckNotInSubmitChanges();
            VerifyTrackingEnabled();
            if (entity == null)
            {
                throw Error.ArgumentNull("entity");
            }
            Array items = Array.CreateInstance(entity.GetType(), 1);
            items.SetValue(entity, 0);
            this.Refresh(mode, items as IEnumerable);
        }
 
        /// <summary>
        /// Refresh a set of objects using the mode specified.  If the refresh
        /// cannot be performed (for example if the object no longer exists in the
        /// database) an InvalidOperationException is thrown.
        /// </summary>
        /// <param name="mode">How the refresh should be performed.</param>
        /// <param name="entities">The objects to refresh.</param>
        public void Refresh(RefreshMode mode, params object[] entities)
        {
            CheckDispose(); // code hygeine requirement
 
            if (entities == null){
                throw Error.ArgumentNull("entities");
            }
 
            Refresh(mode, (IEnumerable)entities);
        }
 
        /// <summary>
        /// Refresh a collection of objects using the mode specified.  If the refresh
        /// cannot be performed (for example if the object no longer exists in the
        /// database) an InvalidOperationException is thrown.
        /// </summary>
        /// <param name="mode">How the refresh should be performed.</param>
        /// <param name="entities">The collection of objects to refresh.</param>
        public void Refresh(RefreshMode mode, IEnumerable entities)
        {
            CheckDispose();
            CheckNotInSubmitChanges();
            VerifyTrackingEnabled();
 
            if (entities == null) {
                throw Error.ArgumentNull("entities");
            }
 
            // if the collection is a query, we need to execute and buffer,
            // since below we will be issuing additional queries and can only
            // have a single reader open.
            var list = entities.Cast<object>().ToList();
 
            // create a fresh context to fetch new state from
            DataContext refreshContext = this.CreateRefreshContext();
 
            foreach (object o in list) {
                // verify that each object in the list is an entity
                MetaType inheritanceRoot = services.Model.GetMetaType(o.GetType()).InheritanceRoot;
                GetTable(inheritanceRoot.Type);
 
                TrackedObject trackedObject = this.services.ChangeTracker.GetTrackedObject(o);
                if (trackedObject == null) {
                    throw Error.UnrecognizedRefreshObject();
                }
 
                if (trackedObject.IsNew) {
                    throw Error.RefreshOfNewObject();
                }
                
                // query to get the current database values
                object[] keyValues = CommonDataServices.GetKeyValues(trackedObject.Type, trackedObject.Original);
                object freshInstance = refreshContext.Services.GetObjectByKey(trackedObject.Type, keyValues);
                if (freshInstance == null) {
                    throw Error.RefreshOfDeletedObject();
                }
 
                // refresh the tracked object using the new values and
                // the mode specified.
                trackedObject.Refresh(mode, freshInstance);
            }
        }
 
        internal DataContext CreateRefreshContext() {
            CheckDispose();
            return new DataContext(this);
        }
 
        private void AcceptChanges() {
            CheckDispose();
            VerifyTrackingEnabled();
            this.services.ChangeTracker.AcceptChanges();
        }
 
        /// <summary>
        /// Returns the query text in the database server's native query language
        /// that would need to be executed to perform the specified query.
        /// </summary>
        /// <param name="query">The query</param>
        /// <returns></returns>
        internal string GetQueryText(IQueryable query) {
            CheckDispose();
            if (query == null) {
                throw Error.ArgumentNull("query");
            }
            return this.provider.GetQueryText(query.Expression);
        }
 
        /// <summary>
        /// Returns an IDbCommand object representing the query in the database server's
        /// native query language.
        /// </summary>
        /// <param name="query"></param>
        /// <returns></returns>
        public DbCommand GetCommand(IQueryable query) {
            CheckDispose();
            if (query == null) {
                throw Error.ArgumentNull("query");
            }
            return this.provider.GetCommand(query.Expression);
        }
 
        /// <summary>
        /// Returns the command text in the database server's native query langauge
        /// that would need to be executed in order to persist the changes made to the
        /// objects back into the database.
        /// </summary>
        /// <returns></returns>
        internal string GetChangeText() {
            CheckDispose();
            VerifyTrackingEnabled();
            return new ChangeProcessor(services, this).GetChangeText();
        }
 
        /// <summary>
        /// Computes the un-ordered set of objects that have changed
        /// </summary>
        /// <returns></returns>
        [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "ChangeSet", Justification="The capitalization was deliberately chosen.")]
        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Non-trivial operations are not suitable for properties.")]
        public ChangeSet GetChangeSet() {
            CheckDispose();
            return new ChangeProcessor(this.services, this).GetChangeSet();
        }
 
        /// <summary>
        /// Execute a command against the database server that does not return a sequence of objects.
        /// The command is specified using the server's native query language, such as SQL.
        /// </summary>
        /// <param name="command">The command specified in the server's native query language.</param>
        /// <param name="parameters">The parameter values to use for the query.</param>
        /// <returns>A single integer return value</returns>
        public int ExecuteCommand(string command, params object[] parameters) {
            CheckDispose();
            if (command == null) {
                throw Error.ArgumentNull("command");
            }
            if (parameters == null) {
                throw Error.ArgumentNull("parameters");
            }
            return (int)this.ExecuteMethodCall(this, (MethodInfo)MethodInfo.GetCurrentMethod(), command, parameters).ReturnValue;
        }
 
        /// <summary>
        /// Execute the sequence returning query against the database server. 
        /// The query is specified using the server's native query language, such as SQL.
        /// </summary>
        /// <typeparam name="TResult">The element type of the result sequence.</typeparam>
        /// <param name="query">The query specified in the server's native query language.</param>
        /// <param name="parameters">The parameter values to use for the query.</param>
        /// <returns>An IEnumerable sequence of objects.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Microsoft: Generic parameters are required for strong-typing of the return type.")]
        public IEnumerable<TResult> ExecuteQuery<TResult>(string query, params object[] parameters) {
            CheckDispose();
            if (query == null) {
                throw Error.ArgumentNull("query");
            }
            if (parameters == null) {
                throw Error.ArgumentNull("parameters");
            }
            return (IEnumerable<TResult>)this.ExecuteMethodCall(this, ((MethodInfo)MethodInfo.GetCurrentMethod()).MakeGenericMethod(typeof(TResult)), query, parameters).ReturnValue;
        }
 
        /// <summary>
        /// Execute the sequence returning query against the database server. 
        /// The query is specified using the server's native query language, such as SQL.
        /// </summary>
        /// <param name="elementType">The element type of the result sequence.</param>
        /// <param name="query">The query specified in the server's native query language.</param>
        /// <param name="parameters">The parameter values to use for the query.</param>
        /// <returns></returns>
        public IEnumerable ExecuteQuery(Type elementType, string query, params object[] parameters) {
            CheckDispose();
            if (elementType == null) {
                throw Error.ArgumentNull("elementType");
            }
            if (query == null) {
                throw Error.ArgumentNull("query");
            }
            if (parameters == null) {
                throw Error.ArgumentNull("parameters");
            }
            if (_miExecuteQuery == null) {
                _miExecuteQuery = typeof(DataContext).GetMethods().Single(m => m.Name == "ExecuteQuery" && m.GetParameters().Length == 2);
            }
            return (IEnumerable)this.ExecuteMethodCall(this, _miExecuteQuery.MakeGenericMethod(elementType), query, parameters).ReturnValue;
        }
        private static MethodInfo _miExecuteQuery;
 
 
        /// <summary>
        /// Executes the equivalent of the specified method call on the database server.
        /// </summary>
        /// <param name="instance">The instance the method is being called on.</param>
        /// <param name="methodInfo">The reflection MethodInfo for the method to invoke.</param>
        /// <param name="parameters">The parameters for the method call.</param>
        /// <returns>The result of the method call. Use this type's ReturnValue property to access the actual return value.</returns>
        internal protected IExecuteResult ExecuteMethodCall(object instance, MethodInfo methodInfo, params object[] parameters) {
            CheckDispose();
            if (instance == null) {
                throw Error.ArgumentNull("instance");
            }
            if (methodInfo == null) {
                throw Error.ArgumentNull("methodInfo");
            }
            if (parameters == null) {
                throw Error.ArgumentNull("parameters");
            }
            return this.provider.Execute(this.GetMethodCall(instance, methodInfo, parameters));
        }
 
        /// <summary>
        /// Create a query object for the specified method call.
        /// </summary>
        /// <typeparam name="TResult">The element type of the query.</typeparam>
        /// <param name="instance">The instance the method is being called on.</param>
        /// <param name="methodInfo">The reflection MethodInfo for the method to invoke.</param>
        /// <param name="parameters">The parameters for the method call.</param>
        /// <returns>The returned query object</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Microsoft: Generic parameters are required for strong-typing of the return type.")]
        internal protected IQueryable<TResult> CreateMethodCallQuery<TResult>(object instance, MethodInfo methodInfo, params object[] parameters) {
            CheckDispose();
            if (instance == null) {
                throw Error.ArgumentNull("instance");
            }
            if (methodInfo == null) {
                throw Error.ArgumentNull("methodInfo");
            }
            if (parameters == null) {
                throw Error.ArgumentNull("parameters");
            }
            if (!typeof(IQueryable<TResult>).IsAssignableFrom(methodInfo.ReturnType)) {
                throw Error.ExpectedQueryableArgument("methodInfo", typeof(IQueryable<TResult>));
            }
            return new DataQuery<TResult>(this, this.GetMethodCall(instance, methodInfo, parameters));
        }
 
        private Expression GetMethodCall(object instance, MethodInfo methodInfo, params object[] parameters) {
            CheckDispose();
            if (parameters.Length > 0) {
                ParameterInfo[] pis = methodInfo.GetParameters();
                List<Expression> args = new List<Expression>(parameters.Length);
                for (int i = 0, n = parameters.Length; i < n; i++) {
                    Type pType = pis[i].ParameterType;
                    if (pType.IsByRef) {
                        pType = pType.GetElementType();
                    }
                    args.Add(Expression.Constant(parameters[i], pType));
                }
                return Expression.Call(Expression.Constant(instance), methodInfo, args);
            }
            return Expression.Call(Expression.Constant(instance), methodInfo);
        }
 
        /// <summary>
        /// Execute a dynamic insert
        /// </summary>
        /// <param name="entity"></param>
        internal protected void ExecuteDynamicInsert(object entity) {
            CheckDispose();
            if (entity == null) {
                throw Error.ArgumentNull("entity");
            }
            this.CheckInSubmitChanges();
            TrackedObject tracked = this.services.ChangeTracker.GetTrackedObject(entity);
            if (tracked == null) {
                throw Error.CannotPerformOperationForUntrackedObject();
            }
            this.services.ChangeDirector.DynamicInsert(tracked);
        }
 
        /// <summary>
        /// Execute a dynamic update
        /// </summary>
        /// <param name="entity"></param>
        internal protected void ExecuteDynamicUpdate(object entity) {
            CheckDispose();
            if (entity == null) {
                throw Error.ArgumentNull("entity");
            }
            this.CheckInSubmitChanges();
            TrackedObject tracked = this.services.ChangeTracker.GetTrackedObject(entity);
            if (tracked == null) {
                throw Error.CannotPerformOperationForUntrackedObject();
            }
            int result = this.services.ChangeDirector.DynamicUpdate(tracked);
            if (result == 0) {
                throw new ChangeConflictException();
            }
        }
 
        /// <summary>
        /// Execute a dynamic delete
        /// </summary>
        /// <param name="entity"></param>
        internal protected void ExecuteDynamicDelete(object entity) {
            CheckDispose();
            if (entity == null) {
                throw Error.ArgumentNull("entity");
            }
            this.CheckInSubmitChanges();
            TrackedObject tracked = this.services.ChangeTracker.GetTrackedObject(entity);
            if (tracked == null) {
                throw Error.CannotPerformOperationForUntrackedObject();
            }
            int result = this.services.ChangeDirector.DynamicDelete(tracked);
            if (result == 0) {
                throw new ChangeConflictException();
            }
        }
 
        /// <summary>
        /// Translates the data from a DbDataReader into sequence of objects.
        /// </summary>
        /// <typeparam name="TResult">The element type of the resulting sequence</typeparam>
        /// <param name="reader">The DbDataReader to translate</param>
        /// <returns>The translated sequence of objects</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Microsoft: Generic parameters are required for strong-typing of the return type.")]
        public IEnumerable<TResult> Translate<TResult>(DbDataReader reader) {
            CheckDispose();
            return (IEnumerable<TResult>)this.Translate(typeof(TResult), reader);
        }
 
        /// <summary>
        /// Translates the data from a DbDataReader into sequence of objects.
        /// </summary>
        /// <param name="elementType">The element type of the resulting sequence</param>
        /// <param name="reader">The DbDataReader to translate</param>
        /// <returns>The translated sequence of objects</returns>
        public IEnumerable Translate(Type elementType, DbDataReader reader) {
            CheckDispose();
            if (elementType == null) {
                throw Error.ArgumentNull("elementType");
            }
            if (reader == null) {
                throw Error.ArgumentNull("reader");
            }
            return this.provider.Translate(elementType, reader);
        }
 
        /// <summary>
        /// Translates the data from a DbDataReader into IMultipleResults.
        /// </summary>
        /// <param name="reader">The DbDataReader to translate</param>
        /// <returns>The translated sequence of objects</returns>
        public IMultipleResults Translate(DbDataReader reader) {
            CheckDispose();
            if (reader == null) {
                throw Error.ArgumentNull("reader");
            }
            return this.provider.Translate(reader);
        }
 
        /// <summary>
        /// Remove all Include\Subquery LoadOptions settings.
        /// </summary>
        internal void ResetLoadOptions() {
            CheckDispose();
            this.loadOptions = null;
        }
 
        /// <summary>
        /// The DataLoadOptions used to define prefetch behavior for defer loaded members
        /// and membership of related collections.
        /// </summary>
        public DataLoadOptions LoadOptions {
            get {
                CheckDispose();
                return this.loadOptions;
            }
            set {
                CheckDispose();
                if (this.services.HasCachedObjects && value != this.loadOptions) {
                    throw Error.LoadOptionsChangeNotAllowedAfterQuery();
                }
                if (value != null) {
                    value.Freeze();
                }
                this.loadOptions = value;
            }
        }
 
        /// <summary>
        /// This list of change conflicts produced by the last call to SubmitChanges.  Use this collection
        /// to resolve conflicts after catching a ChangeConflictException and before calling SubmitChanges again.
        /// </summary>
        public ChangeConflictCollection ChangeConflicts {
            get {
                CheckDispose(); 
                return this.conflicts;
            }
        }
    }
    
    /// <summary>
    /// Defines behavior for implementations of IQueryable that allow modifications to the membership of the resulting set.
    /// </summary>
    /// <typeparam name="TEntity">Type of entities returned from the queryable.</typeparam>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
    public interface ITable<TEntity> : IQueryable<TEntity>
        where TEntity : class
    {
        /// <summary>
        /// Notify the set that an object representing a new entity should be added to the set.
        /// Depending on the implementation, the change to the set may not be visible in an enumeration of the set 
        /// until changes to that set have been persisted in some manner.
        /// </summary>
        /// <param name="entity">Entity object to be added.</param>
        void InsertOnSubmit(TEntity entity);
 
        /// <summary>
        /// Notify the set that an object representing a new entity should be added to the set.
        /// Depending on the implementation, the change to the set may not be visible in an enumeration of the set 
        /// until changes to that set have been persisted in some manner.
        /// </summary>
        /// <param name="entity">Entity object to be attached.</param>
        void Attach(TEntity entity);
 
        /// <summary>
        /// Notify the set that an object representing an entity should be removed from the set.
        /// Depending on the implementation, the change to the set may not be visible in an enumeration of the set 
        /// until changes to that set have been persisted in some manner.
        /// </summary>
        /// <param name="entity">Entity object to be removed.</param>
        /// <exception cref="InvalidOperationException">Throws if the specified object is not in the set.</exception>
        void DeleteOnSubmit(TEntity entity);
    }
 
    /// <summary>
    /// ITable is the common interface for DataContext tables. It can be used as the source
    /// of a dynamic/runtime-generated query.
    /// </summary>
    [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification="Microsoft: Meant to represent a database table which is delayed loaded and doesn't provide collection semantics.")]
    public interface ITable : IQueryable {
        /// <summary>
        /// The DataContext containing this Table.
        /// </summary>
        DataContext Context { get; }
        /// <summary>
        /// Adds an entity in a 'pending insert' state to this table.  The added entity will not be observed
        /// in query results from this table until after SubmitChanges has been called. Any untracked
        /// objects referenced directly or transitively by the entity will also be inserted.
        /// </summary>
        /// <param name="entity"></param>
        void InsertOnSubmit(object entity);
        /// <summary>
        /// Adds all entities of a collection to the DataContext in a 'pending insert' state.
        /// The added entities will not be observed in query results until after SubmitChanges() 
        /// has been called. Any untracked objects referenced directly or transitively by the
        /// the inserted entities will also be inserted.
        /// </summary>
        /// <param name="entities"></param>
        void InsertAllOnSubmit(IEnumerable entities);
        /// <summary>
        /// Attaches an entity to the DataContext in an unmodified state, similiar to as if it had been 
        /// retrieved via a query. Other entities accessible from this entity are attached as unmodified 
        /// but may subsequently be transitioned to other states by performing table operations on them
        /// individually.
        /// </summary>
        /// <param name="entity"></param>
        void Attach(object entity);
        /// <summary>
        /// Attaches an entity to the DataContext in either a modified or unmodified state.
        /// If attaching as modified, the entity must either declare a version member or must 
        /// not participate in update conflict checking. Other entities accessible from this 
        /// entity are attached as unmodified but may subsequently be transitioned to other 
        /// states by performing table operations on them individually.
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="asModified"></param>
        void Attach(object entity, bool asModified);
        /// <summary>
        /// Attaches an entity to the DataContext in either a modified or unmodified state by specifying both the entity
        /// and its original state. Other entities accessible from this 
        /// entity are attached as unmodified but may subsequently be transitioned to other 
        /// states by performing table operations on them individually.
        /// </summary>
        /// <param name="entity">The entity to attach.</param>
        /// <param name="original">An instance of the same entity type with data members containing
        /// the original values.</param>
        void Attach(object entity, object original);
        /// <summary>
        /// Attaches all entities of a collection to the DataContext in an unmodified state, 
        /// similiar to as if each had been retrieved via a query. Other entities accessible from these 
        /// entities are attached as unmodified but may subsequently be transitioned to other 
        /// states by performing table operations on them individually.
        /// </summary>
        /// <param name="entities"></param>
        void AttachAll(IEnumerable entities);
        /// <summary>
        /// Attaches all entities of a collection to the DataContext in either a modified or unmodified state.
        /// If attaching as modified, the entity must either declare a version member or must not participate in update conflict checking.
        /// Other entities accessible from these 
        /// entities are attached as unmodified but may subsequently be transitioned to other 
        /// states by performing table operations on them individually.
        /// </summary>
        /// <param name="entities">The collection of entities.</param>
        /// <param name="asModified">True if the entities are to be attach as modified.</param>
        void AttachAll(IEnumerable entities, bool asModified);
        /// <summary>
        /// Puts an entity from this table into a 'pending delete' state.  The removed entity will not be observed
        /// missing from query results until after SubmitChanges() has been called.
        /// </summary>
        /// <param name="entity">The entity to remove.</param>
        void DeleteOnSubmit(object entity);
        /// <summary>
        /// Puts all entities from the collection 'entities' into a 'pending delete' state.  The removed entities will
        /// not be observed missing from the query results until after SubmitChanges() is called.
        /// </summary>
        /// <param name="entities"></param>
        void DeleteAllOnSubmit(IEnumerable entities);
        /// <summary>
        /// Returns an instance containing the original state of the entity.
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        object GetOriginalEntityState(object entity);
        /// <summary>
        /// Returns an array of modified members containing their current and original values
        /// for the entity specified.
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        ModifiedMemberInfo[] GetModifiedMembers(object entity);
        /// <summary>
        /// True if the table is read-only.
        /// </summary>
        bool IsReadOnly { get; }
    }
 
    /// <summary>
    /// Table is a collection of persistent entities. It always contains the set of entities currently 
    /// persisted in the database. Use it as a source of queries and to add/insert and remove/delete entities.
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification="Microsoft: Meant to represent a database table which is delayed loaded and doesn't provide collection semantics.")]
    public sealed class Table<TEntity> : IQueryable<TEntity>, IQueryProvider, IEnumerable<TEntity>, IQueryable, IEnumerable, ITable, IListSource, ITable<TEntity> 
        where TEntity : class {
        DataContext context;
        MetaTable metaTable;
 
        internal Table(DataContext context, MetaTable metaTable) {
            System.Diagnostics.Debug.Assert(metaTable != null);
            this.context = context;
            this.metaTable = metaTable;
        }
 
        /// <summary>
        /// The DataContext containing this Table.
        /// </summary>
        public DataContext Context {
            get { return this.context; }
        }
 
        /// <summary>
        /// True if the table is read-only.
        /// </summary>
        public bool IsReadOnly {
            get { return !metaTable.RowType.IsEntity; }
        }
 
        Expression IQueryable.Expression {
            get { return Expression.Constant(this); }
        }
 
        Type IQueryable.ElementType {
            get { return typeof(TEntity); }
        }
 
        IQueryProvider IQueryable.Provider{
            get{
                return (IQueryProvider)this;
            }
        }
 
        [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
        IQueryable IQueryProvider.CreateQuery(Expression expression) {
            if (expression == null) {
                throw Error.ArgumentNull("expression");
            }
            Type eType = System.Data.Linq.SqlClient.TypeSystem.GetElementType(expression.Type);
            Type qType = typeof(IQueryable<>).MakeGenericType(eType);
            if (!qType.IsAssignableFrom(expression.Type)) {
                throw Error.ExpectedQueryableArgument("expression", qType);
            }
            Type dqType = typeof(DataQuery<>).MakeGenericType(eType);
            return (IQueryable)Activator.CreateInstance(dqType, new object[] { this.context, expression });
        }
 
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Microsoft: Generic parameters are required for strong-typing of the return type.")]
        IQueryable<TResult> IQueryProvider.CreateQuery<TResult>(Expression expression) {
            if (expression == null) {
                throw Error.ArgumentNull("expression");
            }
            if (!typeof(IQueryable<TResult>).IsAssignableFrom(expression.Type)) {
                throw Error.ExpectedQueryableArgument("expression", typeof(IEnumerable<TResult>));
            }
            return new DataQuery<TResult>(this.context, expression);
        }
 
        object IQueryProvider.Execute(Expression expression) {
            return this.context.Provider.Execute(expression).ReturnValue;
        }
 
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Microsoft: Generic parameters are required for strong-typing of the return type.")]
        TResult IQueryProvider.Execute<TResult>(Expression expression) {
            return (TResult)this.context.Provider.Execute(expression).ReturnValue;
        }
 
        IEnumerator IEnumerable.GetEnumerator() {
            return this.GetEnumerator();
        }
 
        IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator() {
            return this.GetEnumerator();
        }
 
        public IEnumerator<TEntity> GetEnumerator() {
            return ((IEnumerable<TEntity>)this.context.Provider.Execute(Expression.Constant(this)).ReturnValue).GetEnumerator();
        }
 
        bool IListSource.ContainsListCollection {
            get { return false; }
        }
 
        private IBindingList cachedList;
 
        IList IListSource.GetList() {
            if (cachedList == null) {
                cachedList = GetNewBindingList();
            }
            return cachedList;
        }
 
        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification="Method doesn't represent a property of the type.")]
        public IBindingList GetNewBindingList() {
            return BindingList.Create<TEntity>(this.context, this);
        }
 
        /// <summary>
        /// Adds an entity in a 'pending insert' state to this table.  The added entity will not be observed
        /// in query results from this table until after SubmitChanges() has been called.  Any untracked
        /// objects referenced directly or transitively by the entity will also be inserted.
        /// </summary>
        /// <param name="entity"></param>
        public void InsertOnSubmit(TEntity entity) {
            if (entity == null) {
                throw Error.ArgumentNull("entity");
            }
            CheckReadOnly();
            context.CheckNotInSubmitChanges();
            context.VerifyTrackingEnabled();
            MetaType type = this.metaTable.RowType.GetInheritanceType(entity.GetType());
            if (!IsTrackableType(type)) {
                throw Error.TypeCouldNotBeAdded(type.Type);
            }
            TrackedObject tracked = this.context.Services.ChangeTracker.GetTrackedObject(entity);
            if (tracked == null) {
                tracked = this.context.Services.ChangeTracker.Track(entity);
                tracked.ConvertToNew();
            } else if (tracked.IsWeaklyTracked) {
                tracked.ConvertToNew();
            } else if (tracked.IsDeleted) {
                tracked.ConvertToPossiblyModified();
            } else if (tracked.IsRemoved) {
                tracked.ConvertToNew();
            } else if (!tracked.IsNew) {
                throw Error.CantAddAlreadyExistingItem();
            }
        }
 
        void ITable.InsertOnSubmit(object entity) {
            if (entity == null) {
                throw Error.ArgumentNull("entity");
            }
            TEntity tEntity = entity as TEntity;
            if (tEntity == null) {
                throw Error.EntityIsTheWrongType();
            }
            this.InsertOnSubmit(tEntity);
        }
 
        /// <summary>
        /// Adds all entities of a collection to the DataContext in a 'pending insert' state.
        /// The added entities will not be observed in query results until after SubmitChanges() 
        /// has been called.
        /// </summary>
        /// <param name="entities"></param>
        public void InsertAllOnSubmit<TSubEntity>(IEnumerable<TSubEntity> entities) where TSubEntity : TEntity {
            if (entities == null) {
                throw Error.ArgumentNull("entities");
            }
            CheckReadOnly();
            context.CheckNotInSubmitChanges();
            context.VerifyTrackingEnabled();
            List<TSubEntity> list = entities.ToList();
            foreach (TEntity entity in list) {
                this.InsertOnSubmit(entity);
            }
        }
 
        void ITable.InsertAllOnSubmit(IEnumerable entities) {
            if (entities == null) {
                throw Error.ArgumentNull("entities");
            }
            CheckReadOnly();
            context.CheckNotInSubmitChanges();
            context.VerifyTrackingEnabled();
            List<object> list = entities.Cast<object>().ToList();
            ITable itable = this;
            foreach (object entity in list) {
                itable.InsertOnSubmit(entity);
            }
        }
 
        /// <summary>
        /// Returns true if this specific type is mapped into the database.
        /// For example, an abstract type can't be present because it can not be instantiated.
        /// </summary>
        private static bool IsTrackableType(MetaType type) {
            if (type == null) {
                return false;
            }
            if (!type.CanInstantiate) {
                return false;
            }
            if (type.HasInheritance && !type.HasInheritanceCode) {
                return false;
            }
            return true;
        }
 
        /// <summary>
        /// Puts an entity from this table into a 'pending delete' state.  The removed entity will not be observed
        /// missing from query results until after SubmitChanges() has been called.
        /// </summary>
        /// <param name="item"></param>
        public void DeleteOnSubmit(TEntity entity) {
            if (entity == null) {
                throw Error.ArgumentNull("entity");
            }
            CheckReadOnly();
            context.CheckNotInSubmitChanges();
            context.VerifyTrackingEnabled();
            TrackedObject tracked = this.context.Services.ChangeTracker.GetTrackedObject(entity);
            if (tracked != null) {
                if (tracked.IsNew) {
                    tracked.ConvertToRemoved();
                }
                else if (tracked.IsPossiblyModified || tracked.IsModified) {
                    tracked.ConvertToDeleted();
                }
            }
            else {
                throw Error.CannotRemoveUnattachedEntity();
            }
        }
 
        void ITable.DeleteOnSubmit(object entity) {
            if (entity == null) {
                throw Error.ArgumentNull("entity");
            }
            TEntity tEntity = entity as TEntity;
            if (tEntity == null) {
                throw Error.EntityIsTheWrongType();
            }
            this.DeleteOnSubmit(tEntity);
        }
 
        /// <summary>
        /// Puts all entities from the collection 'entities' into a 'pending delete' state.  The removed entities will
        /// not be observed missing from the query results until after SubmitChanges() is called.
        /// </summary>
        /// <param name="entities"></param>
        public void DeleteAllOnSubmit<TSubEntity>(IEnumerable<TSubEntity> entities) where TSubEntity : TEntity {
            if (entities == null) {
                throw Error.ArgumentNull("entities");
            }
            CheckReadOnly();
            context.CheckNotInSubmitChanges();
            context.VerifyTrackingEnabled();
            List<TSubEntity> list = entities.ToList();
            foreach (TEntity entity in list) {
                this.DeleteOnSubmit(entity);
            }
        }
 
        void ITable.DeleteAllOnSubmit(IEnumerable entities) {
            if (entities == null) {
                throw Error.ArgumentNull("entities");
            }
            CheckReadOnly();
            context.CheckNotInSubmitChanges();
            context.VerifyTrackingEnabled();
            List<object> list = entities.Cast<object>().ToList();
            ITable itable = this;
            foreach (object entity in list) {
                itable.DeleteOnSubmit(entity);
            }
        }
 
        /// <summary>
        /// Attaches an entity to the DataContext in an unmodified state, similiar to as if it had been 
        /// retrieved via a query. Deferred loading is not enabled. Other entities accessible from this
        /// entity are not automatically attached.
        /// </summary>
        /// <param name="entity"></param>
        public void Attach(TEntity entity) {
            if (entity == null) {
                throw Error.ArgumentNull("entity");
            }
            this.Attach(entity, false);
        }
 
        void ITable.Attach(object entity) {
            if (entity == null) {
                throw Error.ArgumentNull("entity");
            }
            TEntity tEntity = entity as TEntity;
            if (tEntity == null) {
                throw Error.EntityIsTheWrongType();
            }
            this.Attach(tEntity, false);
        }
 
        /// <summary>
        /// Attaches an entity to the DataContext in either a modified or unmodified state.
        /// If attaching as modified, the entity must either declare a version member or must not participate in update conflict checking.
        /// Deferred loading is not enabled. Other entities accessible from this entity are not automatically attached.
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="asModified"></param>
        public void Attach(TEntity entity, bool asModified) {
            if (entity == null) {
                throw Error.ArgumentNull("entity");
            }
            CheckReadOnly();
            context.CheckNotInSubmitChanges();
            context.VerifyTrackingEnabled();
            MetaType type = this.metaTable.RowType.GetInheritanceType(entity.GetType());
            if (!IsTrackableType(type)) {
                throw Error.TypeCouldNotBeTracked(type.Type);
            }
            if (asModified) {
                bool canAttach = type.VersionMember != null || !type.HasUpdateCheck;
                if (!canAttach) {
                    throw Error.CannotAttachAsModifiedWithoutOriginalState();
                }
            }
            TrackedObject tracked = this.Context.Services.ChangeTracker.GetTrackedObject(entity);
            if (tracked == null || tracked.IsWeaklyTracked) {
                if (tracked == null) {
                    tracked = this.context.Services.ChangeTracker.Track(entity, true);
                }
                if (asModified) {
                    tracked.ConvertToModified();
                } else {
                    tracked.ConvertToUnmodified();
                }
                if (this.Context.Services.InsertLookupCachedObject(type, entity) != entity) {
                    throw new DuplicateKeyException(entity, Strings.CantAddAlreadyExistingKey);
                }
                tracked.InitializeDeferredLoaders();
            }
            else {
                throw Error.CannotAttachAlreadyExistingEntity();
            }
        }
 
        void ITable.Attach(object entity, bool asModified) {
            if (entity == null) {
                throw Error.ArgumentNull("entity");
            }
            TEntity tEntity = entity as TEntity;
            if (tEntity == null) {
                throw Error.EntityIsTheWrongType();
            }
            this.Attach(tEntity, asModified);
        }
 
        /// <summary>
        /// Attaches an entity to the DataContext in either a modified or unmodified state by specifying both the entity
        /// and its original state.
        /// </summary>
        /// <param name="entity">The entity to attach.</param>
        /// <param name="original">An instance of the same entity type with data members containing
        /// the original values.</param>
        public void Attach(TEntity entity, TEntity original) {
            if (entity == null) {
                throw Error.ArgumentNull("entity");
            }
            if (original == null) {
                throw Error.ArgumentNull("original");
            }
            if (entity.GetType() != original.GetType()) {
                throw Error.OriginalEntityIsWrongType();
            }
            CheckReadOnly();
            context.CheckNotInSubmitChanges();
            context.VerifyTrackingEnabled();
            MetaType type = this.metaTable.RowType.GetInheritanceType(entity.GetType());
            if (!IsTrackableType(type)) {
                throw Error.TypeCouldNotBeTracked(type.Type);
            }
            TrackedObject tracked = this.context.Services.ChangeTracker.GetTrackedObject(entity);
            if (tracked == null || tracked.IsWeaklyTracked) {
                if (tracked == null) {
                    tracked = this.context.Services.ChangeTracker.Track(entity, true);
                }
                tracked.ConvertToPossiblyModified(original);
                if (this.Context.Services.InsertLookupCachedObject(type, entity) != entity) {
                    throw new DuplicateKeyException(entity, Strings.CantAddAlreadyExistingKey);
                }
                tracked.InitializeDeferredLoaders();
            }
            else {
                throw Error.CannotAttachAlreadyExistingEntity();
            }
        }
 
        void ITable.Attach(object entity, object original) {
            if (entity == null) {
                throw Error.ArgumentNull("entity");
            }
            if (original == null) {
                throw Error.ArgumentNull("original");
            }
            CheckReadOnly();
            context.CheckNotInSubmitChanges();
            context.VerifyTrackingEnabled();
            TEntity tEntity = entity as TEntity;
            if (tEntity == null) {
                throw Error.EntityIsTheWrongType();
            }
            if (entity.GetType() != original.GetType()) {
                throw Error.OriginalEntityIsWrongType();
            }
            this.Attach(tEntity, (TEntity)original);
        }
 
        /// <summary>
        /// Attaches all entities of a collection to the DataContext in an unmodified state, 
        /// similiar to as if each had been retrieved via a query. Deferred loading is not enabled. 
        /// Other entities accessible from these entities are not automatically attached.
        /// </summary>
        /// <param name="entities"></param>
        public void AttachAll<TSubEntity>(IEnumerable<TSubEntity> entities) where TSubEntity : TEntity {
            if (entities == null) {
                throw Error.ArgumentNull("entities");
            }
            this.AttachAll(entities, false);
        }
 
        void ITable.AttachAll(IEnumerable entities) {
            if (entities == null) {
                throw Error.ArgumentNull("entities");
            }
            ((ITable)this).AttachAll(entities, false);
        }
 
        /// <summary>
        /// Attaches all entities of a collection to the DataContext in either a modified or unmodified state.
        /// If attaching as modified, the entity must either declare a version member or must not participate in update conflict checking.
        /// Deferred loading is not enabled.  Other entities accessible from these entities are not automatically attached.
        /// </summary>
        /// <param name="entities">The collection of entities.</param>
        /// <param name="asModified">True if the entities are to be attach as modified.</param>
        public void AttachAll<TSubEntity>(IEnumerable<TSubEntity> entities, bool asModified) where TSubEntity : TEntity {
            if (entities == null) {
                throw Error.ArgumentNull("entities");
            }
            CheckReadOnly();
            context.CheckNotInSubmitChanges();
            context.VerifyTrackingEnabled();
            List<TSubEntity> list = entities.ToList();
            foreach (TEntity entity in list) {
                this.Attach(entity, asModified);
            }
        }
 
        void ITable.AttachAll(IEnumerable entities, bool asModified) {
            if (entities == null) {
                throw Error.ArgumentNull("entities");
            }
            CheckReadOnly();
            context.CheckNotInSubmitChanges();
            context.VerifyTrackingEnabled();
            List<object> list = entities.Cast<object>().ToList();
            ITable itable = this;
            foreach (object entity in list) {
                itable.Attach(entity, asModified);
            }
        }
 
        /// <summary>
        /// Returns an instance containing the original state of the entity.
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        public TEntity GetOriginalEntityState(TEntity entity) {
            if (entity == null) {
                throw Error.ArgumentNull("entity");
            }
            MetaType type = this.Context.Mapping.GetMetaType(entity.GetType());
            if (type == null || !type.IsEntity) {
                throw Error.EntityIsTheWrongType();
            }
            TrackedObject tracked = this.Context.Services.ChangeTracker.GetTrackedObject(entity);
            if (tracked != null) {
                if (tracked.Original != null) {
                    return (TEntity) tracked.CreateDataCopy(tracked.Original);
                }
                else {
                    return (TEntity) tracked.CreateDataCopy(tracked.Current);
                }
            }
            return null;
        }
 
        object ITable.GetOriginalEntityState(object entity) {
            if (entity == null) {
                throw Error.ArgumentNull("entity");
            }
            TEntity tEntity = entity as TEntity;
            if (tEntity == null) {
                throw Error.EntityIsTheWrongType();
            }
            return this.GetOriginalEntityState(tEntity);
        }
 
        /// <summary>
        /// Returns an array of modified members containing their current and original values
        /// for the entity specified.
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        public ModifiedMemberInfo[] GetModifiedMembers(TEntity entity) {
            if (entity == null) {
                throw Error.ArgumentNull("entity");
            }
            MetaType type = this.Context.Mapping.GetMetaType(entity.GetType());
            if (type == null || !type.IsEntity) {
                throw Error.EntityIsTheWrongType();
            }
            TrackedObject tracked = this.Context.Services.ChangeTracker.GetTrackedObject(entity);
            if (tracked != null) {
                return tracked.GetModifiedMembers().ToArray();
            }
            return new ModifiedMemberInfo[] { };
        }
 
        ModifiedMemberInfo[] ITable.GetModifiedMembers(object entity) {
            if (entity == null) {
                throw Error.ArgumentNull("entity");
            }
            TEntity tEntity = entity as TEntity;
            if (tEntity == null) {
                throw Error.EntityIsTheWrongType();
            }
            return this.GetModifiedMembers(tEntity);
        }
 
        private void CheckReadOnly() {
            if (this.IsReadOnly) {
                throw Error.CannotPerformCUDOnReadOnlyTable(ToString());
            }
        }
 
        public override string ToString() {
            return "Table(" + typeof(TEntity).Name + ")";
        }
    }
 
    [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "ChangeSet", Justification="The capitalization was deliberately chosen.")]
    public sealed class ChangeSet {
        ReadOnlyCollection<object> inserts;
        ReadOnlyCollection<object> deletes;
        ReadOnlyCollection<object> updates;
 
        internal ChangeSet(
            ReadOnlyCollection<object> inserts,
            ReadOnlyCollection<object> deletes,
            ReadOnlyCollection<object> updates
            ) {
            this.inserts = inserts;
            this.deletes = deletes;
            this.updates = updates;
        }
 
        public IList<object> Inserts {
            get { return this.inserts; }
        }
 
        public IList<object> Deletes {
            get { return this.deletes; }
        }
 
        public IList<object> Updates {
            get { return this.updates; }
        }
 
        public override string ToString() {
            return "{" +
                string.Format(
                    Globalization.CultureInfo.InvariantCulture,
                    "Inserts: {0}, Deletes: {1}, Updates: {2}",
                    this.Inserts.Count,
                    this.Deletes.Count,
                    this.Updates.Count
                    ) + "}";
        }
    }
 
    [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Microsoft: Types are never compared to each other.  When comparisons happen it is against the entities that are represented by these constructs.")]
    public struct ModifiedMemberInfo {
        MemberInfo member;
        object current;
        object original;
 
        internal ModifiedMemberInfo(MemberInfo member, object current, object original) {
            this.member = member;
            this.current = current;
            this.original = original;
        }
 
        public MemberInfo Member {
            get { return this.member; }
        }
 
        public object CurrentValue {
            get { return this.current; }
        }
 
        public object OriginalValue {
            get { return this.original; }
        }
    }
}