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; /// /// Used to specify how a submit should behave when one /// or more updates fail due to optimistic concurrency /// conflicts. /// public enum ConflictMode { /// /// Fail immediately when the first change conflict is encountered. /// FailOnFirstConflict, /// /// Only fail after all changes have been attempted. /// ContinueOnConflict } /// /// Used to specify a value synchronization strategy. /// public enum RefreshMode { /// /// Keep the current values. /// KeepCurrentValues, /// /// 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. /// KeepChanges, /// /// All current values are overwritten with current database values, /// regardless of whether they have been changed. /// OverwriteCurrentValues } /// /// 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. /// public class DataContext : IDisposable { CommonDataServices services; IProvider provider; Dictionary 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(); this.InitTables(this); } internal void ClearCache() { CheckDispose(); this.services.ResetServices(); } internal CommonDataServices Services { get { CheckDispose(); return this.services; } } /// /// The connection object used by this DataContext when executing queries and commands. /// public DbConnection Connection { get { CheckDispose(); return this.provider.Connection; } } /// /// The transaction object used by this DataContext when executing queries and commands. /// public DbTransaction Transaction { get { CheckDispose(); return this.provider.Transaction; } set { CheckDispose(); this.provider.Transaction = value; } } /// /// The command timeout to use when executing commands. /// public int CommandTimeout { get { CheckDispose(); return this.provider.CommandTimeout; } set { CheckDispose(); this.provider.CommandTimeout = value; } } /// /// A text writer used by this DataContext to output information such as query and commands /// being executed. /// public TextWriter Log { get { CheckDispose(); return this.provider.Log; } set { CheckDispose(); this.provider.Log = value; } } /// /// 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. /// 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(); } } /// /// True if deferred loading is enabled, false otherwise. With deferred /// loading disabled, association members return default values and are /// not defer loaded. /// 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; } } /// /// The mapping model used to describe the entities /// public MetaModel Mapping { get { CheckDispose(); return this.services.Model; } } /// /// Verify that change tracking is enabled, and throw an exception /// if it is not. /// internal void VerifyTrackingEnabled() { CheckDispose(); if (!ObjectTrackingEnabled) { throw Error.ObjectTrackingRequired(); } } /// /// Verify that submit changes is not occurring /// internal void CheckNotInSubmitChanges() { CheckDispose(); if (this.isInSubmitChanges) { throw Error.CannotPerformOperationDuringSubmitChanges(); } } /// /// Verify that submit changes is occurring /// internal void CheckInSubmitChanges() { CheckDispose(); if (!this.isInSubmitChanges) { throw Error.CannotPerformOperationOutsideSubmitChanges(); } } /// /// Returns the strongly-typed Table object representing a collection of persistent entities. /// Use this collection as the starting point for queries. /// /// The type of the entity objects. In case of a persistent hierarchy /// the entity specified must be the base type of the hierarchy. /// [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "[....]: Generic parameters are required for strong-typing of the return type.")] public Table GetTable() 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)table; } /// /// Returns the weakly-typed ITable object representing a collection of persistent entities. /// Use this collection as the starting point for dynamic/runtime-computed queries. /// /// The type of the entity objects. In case of a persistent hierarchy /// the entity specified must be the base type of the hierarchy. /// 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); } } } } /// /// 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 /// internal IProvider Provider { get { CheckDispose(); return this.provider; } } /// /// Returns true if the database specified by the connection object exists. /// /// public bool DatabaseExists() { CheckDispose(); return this.provider.DatabaseExists(); } /// /// 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. /// public void CreateDatabase() { CheckDispose(); this.provider.CreateDatabase(); } /// /// Deletes the database instance at the location specified by the connection. /// public void DeleteDatabase() { CheckDispose(); this.provider.DeleteDatabase(); } /// /// 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. /// public void SubmitChanges() { CheckDispose(); SubmitChanges(ConflictMode.FailOnFirstConflict); } /// /// 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. /// /// Determines how SubmitChanges handles conflicts. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "[....]: 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; } } /// /// 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. /// /// How the refresh should be performed. /// The object to refresh. The object must be /// the result of a previous query. 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); } /// /// 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. /// /// How the refresh should be performed. /// The objects to refresh. public void Refresh(RefreshMode mode, params object[] entities) { CheckDispose(); // code hygeine requirement if (entities == null){ throw Error.ArgumentNull("entities"); } Refresh(mode, (IEnumerable)entities); } /// /// 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. /// /// How the refresh should be performed. /// The collection of objects to refresh. 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().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(); } /// /// Returns the query text in the database server's native query language /// that would need to be executed to perform the specified query. /// /// The query /// internal string GetQueryText(IQueryable query) { CheckDispose(); if (query == null) { throw Error.ArgumentNull("query"); } return this.provider.GetQueryText(query.Expression); } /// /// Returns an IDbCommand object representing the query in the database server's /// native query language. /// /// /// public DbCommand GetCommand(IQueryable query) { CheckDispose(); if (query == null) { throw Error.ArgumentNull("query"); } return this.provider.GetCommand(query.Expression); } /// /// 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. /// /// internal string GetChangeText() { CheckDispose(); VerifyTrackingEnabled(); return new ChangeProcessor(services, this).GetChangeText(); } /// /// Computes the un-ordered set of objects that have changed /// /// [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(); } /// /// 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. /// /// The command specified in the server's native query language. /// The parameter values to use for the query. /// A single integer return value 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; } /// /// Execute the sequence returning query against the database server. /// The query is specified using the server's native query language, such as SQL. /// /// The element type of the result sequence. /// The query specified in the server's native query language. /// The parameter values to use for the query. /// An IEnumerable sequence of objects. [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "[....]: Generic parameters are required for strong-typing of the return type.")] public IEnumerable ExecuteQuery(string query, params object[] parameters) { CheckDispose(); if (query == null) { throw Error.ArgumentNull("query"); } if (parameters == null) { throw Error.ArgumentNull("parameters"); } return (IEnumerable)this.ExecuteMethodCall(this, ((MethodInfo)MethodInfo.GetCurrentMethod()).MakeGenericMethod(typeof(TResult)), query, parameters).ReturnValue; } /// /// Execute the sequence returning query against the database server. /// The query is specified using the server's native query language, such as SQL. /// /// The element type of the result sequence. /// The query specified in the server's native query language. /// The parameter values to use for the query. /// 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; /// /// Executes the equivalent of the specified method call on the database server. /// /// The instance the method is being called on. /// The reflection MethodInfo for the method to invoke. /// The parameters for the method call. /// The result of the method call. Use this type's ReturnValue property to access the actual return value. 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)); } /// /// Create a query object for the specified method call. /// /// The element type of the query. /// The instance the method is being called on. /// The reflection MethodInfo for the method to invoke. /// The parameters for the method call. /// The returned query object [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "[....]: Generic parameters are required for strong-typing of the return type.")] internal protected IQueryable CreateMethodCallQuery(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).IsAssignableFrom(methodInfo.ReturnType)) { throw Error.ExpectedQueryableArgument("methodInfo", typeof(IQueryable)); } return new DataQuery(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 args = new List(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); } /// /// Execute a dynamic insert /// /// 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); } /// /// Execute a dynamic update /// /// 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(); } } /// /// Execute a dynamic delete /// /// 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(); } } /// /// Translates the data from a DbDataReader into sequence of objects. /// /// The element type of the resulting sequence /// The DbDataReader to translate /// The translated sequence of objects [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "[....]: Generic parameters are required for strong-typing of the return type.")] public IEnumerable Translate(DbDataReader reader) { CheckDispose(); return (IEnumerable)this.Translate(typeof(TResult), reader); } /// /// Translates the data from a DbDataReader into sequence of objects. /// /// The element type of the resulting sequence /// The DbDataReader to translate /// The translated sequence of objects 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); } /// /// Translates the data from a DbDataReader into IMultipleResults. /// /// The DbDataReader to translate /// The translated sequence of objects public IMultipleResults Translate(DbDataReader reader) { CheckDispose(); if (reader == null) { throw Error.ArgumentNull("reader"); } return this.provider.Translate(reader); } /// /// Remove all Include\Subquery LoadOptions settings. /// internal void ResetLoadOptions() { CheckDispose(); this.loadOptions = null; } /// /// The DataLoadOptions used to define prefetch behavior for defer loaded members /// and membership of related collections. /// 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; } } /// /// 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. /// public ChangeConflictCollection ChangeConflicts { get { CheckDispose(); return this.conflicts; } } } /// /// Defines behavior for implementations of IQueryable that allow modifications to the membership of the resulting set. /// /// Type of entities returned from the queryable. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] public interface ITable : IQueryable where TEntity : class { /// /// 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. /// /// Entity object to be added. void InsertOnSubmit(TEntity entity); /// /// 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. /// /// Entity object to be attached. void Attach(TEntity entity); /// /// 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. /// /// Entity object to be removed. /// Throws if the specified object is not in the set. void DeleteOnSubmit(TEntity entity); } /// /// ITable is the common interface for DataContext tables. It can be used as the source /// of a dynamic/runtime-generated query. /// [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification="[....]: Meant to represent a database table which is delayed loaded and doesn't provide collection semantics.")] public interface ITable : IQueryable { /// /// The DataContext containing this Table. /// DataContext Context { get; } /// /// 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. /// /// void InsertOnSubmit(object entity); /// /// 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. /// /// void InsertAllOnSubmit(IEnumerable entities); /// /// 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. /// /// void Attach(object entity); /// /// 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. /// /// /// void Attach(object entity, bool asModified); /// /// 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. /// /// The entity to attach. /// An instance of the same entity type with data members containing /// the original values. void Attach(object entity, object original); /// /// 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. /// /// void AttachAll(IEnumerable entities); /// /// 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. /// /// The collection of entities. /// True if the entities are to be attach as modified. void AttachAll(IEnumerable entities, bool asModified); /// /// 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. /// /// The entity to remove. void DeleteOnSubmit(object entity); /// /// 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. /// /// void DeleteAllOnSubmit(IEnumerable entities); /// /// Returns an instance containing the original state of the entity. /// /// /// object GetOriginalEntityState(object entity); /// /// Returns an array of modified members containing their current and original values /// for the entity specified. /// /// /// ModifiedMemberInfo[] GetModifiedMembers(object entity); /// /// True if the table is read-only. /// bool IsReadOnly { get; } } /// /// 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. /// /// [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification="[....]: Meant to represent a database table which is delayed loaded and doesn't provide collection semantics.")] public sealed class Table : IQueryable, IQueryProvider, IEnumerable, IQueryable, IEnumerable, ITable, IListSource, ITable 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; } /// /// The DataContext containing this Table. /// public DataContext Context { get { return this.context; } } /// /// True if the table is read-only. /// 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 = "[....]: Generic parameters are required for strong-typing of the return type.")] IQueryable IQueryProvider.CreateQuery(Expression expression) { if (expression == null) { throw Error.ArgumentNull("expression"); } if (!typeof(IQueryable).IsAssignableFrom(expression.Type)) { throw Error.ExpectedQueryableArgument("expression", typeof(IEnumerable)); } return new DataQuery(this.context, expression); } object IQueryProvider.Execute(Expression expression) { return this.context.Provider.Execute(expression).ReturnValue; } [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "[....]: Generic parameters are required for strong-typing of the return type.")] TResult IQueryProvider.Execute(Expression expression) { return (TResult)this.context.Provider.Execute(expression).ReturnValue; } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } public IEnumerator GetEnumerator() { return ((IEnumerable)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(this.context, this); } /// /// 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. /// /// 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); } /// /// 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. /// /// public void InsertAllOnSubmit(IEnumerable entities) where TSubEntity : TEntity { if (entities == null) { throw Error.ArgumentNull("entities"); } CheckReadOnly(); context.CheckNotInSubmitChanges(); context.VerifyTrackingEnabled(); List 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 list = entities.Cast().ToList(); ITable itable = this; foreach (object entity in list) { itable.InsertOnSubmit(entity); } } /// /// 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. /// 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; } /// /// 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. /// /// 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); } /// /// 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. /// /// public void DeleteAllOnSubmit(IEnumerable entities) where TSubEntity : TEntity { if (entities == null) { throw Error.ArgumentNull("entities"); } CheckReadOnly(); context.CheckNotInSubmitChanges(); context.VerifyTrackingEnabled(); List 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 list = entities.Cast().ToList(); ITable itable = this; foreach (object entity in list) { itable.DeleteOnSubmit(entity); } } /// /// 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. /// /// 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); } /// /// 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. /// /// /// 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); } /// /// Attaches an entity to the DataContext in either a modified or unmodified state by specifying both the entity /// and its original state. /// /// The entity to attach. /// An instance of the same entity type with data members containing /// the original values. 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); } /// /// 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. /// /// public void AttachAll(IEnumerable 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); } /// /// 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. /// /// The collection of entities. /// True if the entities are to be attach as modified. public void AttachAll(IEnumerable entities, bool asModified) where TSubEntity : TEntity { if (entities == null) { throw Error.ArgumentNull("entities"); } CheckReadOnly(); context.CheckNotInSubmitChanges(); context.VerifyTrackingEnabled(); List 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 list = entities.Cast().ToList(); ITable itable = this; foreach (object entity in list) { itable.Attach(entity, asModified); } } /// /// Returns an instance containing the original state of the entity. /// /// /// 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); } /// /// Returns an array of modified members containing their current and original values /// for the entity specified. /// /// /// 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 inserts; ReadOnlyCollection deletes; ReadOnlyCollection updates; internal ChangeSet( ReadOnlyCollection inserts, ReadOnlyCollection deletes, ReadOnlyCollection updates ) { this.inserts = inserts; this.deletes = deletes; this.updates = updates; } public IList Inserts { get { return this.inserts; } } public IList Deletes { get { return this.deletes; } } public IList 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 = "[....]: 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; } } } }