using EFCache; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data.Entity; using System.Data.Entity.Core.Objects; using System.Data.Entity.Infrastructure; using System.Data.SqlClient; using System.Data.SQLite; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading; using System.Threading.Tasks; using Tango.BL.Entities; using Tango.Core; using Tango.Settings; namespace Tango.BL { //[DbConfigurationType(typeof(ObservablesContextConfiguration))] public partial class ObservablesContext { private List _pending_notifications = new List(); private ObservablesContextAdapter _adapter; private static DataSource _override_datasource; private DataSource _dataSource; private static ConcurrentList _open_contexts; /// /// Gets a value indicating whether this instance is disposed. /// public bool IsDisposed { get; private set; } /// /// Initializes the class. /// static ObservablesContext() { _open_contexts = new ConcurrentList(); } /// /// Initializes a new instance of the class. /// public ObservablesContext() { _open_contexts.Add(this); } /// /// Initializes a new instance of the class. /// /// The server file path. /// if set to true will try to connect to an .mdf file. public ObservablesContext(DataSource dataSource) : base(dataSource.ToConnection(), true) { _open_contexts.Add(this); _dataSource = dataSource; Database.SetInitializer(null); Configuration.LazyLoadingEnabled = false; _adapter = new ObservablesContextAdapter(this); } /// /// Creates a default remote database context by the address specified in . /// /// public static ObservablesContext CreateDefault() { return new ObservablesContext(_override_datasource != null ? _override_datasource : SettingsManager.Default.GetOrCreate().DataSource); } /// /// Creates a default remote database context. /// /// public static ObservablesContext CreateDefault(DataSource dataSource) { return new ObservablesContext(dataSource); } /// /// Creates a default remote database context. /// /// public static ObservablesContext CreateDefault(String address, String catalog, DataSourceType type) { return new ObservablesContext(new DataSource() { Address = address, Catalog = catalog, IntegratedSecurity = true, Type = type }); } /// /// Creates a default remote database context. /// /// public static ObservablesContext CreateDefault(String address, DataSourceType type) { return CreateDefault(address, "Tango", type); } /// /// Creates a default remote database context. /// /// public static ObservablesContext CreateDefault(String address) { return CreateDefault(address, "Tango", DataSourceType.SQLServer); } /// /// Gets the inner object context. /// /// private ObjectContext GetObjectContext() { return ((IObjectContextAdapter)this).ObjectContext; } /// /// Saves all changes made in this context to the underlying database. /// /// /// The number of objects written to the underlying database. /// public override int SaveChanges() { foreach (var entity in ChangeTracker.Entries().Where(x => x.State == EntityState.Added || x.State == EntityState.Modified).ToList()) { if (entity is IObservableEntity en) { en?.OnBeforeSave(); } else if (entity.Entity is IObservableEntity en2) { en2?.OnBeforeSave(); } } var result = base.SaveChanges(); RaisePendingNotifications(); return result; } /// /// Asynchronously saves all changes made in this context to the underlying database. /// /// A to observe while waiting for the task to complete. /// /// A task that represents the asynchronous save operation. /// The task result contains the number of objects written to the underlying database. /// /// /// Multiple active operations on the same context instance are not supported. Use 'await' to ensure /// that any asynchronous operations have completed before calling another method on this context. /// public override Task SaveChangesAsync(CancellationToken cancellationToken) { foreach (var entity in ChangeTracker.Entries().Where(x => x.State == EntityState.Added || x.State == EntityState.Modified).ToList().Select(x => x.Entity).ToList()) { if (entity is IObservableEntity && entity != null) { (entity as IObservableEntity).OnBeforeSave(); } } var result = base.SaveChangesAsync(cancellationToken); RaisePendingNotifications(); return result; } /// /// Raises the pending notifications. /// private void RaisePendingNotifications() { Task.Factory.StartNew(() => { foreach (var e in _pending_notifications.DistinctBy(x => x.NotifiedEntity)) { try { e.NotifiedEntity.RaiseModified(e.Context, e.ModifiedEntity, e.NotifiedEntity); } catch { } } _pending_notifications.Clear(); }); } /// /// Extension point allowing the user to override the default behavior of validating only /// added and modified entities. /// /// DbEntityEntry instance that is supposed to be validated. /// /// true to proceed with validation; false otherwise. /// protected override bool ShouldValidateEntity(DbEntityEntry entityEntry) { if (entityEntry.State == EntityState.Modified && entityEntry.Entity is IObservableEntity) { IObservableEntity modified = entityEntry.Entity as IObservableEntity; foreach (var toNotify in ObservableEntitiesContainer.RegisteredEntities.ToList().Where(x => x.Guid == modified.Guid).ToList()) { _pending_notifications.Add(new ObservableModifiedEventArgs(this, modified, toNotify)); } } return base.ShouldValidateEntity(entityEntry); } /// /// Gets an instance of which wraps this instance. /// public ObservablesContextAdapter Adapter { get { return _adapter; } } /// /// Overrides the default data source that is read from the core settings. /// /// The data source. public static void OverrideSettingsDataSource(DataSource dataSource) { _override_datasource = dataSource; } /// /// Gets the current data source. /// /// public DataSource GetDataSource() { return _dataSource; } /// /// Gets the actual data source (settings or overridden). /// /// public static DataSource GetActualDataSource() { return _override_datasource != null ? _override_datasource : SettingsManager.Default.GetOrCreate().DataSource; } /// /// Returns a that represents this instance. /// /// /// A that represents this instance. /// /// public override string ToString() { return GetDataSource().ToString(); } /// /// Clears the model store on the file system. /// public static void ClearModelStore() { //ObservablesContextConfiguration.ClearModelStore(); } /// /// Enables the in memory cache. /// /// Maximum cache time to preserve a single entity cache. public static void EnableInMemoryCache(TimeSpan cacheTime, ObservablesContextInMemoryCachingMode mode) { if (mode != ObservablesContextInMemoryCachingMode.None) { var cache = new ObservablesContextInMemoryCache() { Expiration = cacheTime }; if (mode == ObservablesContextInMemoryCachingMode.Relative) { cache.ResetAccessTimeOnAccess = true; } EntityFrameworkCache.Initialize(cache); } } /// /// Disposes the context. The underlying is also disposed if it was created /// is by this context or ownership was passed to this context when this context was created. /// The connection to the database ( object) is also disposed if it was created /// is by this context or ownership was passed to this context when this context was created. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { _open_contexts.Remove(this); base.Dispose(disposing); IsDisposed = true; } public static void UpdateAccessToken(String accessToken, DateTime expiration) { if (_override_datasource != null) { _override_datasource.AccessToken = accessToken; _override_datasource.AccessTokenExpiration = expiration; foreach (var context in _open_contexts.Where(x => x._dataSource.Type == DataSourceType.AccessToken)) { context._dataSource = _override_datasource; var connection = context.Database.Connection as SqlConnection; if (connection != null) { connection.AccessToken = context._dataSource.AccessToken; } } } } } }