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