2016-08-03 10:59:49 +00:00
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>
2017-08-21 15:34:15 +00:00
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Microsoft: Generic parameters are required for strong-typing of the return type.")]
2016-08-03 10:59:49 +00:00
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>
2017-08-21 15:34:15 +00:00
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Microsoft: In the middle of attempting to rollback a transaction, outer transaction is thrown.")]
2016-08-03 10:59:49 +00:00
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>
2017-08-21 15:34:15 +00:00
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Microsoft: Generic parameters are required for strong-typing of the return type.")]
2016-08-03 10:59:49 +00:00
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>
2017-08-21 15:34:15 +00:00
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Microsoft: Generic parameters are required for strong-typing of the return type.")]
2016-08-03 10:59:49 +00:00
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>
2017-08-21 15:34:15 +00:00
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Microsoft: Generic parameters are required for strong-typing of the return type.")]
2016-08-03 10:59:49 +00:00
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>
2017-08-21 15:34:15 +00:00
[SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification="Microsoft: Meant to represent a database table which is delayed loaded and doesn't provide collection semantics.")]
2016-08-03 10:59:49 +00:00
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>
2017-08-21 15:34:15 +00:00
[SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification="Microsoft: Meant to represent a database table which is delayed loaded and doesn't provide collection semantics.")]
2016-08-03 10:59:49 +00:00
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 } ) ;
}
2017-08-21 15:34:15 +00:00
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Microsoft: Generic parameters are required for strong-typing of the return type.")]
2016-08-03 10:59:49 +00:00
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 ;
}
2017-08-21 15:34:15 +00:00
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Microsoft: Generic parameters are required for strong-typing of the return type.")]
2016-08-03 10:59:49 +00:00
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
) + "}" ;
}
}
2017-08-21 15:34:15 +00:00
[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.")]
2016-08-03 10:59:49 +00:00
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 ; }
}
}
}