PageRenderTime 51ms CodeModel.GetById 15ms RepoModel.GetById 0ms 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
Possible License(s): MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0, Unlicense, Apache-2.0, LGPL-2.0

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

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