PageRenderTime 8ms CodeModel.GetById 18ms app.highlight 27ms RepoModel.GetById 1ms app.codeStats 1ms

/mcs/class/System.Data.Linq/src/DbLinq/Data/Linq/DataContext.cs

https://bitbucket.org/puffnfresh/mono-dependency-analysis
C# | 1275 lines | 912 code | 157 blank | 206 comment | 145 complexity | 7613003602d4ac3882770fb8d3f8076b MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1#region MIT license
   2// 
   3// MIT license
   4//
   5// Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne
   6// 
   7// Permission is hereby granted, free of charge, to any person obtaining a copy
   8// of this software and associated documentation files (the "Software"), to deal
   9// in the Software without restriction, including without limitation the rights
  10// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11// copies of the Software, and to permit persons to whom the Software is
  12// furnished to do so, subject to the following conditions:
  13// 
  14// The above copyright notice and this permission notice shall be included in
  15// all copies or substantial portions of the Software.
  16// 
  17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23// THE SOFTWARE.
  24// 
  25#endregion
  26
  27using System;
  28using System.Collections;
  29using System.Data;
  30using System.Data.Common;
  31using System.Data.Linq;
  32using System.Data.Linq.Mapping;
  33using System.Linq.Expressions;
  34using System.Collections.Generic;
  35using System.IO;
  36using System.Linq;
  37using System.Reflection;
  38using System.Reflection.Emit;
  39
  40#if MONO_STRICT
  41using AttributeMappingSource  = System.Data.Linq.Mapping.AttributeMappingSource;
  42#else
  43using AttributeMappingSource  = DbLinq.Data.Linq.Mapping.AttributeMappingSource;
  44#endif
  45
  46using DbLinq;
  47using DbLinq.Data.Linq;
  48using DbLinq.Data.Linq.Database;
  49using DbLinq.Data.Linq.Database.Implementation;
  50using DbLinq.Data.Linq.Identity;
  51using DbLinq.Data.Linq.Implementation;
  52using DbLinq.Data.Linq.Mapping;
  53using DbLinq.Data.Linq.Sugar;
  54using DbLinq.Factory;
  55using DbLinq.Util;
  56using DbLinq.Vendor;
  57
  58#if MONO_STRICT
  59namespace System.Data.Linq
  60#else
  61namespace DbLinq.Data.Linq
  62#endif
  63{
  64    public partial class DataContext : IDisposable
  65    {
  66        //private readonly Dictionary<string, ITable> _tableMap = new Dictionary<string, ITable>();
  67        private readonly Dictionary<Type, ITable> _tableMap = new Dictionary<Type, ITable>();
  68
  69        public MetaModel Mapping { get; private set; }
  70        // PC question: at ctor, we get a IDbConnection and the Connection property exposes a DbConnection
  71        //              WTF?
  72        public DbConnection Connection { get { return DatabaseContext.Connection as DbConnection; } }
  73
  74        // all properties below are set public to optionally be injected
  75        internal IVendor Vendor { get; set; }
  76        internal IQueryBuilder QueryBuilder { get; set; }
  77        internal IQueryRunner QueryRunner { get; set; }
  78        internal IMemberModificationHandler MemberModificationHandler { get; set; }
  79        internal IDatabaseContext DatabaseContext { get; private set; }
  80        // /all properties...
  81
  82        private bool objectTrackingEnabled = true;
  83        private bool deferredLoadingEnabled = true;
  84
  85        private bool queryCacheEnabled = false;
  86
  87        /// <summary>
  88        /// Disable the QueryCache: this is surely good for rarely used Select, since preparing
  89        /// the SelectQuery to be cached could require more time than build the sql from scratch.
  90        /// </summary>
  91        [DBLinqExtended]
  92        public bool QueryCacheEnabled 
  93        {
  94            get { return queryCacheEnabled; }
  95            set { queryCacheEnabled = value; }
  96        }
  97
  98        private IEntityTracker currentTransactionEntities;
  99        private IEntityTracker CurrentTransactionEntities
 100        {
 101            get
 102            {
 103                if (this.currentTransactionEntities == null)
 104                {
 105                    if (this.ObjectTrackingEnabled)
 106                        this.currentTransactionEntities = new EntityTracker();
 107                    else
 108                        this.currentTransactionEntities = new DisabledEntityTracker();
 109                }
 110                return this.currentTransactionEntities;
 111            }
 112        }
 113
 114        private IEntityTracker allTrackedEntities;
 115        private IEntityTracker AllTrackedEntities
 116        {
 117            get
 118            {
 119                if (this.allTrackedEntities == null)
 120                {
 121                    allTrackedEntities = ObjectTrackingEnabled
 122                        ? (IEntityTracker) new EntityTracker()
 123                        : (IEntityTracker) new DisabledEntityTracker();
 124                }
 125                return this.allTrackedEntities;
 126            }
 127        }
 128
 129        private IIdentityReaderFactory identityReaderFactory;
 130        private readonly IDictionary<Type, IIdentityReader> identityReaders = new Dictionary<Type, IIdentityReader>();
 131
 132        /// <summary>
 133        /// The default behavior creates one MappingContext.
 134        /// </summary>
 135        [DBLinqExtended]
 136        internal virtual MappingContext _MappingContext { get; set; }
 137
 138        [DBLinqExtended]
 139        internal IVendorProvider _VendorProvider { get; set; }
 140
 141        public DataContext(IDbConnection connection, MappingSource mapping)
 142        {
 143            Profiler.At("START DataContext(IDbConnection, MappingSource)");
 144            Init(new DatabaseContext(connection), mapping, null);
 145            Profiler.At("END DataContext(IDbConnection, MappingSource)");
 146        }
 147
 148        public DataContext(IDbConnection connection)
 149        {
 150            Profiler.At("START DataContext(IDbConnection)");
 151            if (connection == null)
 152                throw new ArgumentNullException("connection");
 153
 154            Init(new DatabaseContext(connection), null, null);
 155            Profiler.At("END DataContext(IDbConnection)");
 156        }
 157
 158        [DbLinqToDo]
 159        public DataContext(string fileOrServerOrConnection, MappingSource mapping)
 160        {
 161            Profiler.At("START DataContext(string, MappingSource)");
 162            if (fileOrServerOrConnection == null)
 163                throw new ArgumentNullException("fileOrServerOrConnection");
 164            if (mapping == null)
 165                throw new ArgumentNullException("mapping");
 166
 167            if (File.Exists(fileOrServerOrConnection))
 168                throw new NotImplementedException("File names not supported.");
 169
 170            // Is this a decent server name check?
 171            // It assumes that the connection string will have at least 2
 172            // parameters (separated by ';')
 173            if (!fileOrServerOrConnection.Contains(";"))
 174                throw new NotImplementedException("Server name not supported.");
 175
 176            // Assume it's a connection string...
 177            IVendor ivendor = GetVendor(ref fileOrServerOrConnection);
 178
 179            IDbConnection dbConnection = ivendor.CreateDbConnection(fileOrServerOrConnection);
 180            Init(new DatabaseContext(dbConnection), mapping, ivendor);
 181            Profiler.At("END DataContext(string, MappingSource)");
 182        }
 183
 184        /// <summary>
 185        /// Construct DataContext, given a connectionString.
 186        /// To determine which DB type to go against, we look for 'DbLinqProvider=xxx' substring.
 187        /// If not found, we assume that we are dealing with MS Sql Server.
 188        /// 
 189        /// Valid values are names of provider DLLs (or any other DLL containing an IVendor implementation)
 190        /// DbLinqProvider=Mysql
 191        /// DbLinqProvider=Oracle etc.
 192        /// </summary>
 193        /// <param name="connectionString">specifies file or server connection</param>
 194        [DbLinqToDo]
 195        public DataContext(string connectionString)
 196        {
 197            Profiler.At("START DataContext(string)");
 198            IVendor ivendor = GetVendor(ref connectionString);
 199
 200            IDbConnection dbConnection = ivendor.CreateDbConnection(connectionString);
 201            Init(new DatabaseContext(dbConnection), null, ivendor);
 202
 203            Profiler.At("END DataContext(string)");
 204        }
 205
 206        private IVendor GetVendor(ref string connectionString)
 207        {
 208            if (connectionString == null)
 209                throw new ArgumentNullException("connectionString");
 210
 211            Assembly assy;
 212            string vendorClassToLoad;
 213            GetVendorInfo(ref connectionString, out assy, out vendorClassToLoad);
 214
 215            var types =
 216                from type in assy.GetTypes()
 217                where type.Name.ToLowerInvariant() == vendorClassToLoad.ToLowerInvariant() &&
 218                    type.GetInterfaces().Contains(typeof(IVendor)) &&
 219                    type.GetConstructor(Type.EmptyTypes) != null
 220                select type;
 221            if (!types.Any())
 222            {
 223                throw new ArgumentException(string.Format("Found no IVendor class in assembly `{0}' named `{1}' having a default constructor.",
 224                    assy.GetName().Name, vendorClassToLoad));
 225            }
 226            else if (types.Count() > 1)
 227            {
 228                throw new ArgumentException(string.Format("Found too many IVendor classes in assembly `{0}' named `{1}' having a default constructor.",
 229                    assy.GetName().Name, vendorClassToLoad));
 230            }
 231            return (IVendor) Activator.CreateInstance(types.First());
 232        }
 233
 234        private void GetVendorInfo(ref string connectionString, out Assembly assembly, out string typeName)
 235        {
 236            System.Text.RegularExpressions.Regex reProvider
 237                = new System.Text.RegularExpressions.Regex(@"DbLinqProvider=([\w\.]+);?");
 238
 239            string assemblyName = null;
 240            string vendor;
 241            if (!reProvider.IsMatch(connectionString))
 242            {
 243                vendor       = "SqlServer";
 244                assemblyName = "DbLinq.SqlServer";
 245            }
 246            else
 247            {
 248                var match    = reProvider.Match(connectionString);
 249                vendor       = match.Groups[1].Value;
 250                assemblyName = "DbLinq." + vendor;
 251
 252                //plain DbLinq - non MONO: 
 253                //IVendor classes are in DLLs such as "DbLinq.MySql.dll"
 254                if (vendor.Contains("."))
 255                {
 256                    //already fully qualified DLL name?
 257                    throw new ArgumentException("Please provide a short name, such as 'MySql', not '" + vendor + "'");
 258                }
 259
 260                //shorten: "DbLinqProvider=X;Server=Y" -> ";Server=Y"
 261                connectionString = reProvider.Replace(connectionString, "");
 262            }
 263
 264            typeName = vendor + "Vendor";
 265
 266            try
 267            {
 268#if MONO_STRICT
 269                assembly = typeof (DataContext).Assembly; // System.Data.Linq.dll
 270#else
 271                assembly = Assembly.Load(assemblyName);
 272#endif
 273            }
 274            catch (Exception e)
 275            {
 276                throw new ArgumentException(
 277                        string.Format(
 278                            "Unable to load the `{0}' DbLinq vendor within assembly '{1}.dll'.",
 279                            assemblyName, vendor),
 280                        "connectionString", e);
 281            }
 282        }
 283
 284        private void Init(IDatabaseContext databaseContext, MappingSource mappingSource, IVendor vendor)
 285        {
 286            if (databaseContext == null)
 287                throw new ArgumentNullException("databaseContext");
 288
 289            // Yes, .NET throws an NRE for this.  Why it's not ArgumentNullException, I couldn't tell you.
 290            if (databaseContext.Connection.ConnectionString == null)
 291                throw new NullReferenceException();
 292
 293            string connectionString = databaseContext.Connection.ConnectionString;
 294            _VendorProvider = ObjectFactory.Get<IVendorProvider>();
 295            Vendor = vendor ?? 
 296                (connectionString != null ? GetVendor(ref connectionString) : null) ??
 297                _VendorProvider.FindVendorByProviderType(typeof(SqlClient.Sql2005Provider));
 298            
 299            DatabaseContext = databaseContext;
 300
 301            MemberModificationHandler = ObjectFactory.Create<IMemberModificationHandler>(); // not a singleton: object is stateful
 302            QueryBuilder = ObjectFactory.Get<IQueryBuilder>();
 303            QueryRunner = ObjectFactory.Get<IQueryRunner>();
 304
 305            //EntityMap = ObjectFactory.Create<IEntityMap>();
 306            identityReaderFactory = ObjectFactory.Get<IIdentityReaderFactory>();
 307
 308            _MappingContext = new MappingContext();
 309
 310            // initialize the mapping information
 311            if (mappingSource == null)
 312                mappingSource = new AttributeMappingSource();
 313            Mapping = mappingSource.GetModel(GetType());
 314        }
 315
 316        /// <summary>
 317        /// Checks if the table is allready mapped or maps it if not.
 318        /// </summary>
 319        /// <param name="tableType">Type of the table.</param>
 320        /// <exception cref="InvalidOperationException">Thrown if the table is not mappable.</exception>
 321        private void CheckTableMapping(Type tableType)
 322        {
 323            //This will throw an exception if the table is not found
 324            if(Mapping.GetTable(tableType) == null)
 325            {
 326                throw new InvalidOperationException("The type '" + tableType.Name + "' is not mapped as a Table.");
 327            }
 328        }
 329
 330        /// <summary>
 331        /// Returns a Table for the type TEntity.
 332        /// </summary>
 333        /// <exception cref="InvalidOperationException">If the type TEntity is not mappable as a Table.</exception>
 334        /// <typeparam name="TEntity">The table type.</typeparam>
 335        public Table<TEntity> GetTable<TEntity>() where TEntity : class
 336        {
 337            return (Table<TEntity>)GetTable(typeof(TEntity));
 338        }
 339
 340        /// <summary>
 341        /// Returns a Table for the given type.
 342        /// </summary>
 343        /// <param name="type">The table type.</param>
 344        /// <exception cref="InvalidOperationException">If the type is not mappable as a Table.</exception>
 345        public ITable GetTable(Type type)
 346        {
 347            Profiler.At("DataContext.GetTable(typeof({0}))", type != null ? type.Name : null);
 348            ITable tableExisting;
 349            if (_tableMap.TryGetValue(type, out tableExisting))
 350                return tableExisting;
 351
 352            //Check for table mapping
 353            CheckTableMapping(type);
 354
 355            var tableNew = Activator.CreateInstance(
 356                              typeof(Table<>).MakeGenericType(type)
 357                              , BindingFlags.NonPublic | BindingFlags.Instance
 358                              , null
 359                              , new object[] { this }
 360                              , System.Globalization.CultureInfo.CurrentCulture) as ITable;
 361
 362            _tableMap[type] = tableNew;
 363            return tableNew;
 364        }
 365
 366        public void SubmitChanges()
 367        {
 368            SubmitChanges(ConflictMode.FailOnFirstConflict);
 369        }
 370
 371        /// <summary>
 372        /// Pings database
 373        /// </summary>
 374        /// <returns></returns>
 375        public bool DatabaseExists()
 376        {
 377            try
 378            {
 379                return Vendor.Ping(this);
 380            }
 381            catch (Exception)
 382            {
 383                return false;
 384            }
 385        }
 386
 387        /// <summary>
 388        /// Commits all pending changes to database 
 389        /// </summary>
 390        /// <param name="failureMode"></param>
 391        public virtual void SubmitChanges(ConflictMode failureMode)
 392        {
 393            if (this.objectTrackingEnabled == false)
 394                throw new InvalidOperationException("Object tracking is not enabled for the current data context instance.");
 395            using (DatabaseContext.OpenConnection()) //ConnMgr will close connection for us
 396            {
 397                if (Transaction != null)
 398                    SubmitChangesImpl(failureMode);
 399                else
 400                {
 401                    using (IDbTransaction transaction = DatabaseContext.CreateTransaction())
 402                    {
 403                        try
 404                        {
 405                            Transaction = (DbTransaction) transaction;
 406                            SubmitChangesImpl(failureMode);
 407                            // TODO: handle conflicts (which can only occur when concurrency mode is implemented)
 408                            transaction.Commit();
 409                        }
 410                        finally
 411                        {
 412                            Transaction = null;
 413                        }
 414                    }
 415                }
 416            }
 417        }
 418
 419        void SubmitChangesImpl(ConflictMode failureMode)
 420        {
 421            var queryContext = new QueryContext(this);
 422
 423            // There's no sense in updating an entity when it's going to 
 424            // be deleted in the current transaction, so do deletes first.
 425            foreach (var entityTrack in CurrentTransactionEntities.EnumerateAll().ToList())
 426            {
 427                switch (entityTrack.EntityState)
 428                {
 429                    case EntityState.ToDelete:
 430                        var deleteQuery = QueryBuilder.GetDeleteQuery(entityTrack.Entity, queryContext);
 431                        QueryRunner.Delete(entityTrack.Entity, deleteQuery);
 432
 433                        UnregisterDelete(entityTrack.Entity);
 434                        AllTrackedEntities.RegisterToDelete(entityTrack.Entity);
 435                        AllTrackedEntities.RegisterDeleted(entityTrack.Entity);
 436                        break;
 437                    default:
 438                        // ignore.
 439                        break;
 440                }
 441            }
 442            foreach (var entityTrack in CurrentTransactionEntities.EnumerateAll()
 443                    .Concat(AllTrackedEntities.EnumerateAll())
 444                    .ToList())
 445            {
 446                switch (entityTrack.EntityState)
 447                {
 448                    case EntityState.ToInsert:
 449                        foreach (var toInsert in GetReferencedObjects(entityTrack.Entity))
 450                        {
 451                            InsertEntity(toInsert, queryContext);
 452                        }
 453                        break;
 454                    case EntityState.ToWatch:
 455                        foreach (var toUpdate in GetReferencedObjects(entityTrack.Entity))
 456                        {
 457                            UpdateEntity(toUpdate, queryContext);
 458                        }
 459                        break;
 460                    default:
 461                        throw new ArgumentOutOfRangeException();
 462                }
 463            }
 464        }
 465
 466        private IEnumerable<object> GetReferencedObjects(object value)
 467        {
 468            var values = new EntitySet<object>();
 469            FillReferencedObjects(value, values);
 470            return values;
 471        }
 472
 473        // Breadth-first traversal of an object graph
 474        private void FillReferencedObjects(object parent, EntitySet<object> values)
 475        {
 476            if (parent == null)
 477                return;
 478            var children = new Queue<object>();
 479			children.Enqueue(parent);
 480			while (children.Count > 0)
 481			{
 482                object value = children.Dequeue();
 483                values.Add(value);
 484                IEnumerable<MetaAssociation> associationList = Mapping.GetMetaType(value.GetType()).Associations.Where(a => !a.IsForeignKey);
 485                if (associationList.Any())
 486			    {
 487				    foreach (MetaAssociation association in associationList)
 488                    {
 489                        var memberData = association.ThisMember;
 490                        var entitySetValue = memberData.Member.GetMemberValue(value);
 491
 492                        if (entitySetValue != null)
 493                        {
 494						    var hasLoadedOrAssignedValues = entitySetValue.GetType().GetProperty("HasLoadedOrAssignedValues");
 495						    if (!((bool)hasLoadedOrAssignedValues.GetValue(entitySetValue, null)))
 496							    continue;   // execution deferred; ignore.
 497						    foreach (var o in ((IEnumerable)entitySetValue))
 498							    children.Enqueue(o);
 499					    }
 500                    }
 501                }
 502			}
 503        }
 504
 505        private void InsertEntity(object entity, QueryContext queryContext)
 506        {
 507            var insertQuery = QueryBuilder.GetInsertQuery(entity, queryContext);
 508            QueryRunner.Insert(entity, insertQuery);
 509            Register(entity);
 510            UpdateReferencedObjects(entity);
 511            MoveToAllTrackedEntities(entity, true);
 512        }
 513
 514        private void UpdateEntity(object entity, QueryContext queryContext)
 515        {
 516            if (!AllTrackedEntities.ContainsReference(entity))
 517                InsertEntity(entity, queryContext);
 518            else if (MemberModificationHandler.IsModified(entity, Mapping))
 519            {
 520                var modifiedMembers = MemberModificationHandler.GetModifiedProperties(entity, Mapping);
 521                var updateQuery = QueryBuilder.GetUpdateQuery(entity, modifiedMembers, queryContext);
 522                QueryRunner.Update(entity, updateQuery, modifiedMembers);
 523
 524                RegisterUpdateAgain(entity);
 525                UpdateReferencedObjects(entity);
 526                MoveToAllTrackedEntities(entity, false);
 527            }
 528        }
 529
 530        private void UpdateReferencedObjects(object root)
 531        {
 532            var metaType = Mapping.GetMetaType(root.GetType());
 533            foreach (var assoc in metaType.Associations)
 534            {
 535                var memberData = assoc.ThisMember;
 536				//This is not correct - AutoSyncing applies to auto-updating columns, such as a TimeStamp, not to foreign key associations, which is always automatically synched
 537				//Confirmed against default .NET l2sql - association columns are always set, even if AutoSync==AutoSync.Never
 538				//if (memberData.Association.ThisKey.Any(m => (m.AutoSync != AutoSync.Always) && (m.AutoSync != sync)))
 539                //    continue;
 540                var oks = memberData.Association.OtherKey.Select(m => m.StorageMember).ToList();
 541                if (oks.Count == 0)
 542                    continue;
 543                var pks = memberData.Association.ThisKey
 544                    .Select(m => m.StorageMember.GetMemberValue(root))
 545                    .ToList();
 546                if (pks.Count != oks.Count)
 547                    throw new InvalidOperationException(
 548                        string.Format("Count of primary keys ({0}) doesn't match count of other keys ({1}).",
 549                            pks.Count, oks.Count));
 550                var members = memberData.Member.GetMemberValue(root) as IEnumerable;
 551                if (members == null)
 552                    continue;
 553                foreach (var member in members)
 554                {
 555                    for (int i = 0; i < pks.Count; ++i)
 556                    {
 557                        oks[i].SetMemberValue(member, pks[i]);
 558                    }
 559                }
 560            }
 561        }
 562
 563        private void MoveToAllTrackedEntities(object entity, bool insert)
 564        {
 565            if (!ObjectTrackingEnabled)
 566                return;
 567            if (CurrentTransactionEntities.ContainsReference(entity))
 568            {
 569                CurrentTransactionEntities.RegisterToDelete(entity);
 570                if (!insert)
 571                    CurrentTransactionEntities.RegisterDeleted(entity);
 572            }
 573            if (!AllTrackedEntities.ContainsReference(entity))
 574            {
 575                var identityReader = _GetIdentityReader(entity.GetType());
 576                AllTrackedEntities.RegisterToWatch(entity, identityReader.GetIdentityKey(entity));
 577            }
 578        }
 579
 580        /// <summary>
 581        /// TODO - allow generated methods to call into stored procedures
 582        /// </summary>
 583        [DBLinqExtended]
 584        internal IExecuteResult _ExecuteMethodCall(DataContext context, System.Reflection.MethodInfo method, params object[] sqlParams)
 585        {
 586            using (DatabaseContext.OpenConnection())
 587            {
 588                System.Data.Linq.IExecuteResult result = Vendor.ExecuteMethodCall(context, method, sqlParams);
 589                return result;
 590            }
 591        }
 592
 593        [DbLinqToDo]
 594        protected IExecuteResult ExecuteMethodCall(object instance, System.Reflection.MethodInfo methodInfo, params object[] parameters)
 595        {
 596            throw new NotImplementedException();
 597        }
 598
 599        #region Identity management
 600
 601        [DBLinqExtended]
 602        internal IIdentityReader _GetIdentityReader(Type t)
 603        {
 604            IIdentityReader identityReader;
 605            if (!identityReaders.TryGetValue(t, out identityReader))
 606            {
 607                identityReader = identityReaderFactory.GetReader(t, this);
 608                identityReaders[t] = identityReader;
 609            }
 610            return identityReader;
 611        }
 612
 613        [DBLinqExtended]
 614        internal object _GetRegisteredEntity(object entity)
 615        {
 616            // TODO: check what is faster: by identity or by ref
 617            var identityReader = _GetIdentityReader(entity.GetType());
 618            var identityKey = identityReader.GetIdentityKey(entity);
 619            if (identityKey == null) // if we don't have an entitykey here, it means that the entity has no PK
 620                return entity;
 621            // even 
 622            var registeredEntityTrack = 
 623                CurrentTransactionEntities.FindByIdentity(identityKey) ??
 624                AllTrackedEntities.FindByIdentity(identityKey);
 625            if (registeredEntityTrack != null)
 626                return registeredEntityTrack.Entity;
 627            return null;
 628        }
 629
 630        //internal object GetRegisteredEntityByKey(IdentityKey identityKey)
 631        //{
 632        //    return EntityMap[identityKey];
 633        //}
 634
 635        /// <summary>
 636        /// Registers an entity in a watch state
 637        /// </summary>
 638        /// <param name="entity"></param>
 639        /// <returns></returns>
 640        [DBLinqExtended]
 641        internal object _GetOrRegisterEntity(object entity)
 642        {
 643            var identityReader = _GetIdentityReader(entity.GetType());
 644            var identityKey = identityReader.GetIdentityKey(entity);
 645            SetEntitySetsQueries(entity);
 646            SetEntityRefQueries(entity);
 647
 648            // if we have no identity, we can't track it
 649            if (identityKey == null)
 650                return entity;
 651
 652            // try to find an already registered entity and return it
 653            var registeredEntityTrack = 
 654                CurrentTransactionEntities.FindByIdentity(identityKey) ??
 655                AllTrackedEntities.FindByIdentity(identityKey);
 656            if (registeredEntityTrack != null)
 657                return registeredEntityTrack.Entity;
 658
 659            // otherwise, register and return
 660            AllTrackedEntities.RegisterToWatch(entity, identityKey);
 661            return entity;
 662        }
 663
 664        readonly IDataMapper DataMapper = ObjectFactory.Get<IDataMapper>();
 665		private void SetEntityRefQueries(object entity)
 666		{
 667            if (!this.deferredLoadingEnabled)
 668                return;
 669
 670            // BUG: This is ignoring External Mappings from XmlMappingSource.
 671
 672			Type thisType = entity.GetType();
 673			IEnumerable<MetaAssociation> associationList = Mapping.GetMetaType(entity.GetType()).Associations.Where(a => a.IsForeignKey);
 674			foreach (MetaAssociation association in associationList)
 675			{
 676				//example of entityRef:Order.Employee
 677				var memberData = association.ThisMember;
 678				Type otherTableType = association.OtherType.Type;
 679				ParameterExpression p = Expression.Parameter(otherTableType, "other");
 680
 681				var otherTable = GetTable(otherTableType);
 682
 683				//ie:EmployeeTerritories.EmployeeID
 684				var foreignKeys = memberData.Association.ThisKey;
 685				BinaryExpression predicate = null;
 686				var otherPKs = memberData.Association.OtherKey;
 687				IEnumerator<MetaDataMember> otherPKEnumerator = otherPKs.GetEnumerator();
 688
 689				if (otherPKs.Count != foreignKeys.Count)
 690					throw new InvalidOperationException("Foreign keys don't match ThisKey");
 691				foreach (MetaDataMember key in foreignKeys)
 692				{
 693					otherPKEnumerator.MoveNext();
 694
 695					var thisForeignKeyProperty = (PropertyInfo)key.Member;
 696					object thisForeignKeyValue = thisForeignKeyProperty.GetValue(entity, null);
 697
 698					if (thisForeignKeyValue != null)
 699					{
 700						BinaryExpression keyPredicate;
 701						if (!(thisForeignKeyProperty.PropertyType.IsNullable()))
 702						{
 703							keyPredicate = Expression.Equal(Expression.MakeMemberAccess(p, otherPKEnumerator.Current.Member),
 704																		Expression.Constant(thisForeignKeyValue));
 705						}
 706						else
 707						{
 708							var ValueProperty = thisForeignKeyProperty.PropertyType.GetProperty("Value");
 709							keyPredicate = Expression.Equal(Expression.MakeMemberAccess(p, otherPKEnumerator.Current.Member),
 710																	 Expression.Constant(ValueProperty.GetValue(thisForeignKeyValue, null)));
 711						}
 712
 713						if (predicate == null)
 714							predicate = keyPredicate;
 715						else
 716							predicate = Expression.And(predicate, keyPredicate);
 717					}
 718				}
 719				IEnumerable query = null;
 720				if (predicate != null)
 721				{
 722					query = GetOtherTableQuery(predicate, p, otherTableType, otherTable) as IEnumerable;
 723					//it would be interesting surround the above query with a .Take(1) expression for performance.
 724				}
 725
 726				// If no separate Storage is specified, use the member directly
 727				MemberInfo storage = memberData.StorageMember;
 728				if (storage == null)
 729					storage = memberData.Member;
 730
 731				 // Check that the storage is a field or a writable property
 732				if (!(storage is FieldInfo) && !(storage is PropertyInfo && ((PropertyInfo)storage).CanWrite)) {
 733					throw new InvalidOperationException(String.Format(
 734						"Member {0}.{1} is not a field nor a writable property",
 735						storage.DeclaringType, storage.Name));
 736				}
 737
 738				Type storageType = storage.GetMemberType();
 739
 740				object entityRefValue = null;
 741				if (query != null)
 742					entityRefValue = Activator.CreateInstance(storageType, query);
 743				else
 744					entityRefValue = Activator.CreateInstance(storageType);
 745
 746				storage.SetMemberValue(entity, entityRefValue);
 747			}
 748		}
 749
 750        /// <summary>
 751        /// This method is executed when the entity is being registered. Each EntitySet property has a internal query that can be set using the EntitySet.SetSource method.
 752        /// Here we set the query source of each EntitySetProperty
 753        /// </summary>
 754        /// <param name="entity"></param>
 755        private void SetEntitySetsQueries(object entity)
 756        {
 757            if (!this.deferredLoadingEnabled)
 758                return;
 759
 760            // BUG: This is ignoring External Mappings from XmlMappingSource.
 761
 762			IEnumerable<MetaAssociation> associationList = Mapping.GetMetaType(entity.GetType()).Associations.Where(a => !a.IsForeignKey);
 763
 764			if (associationList.Any())
 765			{
 766				foreach (MetaAssociation association in associationList)
 767                {
 768					//example of entitySet: Employee.EmployeeTerritories
 769					var memberData = association.ThisMember;
 770					Type otherTableType = association.OtherType.Type;
 771                    ParameterExpression p = Expression.Parameter(otherTableType, "other");
 772
 773                    //other table:EmployeeTerritories
 774                    var otherTable = GetTable(otherTableType);
 775
 776					var otherKeys = memberData.Association.OtherKey;
 777					var thisKeys = memberData.Association.ThisKey;
 778                    if (otherKeys.Count != thisKeys.Count)
 779                        throw new InvalidOperationException("This keys don't match OtherKey");
 780                    BinaryExpression predicate = null;
 781                    IEnumerator<MetaDataMember> thisKeyEnumerator = thisKeys.GetEnumerator();
 782					foreach (MetaDataMember otherKey in otherKeys)
 783                    {
 784                        thisKeyEnumerator.MoveNext();
 785                        //other table member:EmployeeTerritories.EmployeeID
 786						var otherTableMember = (PropertyInfo)otherKey.Member;
 787
 788                        BinaryExpression keyPredicate;
 789                        if (!(otherTableMember.PropertyType.IsNullable()))
 790                        {
 791                            keyPredicate = Expression.Equal(Expression.MakeMemberAccess(p, otherTableMember),
 792                                                                        Expression.Constant(thisKeyEnumerator.Current.Member.GetMemberValue(entity)));
 793                        }
 794                        else
 795                        {
 796                            var ValueProperty = otherTableMember.PropertyType.GetProperty("Value");
 797                            keyPredicate = Expression.Equal(Expression.MakeMemberAccess(
 798                                                                        Expression.MakeMemberAccess(p, otherTableMember),
 799                                                                        ValueProperty),
 800                                                                     Expression.Constant(thisKeyEnumerator.Current.Member.GetMemberValue(entity)));
 801                        }
 802                        if (predicate == null)
 803                            predicate = keyPredicate;
 804                        else
 805                            predicate = Expression.And(predicate, keyPredicate);
 806                    }
 807
 808                    var query = GetOtherTableQuery(predicate, p, otherTableType, otherTable);
 809
 810					var entitySetValue = memberData.Member.GetMemberValue(entity);
 811
 812                    if (entitySetValue == null)
 813                    {
 814						entitySetValue = Activator.CreateInstance(memberData.Member.GetMemberType());
 815						memberData.Member.SetMemberValue(entity, entitySetValue);
 816                    }
 817
 818                    var hasLoadedOrAssignedValues = entitySetValue.GetType().GetProperty("HasLoadedOrAssignedValues");
 819                    if ((bool)hasLoadedOrAssignedValues.GetValue(entitySetValue, null))
 820                        continue;
 821
 822                    var setSourceMethod = entitySetValue.GetType().GetMethod("SetSource");
 823                    setSourceMethod.Invoke(entitySetValue, new[] { query });
 824                    //employee.EmployeeTerritories.SetSource(Table[EmployeesTerritories].Where(other=>other.employeeID="WARTH"))
 825                }
 826            }
 827        }
 828
 829		private static MethodInfo _WhereMethod = typeof(Queryable).GetMethods().First(m => m.Name == "Where");
 830        internal object GetOtherTableQuery(Expression predicate, ParameterExpression parameter, Type otherTableType, IQueryable otherTable)
 831        {
 832            //predicate: other.EmployeeID== "WARTH"
 833            Expression lambdaPredicate = Expression.Lambda(predicate, parameter);
 834            //lambdaPredicate: other=>other.EmployeeID== "WARTH"
 835
 836			Expression call = Expression.Call(_WhereMethod.MakeGenericMethod(otherTableType), otherTable.Expression, lambdaPredicate);
 837            //Table[EmployeesTerritories].Where(other=>other.employeeID="WARTH")
 838
 839            return otherTable.Provider.CreateQuery(call);
 840        }
 841
 842        #endregion
 843
 844        #region Insert/Update/Delete management
 845
 846        /// <summary>
 847        /// Registers an entity for insert
 848        /// </summary>
 849        /// <param name="entity"></param>
 850        internal void RegisterInsert(object entity)
 851        {
 852            CurrentTransactionEntities.RegisterToInsert(entity);
 853        }
 854
 855        private void DoRegisterUpdate(object entity)
 856        {
 857            if (entity == null)
 858                throw new ArgumentNullException("entity");
 859
 860            if (!this.objectTrackingEnabled)
 861                return;
 862
 863            var identityReader = _GetIdentityReader(entity.GetType());
 864            var identityKey = identityReader.GetIdentityKey(entity);
 865            // if we have no key, we can not watch
 866            if (identityKey == null || identityKey.Keys.Count == 0)
 867                return;
 868            // register entity
 869            AllTrackedEntities.RegisterToWatch(entity, identityKey);
 870        }
 871
 872        /// <summary>
 873        /// Registers an entity for update
 874        /// The entity will be updated only if some of its members have changed after the registration
 875        /// </summary>
 876        /// <param name="entity"></param>
 877        internal void RegisterUpdate(object entity)
 878        {
 879            DoRegisterUpdate(entity);
 880			MemberModificationHandler.Register(entity, Mapping);
 881        }
 882
 883        /// <summary>
 884        /// Registers or re-registers an entity and clears its state
 885        /// </summary>
 886        /// <param name="entity"></param>
 887        /// <returns></returns>
 888        internal object Register(object entity)
 889        {
 890            if (! this.objectTrackingEnabled)
 891                return entity;
 892            var registeredEntity = _GetOrRegisterEntity(entity);
 893            // the fact of registering again clears the modified state, so we're... clear with that
 894            MemberModificationHandler.Register(registeredEntity, Mapping);
 895            return registeredEntity;
 896        }
 897
 898        /// <summary>
 899        /// Registers an entity for update
 900        /// The entity will be updated only if some of its members have changed after the registration
 901        /// </summary>
 902        /// <param name="entity"></param>
 903        /// <param name="entityOriginalState"></param>
 904        internal void RegisterUpdate(object entity, object entityOriginalState)
 905        {
 906            if (!this.objectTrackingEnabled)
 907                return;
 908            DoRegisterUpdate(entity);
 909            MemberModificationHandler.Register(entity, entityOriginalState, Mapping);
 910        }
 911
 912        /// <summary>
 913        /// Clears the current state, and marks the object as clean
 914        /// </summary>
 915        /// <param name="entity"></param>
 916        internal void RegisterUpdateAgain(object entity)
 917        {
 918            if (!this.objectTrackingEnabled)
 919                return;
 920            MemberModificationHandler.ClearModified(entity, Mapping);
 921        }
 922
 923        /// <summary>
 924        /// Registers an entity for delete
 925        /// </summary>
 926        /// <param name="entity"></param>
 927        internal void RegisterDelete(object entity)
 928        {
 929            if (!this.objectTrackingEnabled)
 930                return;
 931            CurrentTransactionEntities.RegisterToDelete(entity);
 932        }
 933
 934        /// <summary>
 935        /// Unregisters entity after deletion
 936        /// </summary>
 937        /// <param name="entity"></param>
 938        internal void UnregisterDelete(object entity)
 939        {
 940            if (!this.objectTrackingEnabled)
 941                return;
 942            CurrentTransactionEntities.RegisterDeleted(entity);
 943        }
 944
 945        #endregion
 946
 947        /// <summary>
 948        /// Changed object determine 
 949        /// </summary>
 950        /// <returns>Lists of inserted, updated, deleted objects</returns>
 951        public ChangeSet GetChangeSet()
 952        {
 953            var inserts = new List<object>();
 954            var updates = new List<object>();
 955            var deletes = new List<object>();
 956            foreach (var entityTrack in CurrentTransactionEntities.EnumerateAll()
 957                    .Concat(AllTrackedEntities.EnumerateAll()))
 958            {
 959                switch (entityTrack.EntityState)
 960                {
 961                    case EntityState.ToInsert:
 962                        inserts.Add(entityTrack.Entity);
 963                        break;
 964                    case EntityState.ToWatch:
 965                        if (MemberModificationHandler.IsModified(entityTrack.Entity, Mapping))
 966                            updates.Add(entityTrack.Entity);
 967                        break;
 968                    case EntityState.ToDelete:
 969                        deletes.Add(entityTrack.Entity);
 970                        break;
 971                    default:
 972                        throw new ArgumentOutOfRangeException();
 973                }
 974            }
 975            return new ChangeSet(inserts, updates, deletes);
 976        }
 977
 978        /// <summary>
 979        /// use ExecuteCommand to call raw SQL
 980        /// </summary>
 981        public int ExecuteCommand(string command, params object[] parameters)
 982        {
 983            var directQuery = QueryBuilder.GetDirectQuery(command, new QueryContext(this));
 984            return QueryRunner.Execute(directQuery, parameters);
 985        }
 986
 987        /// <summary>
 988        /// Execute raw SQL query and return object
 989        /// </summary>
 990        public IEnumerable<TResult> ExecuteQuery<TResult>(string query, params object[] parameters) where TResult : class, new()
 991        {
 992            if (query == null)
 993                throw new ArgumentNullException("query");
 994
 995            return CreateExecuteQueryEnumerable<TResult>(query, parameters);
 996        }
 997
 998        private IEnumerable<TResult> CreateExecuteQueryEnumerable<TResult>(string query, object[] parameters)
 999            where TResult : class, new()
1000        {
1001            foreach (TResult result in ExecuteQuery(typeof(TResult), query, parameters))
1002                yield return result;
1003        }
1004
1005        public IEnumerable ExecuteQuery(Type elementType, string query, params object[] parameters)
1006        {
1007            if (elementType == null)
1008                throw new ArgumentNullException("elementType");
1009            if (query == null)
1010                throw new ArgumentNullException("query");
1011
1012            var queryContext = new QueryContext(this);
1013            var directQuery = QueryBuilder.GetDirectQuery(query, queryContext);
1014            return QueryRunner.ExecuteSelect(elementType, directQuery, parameters);
1015        }
1016
1017        /// <summary>
1018        /// Gets or sets the load options
1019        /// </summary>
1020        [DbLinqToDo]
1021		public DataLoadOptions LoadOptions
1022		{
1023			get { throw new NotImplementedException(); }
1024			set { throw new NotImplementedException(); }
1025		}
1026
1027        public DbTransaction Transaction {
1028            get { return (DbTransaction) DatabaseContext.CurrentTransaction; }
1029            set { DatabaseContext.CurrentTransaction = value; }
1030        }
1031
1032        /// <summary>
1033        /// Runs the given reader and returns columns.
1034        /// </summary>
1035        /// <typeparam name="TResult">The type of the result.</typeparam>
1036        /// <param name="reader">The reader.</param>
1037        /// <returns></returns>
1038        public IEnumerable<TResult> Translate<TResult>(DbDataReader reader)
1039        {
1040            if (reader == null)
1041                throw new ArgumentNullException("reader");
1042            return CreateTranslateIterator<TResult>(reader);
1043        }
1044
1045        IEnumerable<TResult> CreateTranslateIterator<TResult>(DbDataReader reader)
1046        {
1047            foreach (TResult result in Translate(typeof(TResult), reader))
1048                yield return result;
1049        }
1050
1051        public IMultipleResults Translate(DbDataReader reader)
1052        {
1053            throw new NotImplementedException();
1054        }
1055
1056        public IEnumerable Translate(Type elementType, DbDataReader reader)
1057        {
1058            if (elementType == null)
1059                throw new ArgumentNullException("elementType");
1060            if (reader == null)
1061                throw new ArgumentNullException("reader");
1062
1063            return QueryRunner.EnumerateResult(elementType, reader, this);
1064        }
1065
1066        public void Dispose()
1067        {
1068            //connection closing should not be done here.
1069            //read: http://msdn2.microsoft.com/en-us/library/bb292288.aspx
1070
1071			//We own the instance of MemberModificationHandler - we must unregister listeners of entities we attached to
1072			MemberModificationHandler.UnregisterAll();
1073        }
1074
1075        [DbLinqToDo]
1076        protected virtual void Dispose(bool disposing)
1077        {
1078            throw new NotImplementedException();
1079        }
1080
1081        /// <summary>
1082        /// Creates a IDbDataAdapter. Used internally by Vendors
1083        /// </summary>
1084        /// <returns></returns>
1085        internal IDbDataAdapter CreateDataAdapter()
1086        {
1087            return DatabaseContext.CreateDataAdapter();
1088        }
1089
1090        /// <summary>
1091        /// Sets a TextWriter where generated SQL commands are written
1092        /// </summary>
1093        public TextWriter Log { get; set; }
1094
1095        /// <summary>
1096        /// Writes text on Log (if not null)
1097        /// Internal helper
1098        /// </summary>
1099        /// <param name="text"></param>
1100        internal void WriteLog(string text)
1101        {
1102            if (Log != null)
1103                Log.WriteLine(text);
1104        }
1105
1106        /// <summary>
1107        /// Write an IDbCommand to Log (if non null)
1108        /// </summary>
1109        /// <param name="command"></param>
1110        internal void WriteLog(IDbCommand command)
1111        {
1112            if (Log != null)
1113            {
1114                Log.WriteLine(command.CommandText);
1115                foreach (IDbDataParameter parameter in command.Parameters)
1116                    WriteLog(parameter);
1117                Log.Write("--");
1118                Log.Write(" Context: {0}", Vendor.VendorName);
1119                Log.Write(" Model: {0}", Mapping.GetType().Name);
1120                Log.Write(" Build: {0}", Assembly.GetExecutingAssembly().GetName().Version);
1121                Log.WriteLine();
1122            }
1123        }
1124
1125        /// <summary>
1126        /// Writes and IDbDataParameter to Log (if non null)
1127        /// </summary>
1128        /// <param name="parameter"></param>
1129        internal void WriteLog(IDbDataParameter parameter)
1130        {
1131            if (Log != null)
1132            {
1133                // -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [2]
1134                // -- <name>: <direction> <type> (...) [<value>]
1135                Log.WriteLine("-- {0}: {1} {2} (Size = {3}; Prec = {4}; Scale = {5}) [{6}]",
1136                    parameter.ParameterName, parameter.Direction, parameter.DbType,
1137                    parameter.Size, parameter.Precision, parameter.Scale, parameter.Value);
1138            }
1139        }
1140
1141        public bool ObjectTrackingEnabled
1142        {
1143            get { return this.objectTrackingEnabled; }
1144            set 
1145            {
1146                if (this.currentTransactionEntities != null && value != this.objectTrackingEnabled)
1147                    throw new InvalidOperationException("Data context options cannot be modified after results have been returned from a query.");
1148                this.objectTrackingEnabled = value;
1149            }
1150        }
1151
1152        [DbLinqToDo]
1153        public int CommandTimeout
1154        {
1155            get { throw new NotImplementedException(); }
1156            set { throw new NotImplementedException(); }
1157        }
1158
1159        public bool DeferredLoadingEnabled
1160        {
1161            get { return this.deferredLoadingEnabled; }
1162            set
1163            {
1164                if (this.currentTransactionEntities != null && value != this.deferredLoadingEnabled)
1165                    throw new InvalidOperationException("Data context options cannot be modified after results have been returned from a query.");
1166                this.deferredLoadingEnabled = value;
1167            }
1168        }
1169
1170        [DbLinqToDo]
1171        public ChangeConflictCollection ChangeConflicts
1172        {
1173            get { throw new NotImplementedException(); }
1174        }
1175
1176        [DbLinqToDo]
1177        public DbCommand GetCommand(IQueryable query)
1178        {
1179            DbCommand dbCommand = GetIDbCommand(query) as DbCommand;
1180            if (dbCommand == null)
1181                throw new InvalidOperationException();
1182
1183            return dbCommand;
1184        }
1185
1186        [DBLinqExtended]
1187        public IDbCommand GetIDbCommand(IQueryable query)
1188        {
1189            if (query == null)
1190                throw new ArgumentNullException("query");
1191
1192            var qp = query.Provider as QueryProvider;
1193            if (qp == null)
1194                throw new InvalidOperationException();
1195
1196            if (qp.ExpressionChain.Expressions.Count == 0)
1197                qp.ExpressionChain.Expressions.Add(CreateDefaultQuery(query));
1198
1199            return qp.GetQuery(null).GetCommand().Command;
1200        }
1201
1202        private Expression CreateDefaultQuery(IQueryable query)
1203        {
1204            // Manually create the expression tree for: IQueryable<TableType>.Select(e => e)
1205            var identityParameter = Expression.Parameter(query.ElementType, "e");
1206            var identityBody = Expression.Lambda(
1207                typeof(Func<,>).MakeGenericType(query.ElementType, query.ElementType),
1208                identityParameter,
1209                new[] { identityParameter }
1210            );
1211
1212            return Expression.Call(
1213                typeof(Queryable),
1214                "Select",
1215                new[] { query.ElementType, query.ElementType },
1216                query.Expression,
1217                Expression.Quote(identityBody)
1218            );
1219        }
1220
1221        [DbLinqToDo]
1222        public void Refresh(RefreshMode mode, IEnumerable entities)
1223        {
1224            throw 

Large files files are truncated, but you can click here to view the full file