PageRenderTime 152ms CodeModel.GetById 40ms app.highlight 79ms RepoModel.GetById 21ms app.codeStats 1ms

/Signum.Engine/Schema/Schema.cs

https://github.com/mutharasank/framework
C# | 1000 lines | 767 code | 223 blank | 10 comment | 124 complexity | 1cb8fefd53431afb4ca8f63604e3e808 MD5 | raw file
   1using System;
   2using System.Collections.Generic;
   3using System.Linq;
   4using System.Text;
   5using System.Reflection;
   6using Signum.Entities;
   7using Signum.Utilities;
   8using System.Globalization;
   9using System.Diagnostics;
  10using Signum.Utilities.ExpressionTrees;
  11using Signum.Utilities.DataStructures;
  12using System.Threading;
  13using System.Linq.Expressions;
  14using Signum.Entities.Reflection;
  15using System.Collections;
  16using Signum.Utilities.Reflection;
  17using Signum.Engine.Linq;
  18using Signum.Entities.DynamicQuery;
  19using Signum.Engine.DynamicQuery;
  20using Signum.Entities.Basics;
  21using Signum.Engine.Basics;
  22
  23namespace Signum.Engine.Maps
  24{
  25    public class Schema : IImplementationsFinder
  26    {
  27        public CultureInfo ForceCultureInfo { get; set; }
  28
  29        public TimeZoneMode TimeZoneMode { get; set; }
  30
  31        Version version;
  32        public Version Version
  33        {
  34            get
  35            {
  36                if (version == null)
  37                    throw new InvalidOperationException("Schema.Version is not set");
  38
  39                return version;
  40            }
  41            set { this.version = value; }
  42        }
  43
  44        string applicationName; 
  45        public string ApplicationName
  46        {
  47            get { return applicationName ?? (applicationName = AppDomain.CurrentDomain.FriendlyName); }
  48            set { applicationName = value; }
  49        }
  50
  51        public SchemaSettings Settings { get; private set; }
  52
  53        public SchemaAssets Assets { get; private set; }
  54
  55        Dictionary<Type, Table> tables = new Dictionary<Type, Table>();
  56        public Dictionary<Type, Table> Tables
  57        {
  58            get { return tables; }
  59        }
  60
  61        const string errorType = "TypeDN table not cached. Remember to call Schema.Current.Initialize";
  62
  63
  64        #region Events
  65
  66        public event Func<Type, string> IsAllowedCallback;
  67
  68        public string IsAllowed(Type type)
  69        {
  70            if (IsAllowedCallback != null)
  71                foreach (Func<Type, string> f in IsAllowedCallback.GetInvocationList())
  72                {
  73                    string result = f(type);
  74
  75                    if (result != null)
  76                        return result;
  77                }
  78
  79            return null;
  80        }
  81
  82        internal Dictionary<string, Type> NameToType = new Dictionary<string, Type>();
  83        internal Dictionary<Type, string> TypeToName = new Dictionary<Type, string>();
  84
  85        internal ResetLazy<TypeCaches> typeCachesLazy;
  86
  87        public void AssertAllowed(Type type)
  88        {
  89            string error = IsAllowed(type);
  90
  91            if (error != null)
  92                throw new UnauthorizedAccessException(EngineMessage.UnauthorizedAccessTo0Because1.NiceToString().Formato(type.NiceName(), error));
  93        }
  94
  95        readonly IEntityEvents entityEventsGlobal = new EntityEvents<IdentifiableEntity>();
  96        public EntityEvents<IdentifiableEntity> EntityEventsGlobal
  97        {
  98            get { return (EntityEvents<IdentifiableEntity>)entityEventsGlobal; }
  99        }
 100
 101        Dictionary<Type, IEntityEvents> entityEvents = new Dictionary<Type, IEntityEvents>();
 102        public EntityEvents<T> EntityEvents<T>()
 103            where T : IdentifiableEntity
 104        {
 105            return (EntityEvents<T>)entityEvents.GetOrCreate(typeof(T), () => new EntityEvents<T>());
 106        }
 107
 108        internal void OnPreSaving(IdentifiableEntity entity, ref bool graphModified)
 109        {
 110            AssertAllowed(entity.GetType());
 111
 112            IEntityEvents ee = entityEvents.TryGetC(entity.GetType());
 113
 114            if (ee != null)
 115                ee.OnPreSaving(entity, ref graphModified);
 116
 117            entityEventsGlobal.OnPreSaving(entity, ref graphModified);
 118        }
 119
 120        internal void OnSaving(IdentifiableEntity entity)
 121        {
 122            AssertAllowed(entity.GetType());
 123
 124            IEntityEvents ee = entityEvents.TryGetC(entity.GetType());
 125
 126            if (ee != null)
 127                ee.OnSaving(entity);
 128
 129            entityEventsGlobal.OnSaving(entity);
 130        }
 131
 132        internal void OnRetrieved(IdentifiableEntity entity)
 133        {
 134            AssertAllowed(entity.GetType());
 135
 136            IEntityEvents ee = entityEvents.TryGetC(entity.GetType());
 137
 138            if (ee != null)
 139                ee.OnRetrieved(entity);
 140
 141            entityEventsGlobal.OnRetrieved(entity);
 142        }
 143
 144        internal void OnPreUnsafeDelete<T>(IQueryable<T> entityQuery) where T : IdentifiableEntity
 145        {
 146            AssertAllowed(typeof(T));
 147
 148            EntityEvents<T> ee = (EntityEvents<T>)entityEvents.TryGetC(typeof(T));
 149
 150            if (ee != null)
 151                ee.OnPreUnsafeDelete(entityQuery);
 152        }
 153
 154        internal void OnPreUnsafeMListDelete<T>(IQueryable mlistQuery, IQueryable<T> entityQuery) where T : IdentifiableEntity
 155        {
 156            AssertAllowed(typeof(T));
 157
 158            EntityEvents<T> ee = (EntityEvents<T>)entityEvents.TryGetC(typeof(T));
 159
 160            if (ee != null)
 161                ee.OnPreUnsafeMListDelete(mlistQuery, entityQuery);
 162        }
 163
 164
 165        internal void OnPreUnsafeUpdate(IUpdateable update)
 166        {
 167            var type = update.EntityType;
 168            if (type.IsInstantiationOf(typeof(MListElement<,>)))
 169                type = type.GetGenericArguments().First();
 170
 171            AssertAllowed(type);
 172
 173            var ee = entityEvents.TryGetC(type);
 174
 175            if (ee != null)
 176                ee.OnPreUnsafeUpdate(update);
 177        }
 178
 179        internal void OnPreUnsafeInsert(Type type, IQueryable query, LambdaExpression constructor, IQueryable entityQuery)
 180        {
 181            AssertAllowed(type);
 182
 183            var ee = entityEvents.TryGetC(type);
 184
 185            if (ee != null)
 186                ee.OnPreUnsafeInsert(query, constructor, entityQuery);
 187        }
 188
 189        public ICacheController CacheController(Type type)
 190        {
 191            IEntityEvents ee = entityEvents.TryGetC(type);
 192
 193            if (ee == null)
 194                return null;
 195
 196            return ee.CacheController;
 197        }
 198
 199        internal CacheControllerBase<T> CacheController<T>() where T : IdentifiableEntity
 200        {
 201            EntityEvents<T> ee = (EntityEvents<T>)entityEvents.TryGetC(typeof(T));
 202
 203            if (ee == null)
 204                return null;
 205
 206            return ee.CacheController;
 207        }
 208
 209        internal IQueryable<T> OnFilterQuery<T>(IQueryable<T> query)
 210            where T : IdentifiableEntity
 211        {
 212            AssertAllowed(typeof(T));
 213
 214            EntityEvents<T> ee = (EntityEvents<T>)entityEvents.TryGetC(typeof(T));
 215            if (ee == null)
 216                return query;
 217
 218            return ee.OnFilterQuery(query);
 219        }
 220
 221        internal bool HasQueryFilter(Type type)
 222        {
 223            IEntityEvents ee = entityEvents.TryGetC(type);
 224            if (ee == null)
 225                return false;
 226
 227            return ee.HasQueryFilter;
 228        }
 229
 230        public event Func<Replacements, SqlPreCommand> Synchronizing;
 231        internal SqlPreCommand SynchronizationScript(string databaseName, bool interactive = true)
 232        {
 233            if (Synchronizing == null)
 234                return null;
 235
 236            using (Sync.ChangeBothCultures(ForceCultureInfo))
 237            using (ExecutionMode.Global())
 238            {
 239                Replacements replacements = new Replacements() { Interactive = interactive };
 240                SqlPreCommand command = Synchronizing
 241                    .GetInvocationList()
 242                    .Cast<Func<Replacements, SqlPreCommand>>()
 243                    .Select(e =>
 244                    {
 245                        try
 246                        {
 247                            return e(replacements);
 248                        }
 249                        catch (Exception ex)
 250                        {
 251                            return new SqlPreCommandSimple("Exception on {0}.{1}: {2}".Formato(e.Method.DeclaringType.Name, e.Method.Name, ex.Message));
 252                        }
 253                    })
 254                    .Combine(Spacing.Triple);
 255
 256                if (command == null)
 257                    return null;
 258
 259                var replacementsComment = replacements.Interactive ? null : replacements.Select(r =>
 260                    SqlPreCommandConcat.Combine(Spacing.Double, new SqlPreCommandSimple("-- Replacements on {0}".Formato(r.Key)),
 261                        r.Value.Select(a => new SqlPreCommandSimple("--   {0} -> {1}".Formato(a.Key, a.Value))).Combine(Spacing.Simple)));
 262
 263                return SqlPreCommand.Combine(Spacing.Double,
 264                    new SqlPreCommandSimple(SynchronizerMessage.StartOfSyncScriptGeneratedOn0.NiceToString().Formato(DateTime.Now)),
 265
 266                    new SqlPreCommandSimple("use {0}".Formato(databaseName)),
 267                    command,
 268                    new SqlPreCommandSimple(SynchronizerMessage.EndOfSyncScript.NiceToString()));
 269            }
 270        }
 271
 272        public event Func<SqlPreCommand> Generating;
 273        internal SqlPreCommand GenerationScipt()
 274        {
 275            if (Generating == null)
 276                return null;
 277
 278            using (Sync.ChangeBothCultures(ForceCultureInfo))
 279            using (ExecutionMode.Global())
 280            {
 281                return Generating
 282                    .GetInvocationList()
 283                    .Cast<Func<SqlPreCommand>>()
 284                    .Select(e => e())
 285                    .Combine(Spacing.Triple);
 286            }
 287        }
 288
 289        public class InitEventDictionary
 290        {
 291            Dictionary<InitLevel, Action> dict = new Dictionary<InitLevel, Action>();
 292
 293            InitLevel? initLevel;
 294
 295            public Action this[InitLevel level]
 296            {
 297                get { return dict.TryGetC(level); }
 298                set
 299                {
 300                    int current = dict.TryGetC(level).Try(d => d.GetInvocationList().Length) ?? 0;
 301                    int @new = value.Try(d => d.GetInvocationList().Length) ?? 0;
 302
 303                    if (Math.Abs(current - @new) > 1)
 304                        throw new InvalidOperationException("add or remove just one event handler each time");
 305
 306                    dict[level] = value;
 307                }
 308            }
 309
 310            public void InitializeUntil(InitLevel topLevel)
 311            {
 312                for (InitLevel current = initLevel + 1 ?? InitLevel.Level0SyncEntities; current <= topLevel; current++)
 313                {
 314                    InitializeJust(current);
 315                    initLevel = current;
 316                }
 317            }
 318
 319            void InitializeJust(InitLevel currentLevel)
 320            {
 321                using (HeavyProfiler.Log("InitializeJuts", () => currentLevel.ToString()))
 322                {
 323                    Action h = dict.TryGetC(currentLevel);
 324                    if (h == null)
 325                        return;
 326
 327                    var handlers = h.GetInvocationList().Cast<Action>();
 328
 329                    foreach (Action handler in handlers)
 330                    {
 331                        using (HeavyProfiler.Log("InitAction", () => "{0}.{1}".Formato(handler.Method.DeclaringType.TypeName(), handler.Method.MethodName())))
 332                        {
 333                            handler();
 334                        }
 335                    }
 336                }
 337            }
 338
 339            public override string ToString()
 340            {
 341                return dict.OrderBy(a => a.Key)
 342                    .ToString(a => "{0} -> \r\n{1}".Formato(
 343                        a.Key,
 344                        a.Value.GetInvocationList().Select(h => h.Method).ToString(mi => "\t{0}.{1}".Formato(
 345                            mi.DeclaringType.TypeName(),
 346                            mi.MethodName()),
 347                        "\r\n")
 348                    ), "\r\n\r\n");
 349            }
 350        }
 351
 352        public InitEventDictionary Initializing = new InitEventDictionary();
 353
 354        public void Initialize()
 355        {
 356            using (ExecutionMode.Global())
 357                Initializing.InitializeUntil(InitLevel.Level4BackgroundProcesses);
 358        }
 359
 360        public void InitializeUntil(InitLevel level)
 361        {
 362            using (ExecutionMode.Global())
 363                Initializing.InitializeUntil(level);
 364        }
 365
 366
 367        #endregion
 368
 369        static Schema()
 370        {
 371            PropertyRoute.SetFindImplementationsCallback(pr => Schema.Current.FindImplementations(pr));
 372        }
 373
 374        internal Schema(SchemaSettings settings)
 375        {
 376            this.Settings = settings;
 377            this.Assets = new SchemaAssets();
 378
 379            Generating += SchemaGenerator.CreateSchemasScript;
 380            Generating += SchemaGenerator.CreateTablesScript;
 381            Generating += SchemaGenerator.InsertEnumValuesScript;
 382            Generating += TypeLogic.Schema_Generating;
 383            Generating += Assets.Schema_Generating;
 384
 385            Synchronizing += SchemaSynchronizer.SnapshotIsolation;
 386            Synchronizing += SchemaSynchronizer.SynchronizeSchemasScript;
 387            Synchronizing += SchemaSynchronizer.SynchronizeTablesScript;
 388            Synchronizing += TypeLogic.Schema_Synchronizing;
 389            Synchronizing += Assets.Schema_Synchronizing;
 390        }
 391
 392        public static Schema Current
 393        {
 394            get { return Connector.Current.Schema; }
 395        }
 396
 397        public Table Table<T>() where T : IdentifiableEntity
 398        {
 399            return Table(typeof(T));
 400        }
 401
 402        public Table Table(Type type)
 403        {
 404            return Tables.GetOrThrow(type, "Table {0} not loaded in schema");
 405        }
 406
 407        internal static Field FindField(IFieldFinder fieldFinder, MemberInfo[] members)
 408        {
 409            IFieldFinder current = fieldFinder;
 410            Field result = null;
 411            foreach (var mi in members)
 412            {
 413                if (current == null)
 414                    throw new InvalidOperationException("{0} does not implement {1}".Formato(result, typeof(IFieldFinder).Name));
 415
 416                result = current.GetField(mi);
 417
 418                current = result as IFieldFinder;
 419            }
 420
 421            return result;
 422        }
 423
 424        internal static Field TryFindField(IFieldFinder fieldFinder, MemberInfo[] members)
 425        {
 426            IFieldFinder current = fieldFinder;
 427            Field result = null;
 428            foreach (var mi in members)
 429            {
 430                if (current == null)
 431                    return null;
 432
 433                result = current.TryGetField(mi);
 434
 435                if (result == null)
 436                    return null;
 437
 438                current = result as IFieldFinder;
 439            }
 440
 441            return result;
 442        }
 443
 444        public Dictionary<PropertyRoute, Implementations> FindAllImplementations(Type root)
 445        {
 446            try
 447            {
 448                if (!Tables.ContainsKey(root))
 449                    return null;
 450
 451                var table = Table(root);
 452
 453                return PropertyRoute.GenerateRoutes(root)
 454                    .Select(r => r.Type.IsMList() ? r.Add("Item") : r)
 455                    .Where(r => r.Type.CleanType().IsIIdentifiable())
 456                    .ToDictionary(r => r, r => FindImplementations(r));
 457            }
 458            catch (Exception e)
 459            {
 460                e.Data["rootType"] = root.TypeName();
 461                throw;
 462            }
 463        }
 464
 465        public Implementations FindImplementations(PropertyRoute route)
 466        {
 467            if (route.PropertyRouteType == PropertyRouteType.LiteEntity)
 468                route = route.Parent;
 469
 470            Type type = route.RootType;
 471
 472            if (!Tables.ContainsKey(type))
 473                return Implementations.By(route.Type.CleanType());
 474
 475            Field field = TryFindField(Table(type), route.Members);
 476            //if (field == null)
 477            //    return Implementations.ByAll;
 478
 479            FieldReference refField = field as FieldReference;
 480            if (refField != null)
 481                return Implementations.By(refField.FieldType.CleanType());
 482
 483            FieldImplementedBy ibField = field as FieldImplementedBy;
 484            if (ibField != null)
 485                return Implementations.By(ibField.ImplementationColumns.Keys.ToArray());
 486
 487            FieldImplementedByAll ibaField = field as FieldImplementedByAll;
 488            if (ibaField != null)
 489                return Implementations.ByAll;
 490
 491            Implementations? implementations = CalculateExpressionImplementations(route);
 492
 493            if (implementations != null)
 494                return implementations.Value;
 495
 496            var ss = Schema.Current.Settings;
 497            if (route.Follow(r => r.Parent)
 498                .TakeWhile(t => t.PropertyRouteType != PropertyRouteType.Root)
 499                .SelectMany(r => ss.FieldAttributes(r))
 500                .Any(a => a is IgnoreAttribute))
 501            {
 502                var atts = ss.FieldAttributes(route);
 503
 504                return Implementations.TryFromAttributes(route.Type.CleanType(), atts, route) ?? Implementations.By();
 505            }
 506
 507            throw new InvalidOperationException("Impossible to determine implementations for {0}".Formato(route, typeof(IIdentifiable).Name));
 508        }
 509
 510        private Implementations? CalculateExpressionImplementations(PropertyRoute route)
 511        {
 512            if (route.PropertyRouteType != PropertyRouteType.FieldOrProperty)
 513                return null;
 514
 515            var lambda = ExpressionCleaner.GetFieldExpansion(route.Parent.Type, route.PropertyInfo);
 516            if (lambda == null)
 517                return null;
 518
 519            Expression e = MetadataVisitor.JustVisit(lambda, new MetaExpression(route.Parent.Type, new CleanMeta(route.Parent.TryGetImplementations(), new[] { route.Parent })));
 520
 521            MetaExpression me = e as MetaExpression;
 522            if (me == null)
 523                return null;
 524
 525            return me.Meta.Implementations;
 526        }
 527
 528        /// <summary>
 529        /// Uses a lambda navigate in a strongly-typed way, you can acces field using the property and collections using Single().
 530        /// </summary>
 531        public Field Field<T, V>(Expression<Func<T, V>> lambdaToField)
 532            where T : IdentifiableEntity
 533        {
 534            return FindField(Table(typeof(T)), Reflector.GetMemberList(lambdaToField));
 535        }
 536
 537        public override string ToString()
 538        {
 539            return "Schema ( tables: {0} )".Formato(tables.Count);
 540        }
 541
 542        public IEnumerable<ITable> GetDatabaseTables()
 543        {
 544            foreach (var table in Schema.Current.Tables.Values)
 545            {
 546                yield return table;
 547
 548                foreach (var subTable in table.RelationalTables().Cast<ITable>())
 549                    yield return subTable;
 550            }
 551        }
 552
 553        public List<DatabaseName> DatabaseNames()
 554        {
 555            return GetDatabaseTables().Select(a => a.Name.Schema.Try(s => s.Database)).Distinct().ToList();
 556        }
 557
 558        public DirectedEdgedGraph<Table, RelationInfo> ToDirectedGraph()
 559        {
 560            return DirectedEdgedGraph<Table, RelationInfo>.Generate(Tables.Values, t => t.DependentTables());
 561        }
 562
 563        public Type GetType(int id)
 564        {
 565            return typeCachesLazy.Value.IdToType[id];
 566        }
 567
 568      
 569    }
 570
 571    public class RelationInfo
 572    {
 573        public bool IsLite { get; set; }
 574        public bool IsNullable { get; set; }
 575        public bool IsCollection { get; set; }
 576        public bool IsEnum { get; set; }
 577    }
 578
 579    internal interface IEntityEvents
 580    {
 581        void OnPreSaving(IdentifiableEntity entity, ref bool graphModified);
 582        void OnSaving(IdentifiableEntity entity);
 583        void OnRetrieved(IdentifiableEntity entity);
 584
 585        void OnPreUnsafeUpdate(IUpdateable update);
 586        void OnPreUnsafeInsert(IQueryable query, LambdaExpression constructor, IQueryable entityQuery);
 587
 588        ICacheController CacheController { get; }
 589
 590        bool HasQueryFilter { get; }
 591    }
 592
 593    public interface ICacheController
 594    {
 595        bool Enabled { get; }
 596        void Load();
 597
 598        IEnumerable<int> GetAllIds();
 599
 600        void Complete(IdentifiableEntity entity, IRetriever retriver);
 601
 602        string GetToString(int id);
 603    }
 604
 605    public class InvalidateEventArgs : EventArgs { }
 606    public class InvaludateEventArgs : EventArgs { }
 607
 608    public abstract class CacheControllerBase<T> : ICacheController
 609        where T : IdentifiableEntity
 610    {
 611        public abstract bool Enabled { get; }
 612        public abstract void Load();
 613
 614        public abstract IEnumerable<int> GetAllIds();
 615
 616        void ICacheController.Complete(IdentifiableEntity entity, IRetriever retriver)
 617        {
 618            Complete((T)entity, retriver);
 619        }
 620
 621        public abstract void Complete(T entity, IRetriever retriver);
 622
 623        public abstract string GetToString(int id);
 624    }
 625
 626    public class EntityEvents<T> : IEntityEvents
 627        where T : IdentifiableEntity
 628    {
 629        public event PreSavingEventHandler<T> PreSaving;
 630        public event SavingEventHandler<T> Saving;
 631
 632        public event RetrievedEventHandler<T> Retrieved;
 633
 634        public CacheControllerBase<T> CacheController { get; set; }
 635
 636        public event FilterQueryEventHandler<T> FilterQuery;
 637
 638        public event DeleteHandler<T> PreUnsafeDelete;
 639        public event DeleteMlistHandler<T> PreUnsafeMListDelete;
 640
 641        public event UpdateHandler<T> PreUnsafeUpdate;
 642
 643        public event InsertHandler<T> PreUnsafeInsert;
 644
 645        internal IQueryable<T> OnFilterQuery(IQueryable<T> query)
 646        {
 647            if (FilterQuery != null)
 648                foreach (FilterQueryEventHandler<T> filter in FilterQuery.GetInvocationList())
 649                    query = filter(query);
 650
 651            return query;
 652        }
 653
 654        public bool HasQueryFilter
 655        {
 656            get { return FilterQuery != null; }
 657        }
 658
 659        internal void OnPreUnsafeDelete(IQueryable<T> entityQuery)
 660        {
 661            if (PreUnsafeDelete != null)
 662                foreach (DeleteHandler<T> action in PreUnsafeDelete.GetInvocationList().Reverse())
 663                    action(entityQuery);
 664        }
 665
 666        internal void OnPreUnsafeMListDelete(IQueryable mlistQuery, IQueryable<T> entityQuery)
 667        {
 668            if (PreUnsafeMListDelete != null)
 669                foreach (DeleteMlistHandler<T> action in PreUnsafeMListDelete.GetInvocationList().Reverse())
 670                    action(mlistQuery, entityQuery);
 671        }
 672
 673        void IEntityEvents.OnPreUnsafeUpdate(IUpdateable update)
 674        {
 675            if (PreUnsafeUpdate != null)
 676            {
 677                var query = update.EntityQuery<T>();
 678                foreach (UpdateHandler<T> action in PreUnsafeUpdate.GetInvocationList().Reverse())
 679                    action(update, query);
 680            }
 681        }
 682
 683        void IEntityEvents.OnPreUnsafeInsert(IQueryable query, LambdaExpression constructor, IQueryable entityQuery)
 684        {
 685            if (PreUnsafeInsert != null)
 686                foreach (InsertHandler<T> action in PreUnsafeInsert.GetInvocationList().Reverse())
 687                    action(query, constructor, (IQueryable<T>)entityQuery);
 688
 689        }
 690
 691        void IEntityEvents.OnPreSaving(IdentifiableEntity entity, ref bool graphModified)
 692        {
 693            if (PreSaving != null)
 694                PreSaving((T)entity, ref graphModified);
 695        }
 696
 697        void IEntityEvents.OnSaving(IdentifiableEntity entity)
 698        {
 699            if (Saving != null)
 700                Saving((T)entity);
 701
 702        }
 703
 704        void IEntityEvents.OnRetrieved(IdentifiableEntity entity)
 705        {
 706            if (Retrieved != null)
 707                Retrieved((T)entity);
 708        }
 709
 710        ICacheController IEntityEvents.CacheController
 711        {
 712            get { return CacheController; }
 713        }
 714    }
 715
 716    public delegate void PreSavingEventHandler<T>(T ident, ref bool graphModified) where T : IdentifiableEntity;
 717    public delegate void RetrievedEventHandler<T>(T ident) where T : IdentifiableEntity;
 718    public delegate void SavingEventHandler<T>(T ident) where T : IdentifiableEntity;
 719    public delegate void SavedEventHandler<T>(T ident, SavedEventArgs args) where T : IdentifiableEntity;
 720    public delegate IQueryable<T> FilterQueryEventHandler<T>(IQueryable<T> query);
 721
 722    public delegate void DeleteHandler<T>(IQueryable<T> entityQuery);
 723    public delegate void DeleteMlistHandler<T>(IQueryable mlistQuery, IQueryable<T> entityQuery);
 724    public delegate void UpdateHandler<T>(IUpdateable update, IQueryable<T> entityQuery);
 725    public delegate void InsertHandler<T>(IQueryable query, LambdaExpression constructor, IQueryable<T> entityQuery);
 726
 727    public class SavedEventArgs
 728    {
 729        public bool IsRoot { get; set; }
 730        public bool WasNew { get; set; }
 731        public bool WasModified { get; set; }
 732    }
 733
 734
 735    public static class TableExtensions
 736    {
 737        internal static string UnScapeSql(this string name)
 738        {
 739            return name.Trim('[', ']');
 740        }
 741    }
 742
 743    public class ServerName : IEquatable<ServerName>
 744    {
 745        public string Name { get; private set; }
 746
 747        /// <summary>
 748        /// Linked Servers: http://msdn.microsoft.com/en-us/library/ms188279.aspx
 749        /// Not fully supported jet
 750        /// </summary>
 751        /// <param name="name"></param>
 752        public ServerName(string name)
 753        {
 754            if (string.IsNullOrEmpty(name))
 755                throw new ArgumentNullException("name");
 756
 757            this.Name = name;
 758        }
 759
 760        public override string ToString()
 761        {
 762            return Name.SqlScape();
 763        }
 764
 765        public bool Equals(ServerName other)
 766        {
 767            return other.Name == Name;
 768        }
 769
 770        public override bool Equals(object obj)
 771        {
 772            var db = obj as ServerName;
 773            return db != null && Equals(db);
 774        }
 775
 776        public override int GetHashCode()
 777        {
 778            return Name.GetHashCode();
 779        }
 780
 781        internal static ServerName Parse(string name)
 782        {
 783            if (string.IsNullOrEmpty(name))
 784                return null;
 785
 786            return new ServerName(name.UnScapeSql());
 787        }
 788    }
 789
 790    public class DatabaseName : IEquatable<DatabaseName>
 791    {
 792        public string Name { get; private set; }
 793
 794        public ServerName Server { get; private set; }
 795
 796        public DatabaseName(ServerName server, string name)
 797        {
 798            if (string.IsNullOrEmpty(name))
 799                throw new ArgumentNullException("name");
 800
 801            this.Name = name;
 802            this.Server = server;
 803        }
 804
 805        public override string ToString()
 806        {
 807            var result = Name.SqlScape();
 808
 809            if (Server == null)
 810                return result;
 811
 812            return Server.ToString() + "." + result;
 813        }
 814
 815        public bool Equals(DatabaseName other)
 816        {
 817            return other.Name == Name &&
 818                object.Equals(Server, other.Server);
 819        }
 820
 821        public override bool Equals(object obj)
 822        {
 823            var db = obj as DatabaseName;
 824            return db != null && Equals(db);
 825        }
 826
 827        public override int GetHashCode()
 828        {
 829            return Name.GetHashCode() ^ (Server == null ? 0 : Server.GetHashCode());
 830        }
 831
 832        internal static DatabaseName Parse(string name)
 833        {
 834            if (string.IsNullOrEmpty(name))
 835                return null;
 836
 837            return new DatabaseName(ServerName.Parse(name.TryBeforeLast('.')), (name.TryAfterLast('.') ?? name).UnScapeSql());
 838        }
 839    }
 840
 841    public class SchemaName : IEquatable<SchemaName>
 842    {
 843        public string Name { get; private set; }
 844
 845        readonly DatabaseName database;
 846
 847        public DatabaseName Database
 848        {
 849            get
 850            {
 851                if (database == null || ObjectName.CurrentOptions.AvoidDatabaseName)
 852                    return null;
 853
 854                return database;
 855            }
 856        }
 857
 858        public static readonly SchemaName Default = new SchemaName(null, "dbo");
 859
 860        public bool IsDefault()
 861        {
 862            return Name == "dbo" && Database == null;
 863        }
 864
 865        public SchemaName(DatabaseName database, string name)
 866        {
 867            if (string.IsNullOrEmpty(name))
 868                throw new ArgumentNullException("name");
 869
 870            this.Name = name;
 871            this.database = database;
 872        }
 873
 874        public override string ToString()
 875        {
 876            var result = Name.SqlScape();
 877
 878            if (Database == null)
 879                return result;
 880
 881            return Database.ToString() + "." + result;
 882        }
 883
 884        public bool Equals(SchemaName other)
 885        {
 886            return other.Name == Name &&
 887                object.Equals(Database, other.Database);
 888        }
 889
 890        public override bool Equals(object obj)
 891        {
 892            var sc = obj as SchemaName;
 893            return sc != null && Equals(sc);
 894        }
 895
 896        public override int GetHashCode()
 897        {
 898            return Name.GetHashCode() ^ (Database == null ? 0 : Database.GetHashCode());
 899        }
 900
 901        internal static SchemaName Parse(string name)
 902        {
 903            if (string.IsNullOrEmpty(name))
 904                return SchemaName.Default;
 905
 906            return new SchemaName(DatabaseName.Parse(name.TryBeforeLast('.')), (name.TryAfterLast('.') ?? name).UnScapeSql());
 907        }
 908
 909    }
 910
 911    public class ObjectName : IEquatable<ObjectName>
 912    {
 913        public string Name { get; private set; }
 914
 915        public SchemaName Schema { get; private set; }
 916
 917        public ObjectName(SchemaName schema, string name)
 918        {
 919            if (string.IsNullOrEmpty(name))
 920                throw new ArgumentNullException("name");
 921
 922            if (schema == null)
 923                throw new ArgumentNullException("schema");
 924
 925            this.Name = name;
 926            this.Schema = schema;
 927        }
 928
 929        public override string ToString()
 930        {
 931            if (Schema == null || Schema.IsDefault())
 932                return Name.SqlScape();
 933
 934            return Schema.ToString() + "." + Name.SqlScape();
 935        }
 936
 937        public string ToStringDbo()
 938        {
 939            if (Schema == null)
 940                return Name.SqlScape();
 941
 942            return Schema.ToString() + "." + Name.SqlScape();
 943        }
 944
 945        public bool Equals(ObjectName other)
 946        {
 947            return other.Name == Name &&
 948                object.Equals(Schema, other.Schema);
 949        }
 950
 951        public override bool Equals(object obj)
 952        {
 953            var sc = obj as ObjectName;
 954            return sc != null && Equals(sc);
 955        }
 956
 957        public override int GetHashCode()
 958        {
 959            return Name.GetHashCode() ^ (Schema == null ? 0 : Schema.GetHashCode());
 960        }
 961
 962        internal static ObjectName Parse(string name)
 963        {
 964            if (string.IsNullOrEmpty(name))
 965                throw new ArgumentNullException("name");
 966
 967            return new ObjectName(SchemaName.Parse(name.TryBeforeLast('.')), (name.TryAfterLast('.') ?? name).UnScapeSql());
 968        }
 969
 970        public ObjectName OnDatabase(DatabaseName databaseName)
 971        {
 972            return new ObjectName(new SchemaName(databaseName, Schema.Name), Name);
 973        }
 974
 975        public ObjectName OnSchema(SchemaName schemaName)
 976        {
 977            return new ObjectName(schemaName, Name);
 978        }
 979
 980        static readonly ThreadVariable<ObjectNameOptions> optionsVariable = Statics.ThreadVariable<ObjectNameOptions>("objectNameOptions");
 981        public static IDisposable OverrideOptions(ObjectNameOptions options)
 982        {
 983            var old = optionsVariable.Value;
 984            optionsVariable.Value = options;
 985            return new Disposable(() => optionsVariable.Value = old);
 986        }
 987
 988        public static ObjectNameOptions CurrentOptions
 989        {
 990            get { return optionsVariable.Value; }
 991        }
 992    }
 993
 994    public struct ObjectNameOptions
 995    {
 996        public bool IncludeDboSchema;
 997        public DatabaseName OverrideDatabaseNameOnSystemQueries;
 998        public bool AvoidDatabaseName;
 999    }
1000}