PageRenderTime 26ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

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

https://github.com/ztfuqingvip/mono
C# | 1279 lines | 916 code | 157 blank | 206 comment | 145 complexity | 00a2d65bb44274109ac30fa39c712d6f MD5 | raw file
Possible License(s): GPL-2.0, Unlicense, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.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. #if MOBILE
  261. _VendorProvider.FindVendorByProviderType(typeof(DbLinq.Sqlite.SqliteSqlProvider));
  262. #else
  263. _VendorProvider.FindVendorByProviderType(typeof(SqlClient.Sql2005Provider));
  264. #endif
  265. DatabaseContext = databaseContext;
  266. MemberModificationHandler = ObjectFactory.Create<IMemberModificationHandler>(); // not a singleton: object is stateful
  267. QueryBuilder = ObjectFactory.Get<IQueryBuilder>();
  268. QueryRunner = ObjectFactory.Get<IQueryRunner>();
  269. //EntityMap = ObjectFactory.Create<IEntityMap>();
  270. identityReaderFactory = ObjectFactory.Get<IIdentityReaderFactory>();
  271. _MappingContext = new MappingContext();
  272. // initialize the mapping information
  273. if (mappingSource == null)
  274. mappingSource = new AttributeMappingSource();
  275. Mapping = mappingSource.GetModel(GetType());
  276. }
  277. /// <summary>
  278. /// Checks if the table is allready mapped or maps it if not.
  279. /// </summary>
  280. /// <param name="tableType">Type of the table.</param>
  281. /// <exception cref="InvalidOperationException">Thrown if the table is not mappable.</exception>
  282. private void CheckTableMapping(Type tableType)
  283. {
  284. //This will throw an exception if the table is not found
  285. if(Mapping.GetTable(tableType) == null)
  286. {
  287. throw new InvalidOperationException("The type '" + tableType.Name + "' is not mapped as a Table.");
  288. }
  289. }
  290. /// <summary>
  291. /// Returns a Table for the type TEntity.
  292. /// </summary>
  293. /// <exception cref="InvalidOperationException">If the type TEntity is not mappable as a Table.</exception>
  294. /// <typeparam name="TEntity">The table type.</typeparam>
  295. public Table<TEntity> GetTable<TEntity>() where TEntity : class
  296. {
  297. return (Table<TEntity>)GetTable(typeof(TEntity));
  298. }
  299. /// <summary>
  300. /// Returns a Table for the given type.
  301. /// </summary>
  302. /// <param name="type">The table type.</param>
  303. /// <exception cref="InvalidOperationException">If the type is not mappable as a Table.</exception>
  304. public ITable GetTable(Type type)
  305. {
  306. Profiler.At("DataContext.GetTable(typeof({0}))", type != null ? type.Name : null);
  307. ITable tableExisting;
  308. if (_tableMap.TryGetValue(type, out tableExisting))
  309. return tableExisting;
  310. //Check for table mapping
  311. CheckTableMapping(type);
  312. var tableNew = Activator.CreateInstance(
  313. typeof(Table<>).MakeGenericType(type)
  314. , BindingFlags.NonPublic | BindingFlags.Instance
  315. , null
  316. , new object[] { this }
  317. , System.Globalization.CultureInfo.CurrentCulture) as ITable;
  318. _tableMap[type] = tableNew;
  319. return tableNew;
  320. }
  321. public void SubmitChanges()
  322. {
  323. SubmitChanges(ConflictMode.FailOnFirstConflict);
  324. }
  325. /// <summary>
  326. /// Pings database
  327. /// </summary>
  328. /// <returns></returns>
  329. public bool DatabaseExists()
  330. {
  331. try
  332. {
  333. return Vendor.Ping(this);
  334. }
  335. catch (Exception)
  336. {
  337. return false;
  338. }
  339. }
  340. /// <summary>
  341. /// Commits all pending changes to database
  342. /// </summary>
  343. /// <param name="failureMode"></param>
  344. public virtual void SubmitChanges(ConflictMode failureMode)
  345. {
  346. if (this.objectTrackingEnabled == false)
  347. throw new InvalidOperationException("Object tracking is not enabled for the current data context instance.");
  348. using (DatabaseContext.OpenConnection()) //ConnMgr will close connection for us
  349. {
  350. if (Transaction != null)
  351. SubmitChangesImpl(failureMode);
  352. else
  353. {
  354. using (IDbTransaction transaction = DatabaseContext.CreateTransaction())
  355. {
  356. try
  357. {
  358. Transaction = (DbTransaction) transaction;
  359. SubmitChangesImpl(failureMode);
  360. // TODO: handle conflicts (which can only occur when concurrency mode is implemented)
  361. transaction.Commit();
  362. }
  363. finally
  364. {
  365. Transaction = null;
  366. }
  367. }
  368. }
  369. }
  370. }
  371. void SubmitChangesImpl(ConflictMode failureMode)
  372. {
  373. var queryContext = new QueryContext(this);
  374. // There's no sense in updating an entity when it's going to
  375. // be deleted in the current transaction, so do deletes first.
  376. foreach (var entityTrack in CurrentTransactionEntities.EnumerateAll().ToList())
  377. {
  378. switch (entityTrack.EntityState)
  379. {
  380. case EntityState.ToDelete:
  381. var deleteQuery = QueryBuilder.GetDeleteQuery(entityTrack.Entity, queryContext);
  382. QueryRunner.Delete(entityTrack.Entity, deleteQuery);
  383. UnregisterDelete(entityTrack.Entity);
  384. AllTrackedEntities.RegisterToDelete(entityTrack.Entity);
  385. AllTrackedEntities.RegisterDeleted(entityTrack.Entity);
  386. break;
  387. default:
  388. // ignore.
  389. break;
  390. }
  391. }
  392. foreach (var entityTrack in CurrentTransactionEntities.EnumerateAll()
  393. .Concat(AllTrackedEntities.EnumerateAll())
  394. .ToList())
  395. {
  396. switch (entityTrack.EntityState)
  397. {
  398. case EntityState.ToInsert:
  399. foreach (var toInsert in GetReferencedObjects(entityTrack.Entity))
  400. {
  401. InsertEntity(toInsert, queryContext);
  402. }
  403. break;
  404. case EntityState.ToWatch:
  405. foreach (var toUpdate in GetReferencedObjects(entityTrack.Entity))
  406. {
  407. UpdateEntity(toUpdate, queryContext);
  408. }
  409. break;
  410. default:
  411. throw new ArgumentOutOfRangeException();
  412. }
  413. }
  414. }
  415. private IEnumerable<object> GetReferencedObjects(object value)
  416. {
  417. var values = new EntitySet<object>();
  418. FillReferencedObjects(value, values);
  419. return values;
  420. }
  421. // Breadth-first traversal of an object graph
  422. private void FillReferencedObjects(object parent, EntitySet<object> values)
  423. {
  424. if (parent == null)
  425. return;
  426. var children = new Queue<object>();
  427. children.Enqueue(parent);
  428. while (children.Count > 0)
  429. {
  430. object value = children.Dequeue();
  431. values.Add(value);
  432. IEnumerable<MetaAssociation> associationList = Mapping.GetMetaType(value.GetType()).Associations.Where(a => !a.IsForeignKey);
  433. if (associationList.Any())
  434. {
  435. foreach (MetaAssociation association in associationList)
  436. {
  437. var memberData = association.ThisMember;
  438. var entitySetValue = memberData.Member.GetMemberValue(value);
  439. if (entitySetValue != null)
  440. {
  441. var hasLoadedOrAssignedValues = entitySetValue.GetType().GetProperty("HasLoadedOrAssignedValues");
  442. if (!((bool)hasLoadedOrAssignedValues.GetValue(entitySetValue, null)))
  443. continue; // execution deferred; ignore.
  444. foreach (var o in ((IEnumerable)entitySetValue))
  445. children.Enqueue(o);
  446. }
  447. }
  448. }
  449. }
  450. }
  451. private void InsertEntity(object entity, QueryContext queryContext)
  452. {
  453. var insertQuery = QueryBuilder.GetInsertQuery(entity, queryContext);
  454. QueryRunner.Insert(entity, insertQuery);
  455. Register(entity);
  456. UpdateReferencedObjects(entity);
  457. MoveToAllTrackedEntities(entity, true);
  458. }
  459. private void UpdateEntity(object entity, QueryContext queryContext)
  460. {
  461. if (!AllTrackedEntities.ContainsReference(entity))
  462. InsertEntity(entity, queryContext);
  463. else if (MemberModificationHandler.IsModified(entity, Mapping))
  464. {
  465. var modifiedMembers = MemberModificationHandler.GetModifiedProperties(entity, Mapping);
  466. var updateQuery = QueryBuilder.GetUpdateQuery(entity, modifiedMembers, queryContext);
  467. QueryRunner.Update(entity, updateQuery, modifiedMembers);
  468. RegisterUpdateAgain(entity);
  469. UpdateReferencedObjects(entity);
  470. MoveToAllTrackedEntities(entity, false);
  471. }
  472. }
  473. private void UpdateReferencedObjects(object root)
  474. {
  475. var metaType = Mapping.GetMetaType(root.GetType());
  476. foreach (var assoc in metaType.Associations)
  477. {
  478. var memberData = assoc.ThisMember;
  479. //This is not correct - AutoSyncing applies to auto-updating columns, such as a TimeStamp, not to foreign key associations, which is always automatically synched
  480. //Confirmed against default .NET l2sql - association columns are always set, even if AutoSync==AutoSync.Never
  481. //if (memberData.Association.ThisKey.Any(m => (m.AutoSync != AutoSync.Always) && (m.AutoSync != sync)))
  482. // continue;
  483. var oks = memberData.Association.OtherKey.Select(m => m.StorageMember).ToList();
  484. if (oks.Count == 0)
  485. continue;
  486. var pks = memberData.Association.ThisKey
  487. .Select(m => m.StorageMember.GetMemberValue(root))
  488. .ToList();
  489. if (pks.Count != oks.Count)
  490. throw new InvalidOperationException(
  491. string.Format("Count of primary keys ({0}) doesn't match count of other keys ({1}).",
  492. pks.Count, oks.Count));
  493. var members = memberData.Member.GetMemberValue(root) as IEnumerable;
  494. if (members == null)
  495. continue;
  496. foreach (var member in members)
  497. {
  498. for (int i = 0; i < pks.Count; ++i)
  499. {
  500. oks[i].SetMemberValue(member, pks[i]);
  501. }
  502. }
  503. }
  504. }
  505. private void MoveToAllTrackedEntities(object entity, bool insert)
  506. {
  507. if (!ObjectTrackingEnabled)
  508. return;
  509. if (CurrentTransactionEntities.ContainsReference(entity))
  510. {
  511. CurrentTransactionEntities.RegisterToDelete(entity);
  512. if (!insert)
  513. CurrentTransactionEntities.RegisterDeleted(entity);
  514. }
  515. if (!AllTrackedEntities.ContainsReference(entity))
  516. {
  517. var identityReader = _GetIdentityReader(entity.GetType());
  518. AllTrackedEntities.RegisterToWatch(entity, identityReader.GetIdentityKey(entity));
  519. }
  520. }
  521. /// <summary>
  522. /// TODO - allow generated methods to call into stored procedures
  523. /// </summary>
  524. [DBLinqExtended]
  525. internal IExecuteResult _ExecuteMethodCall(DataContext context, System.Reflection.MethodInfo method, params object[] sqlParams)
  526. {
  527. using (DatabaseContext.OpenConnection())
  528. {
  529. System.Data.Linq.IExecuteResult result = Vendor.ExecuteMethodCall(context, method, sqlParams);
  530. return result;
  531. }
  532. }
  533. [DbLinqToDo]
  534. protected IExecuteResult ExecuteMethodCall(object instance, System.Reflection.MethodInfo methodInfo, params object[] parameters)
  535. {
  536. throw new NotImplementedException();
  537. }
  538. #region Identity management
  539. [DBLinqExtended]
  540. internal IIdentityReader _GetIdentityReader(Type t)
  541. {
  542. IIdentityReader identityReader;
  543. if (!identityReaders.TryGetValue(t, out identityReader))
  544. {
  545. identityReader = identityReaderFactory.GetReader(t, this);
  546. identityReaders[t] = identityReader;
  547. }
  548. return identityReader;
  549. }
  550. [DBLinqExtended]
  551. internal object _GetRegisteredEntity(object entity)
  552. {
  553. // TODO: check what is faster: by identity or by ref
  554. var identityReader = _GetIdentityReader(entity.GetType());
  555. var identityKey = identityReader.GetIdentityKey(entity);
  556. if (identityKey == null) // if we don't have an entitykey here, it means that the entity has no PK
  557. return entity;
  558. // even
  559. var registeredEntityTrack =
  560. CurrentTransactionEntities.FindByIdentity(identityKey) ??
  561. AllTrackedEntities.FindByIdentity(identityKey);
  562. if (registeredEntityTrack != null)
  563. return registeredEntityTrack.Entity;
  564. return null;
  565. }
  566. //internal object GetRegisteredEntityByKey(IdentityKey identityKey)
  567. //{
  568. // return EntityMap[identityKey];
  569. //}
  570. /// <summary>
  571. /// Registers an entity in a watch state
  572. /// </summary>
  573. /// <param name="entity"></param>
  574. /// <returns></returns>
  575. [DBLinqExtended]
  576. internal object _GetOrRegisterEntity(object entity)
  577. {
  578. var identityReader = _GetIdentityReader(entity.GetType());
  579. var identityKey = identityReader.GetIdentityKey(entity);
  580. SetEntitySetsQueries(entity);
  581. SetEntityRefQueries(entity);
  582. // if we have no identity, we can't track it
  583. if (identityKey == null)
  584. return entity;
  585. // try to find an already registered entity and return it
  586. var registeredEntityTrack =
  587. CurrentTransactionEntities.FindByIdentity(identityKey) ??
  588. AllTrackedEntities.FindByIdentity(identityKey);
  589. if (registeredEntityTrack != null)
  590. return registeredEntityTrack.Entity;
  591. // otherwise, register and return
  592. AllTrackedEntities.RegisterToWatch(entity, identityKey);
  593. return entity;
  594. }
  595. readonly IDataMapper DataMapper = ObjectFactory.Get<IDataMapper>();
  596. private void SetEntityRefQueries(object entity)
  597. {
  598. if (!this.deferredLoadingEnabled)
  599. return;
  600. // BUG: This is ignoring External Mappings from XmlMappingSource.
  601. Type thisType = entity.GetType();
  602. IEnumerable<MetaAssociation> associationList = Mapping.GetMetaType(entity.GetType()).Associations.Where(a => a.IsForeignKey);
  603. foreach (MetaAssociation association in associationList)
  604. {
  605. //example of entityRef:Order.Employee
  606. var memberData = association.ThisMember;
  607. Type otherTableType = association.OtherType.Type;
  608. ParameterExpression p = Expression.Parameter(otherTableType, "other");
  609. var otherTable = GetTable(otherTableType);
  610. //ie:EmployeeTerritories.EmployeeID
  611. var foreignKeys = memberData.Association.ThisKey;
  612. BinaryExpression predicate = null;
  613. var otherPKs = memberData.Association.OtherKey;
  614. IEnumerator<MetaDataMember> otherPKEnumerator = otherPKs.GetEnumerator();
  615. if (otherPKs.Count != foreignKeys.Count)
  616. throw new InvalidOperationException("Foreign keys don't match ThisKey");
  617. foreach (MetaDataMember key in foreignKeys)
  618. {
  619. otherPKEnumerator.MoveNext();
  620. var thisForeignKeyProperty = (PropertyInfo)key.Member;
  621. object thisForeignKeyValue = thisForeignKeyProperty.GetValue(entity, null);
  622. if (thisForeignKeyValue != null)
  623. {
  624. BinaryExpression keyPredicate;
  625. if (!(thisForeignKeyProperty.PropertyType.IsNullable()))
  626. {
  627. keyPredicate = Expression.Equal(Expression.MakeMemberAccess(p, otherPKEnumerator.Current.Member),
  628. Expression.Constant(thisForeignKeyValue));
  629. }
  630. else
  631. {
  632. var ValueProperty = thisForeignKeyProperty.PropertyType.GetProperty("Value");
  633. keyPredicate = Expression.Equal(Expression.MakeMemberAccess(p, otherPKEnumerator.Current.Member),
  634. Expression.Constant(ValueProperty.GetValue(thisForeignKeyValue, null)));
  635. }
  636. if (predicate == null)
  637. predicate = keyPredicate;
  638. else
  639. predicate = Expression.And(predicate, keyPredicate);
  640. }
  641. }
  642. IEnumerable query = null;
  643. if (predicate != null)
  644. {
  645. query = GetOtherTableQuery(predicate, p, otherTableType, otherTable) as IEnumerable;
  646. //it would be interesting surround the above query with a .Take(1) expression for performance.
  647. }
  648. // If no separate Storage is specified, use the member directly
  649. MemberInfo storage = memberData.StorageMember;
  650. if (storage == null)
  651. storage = memberData.Member;
  652. // Check that the storage is a field or a writable property
  653. if (!(storage is FieldInfo) && !(storage is PropertyInfo && ((PropertyInfo)storage).CanWrite)) {
  654. throw new InvalidOperationException(String.Format(
  655. "Member {0}.{1} is not a field nor a writable property",
  656. storage.DeclaringType, storage.Name));
  657. }
  658. Type storageType = storage.GetMemberType();
  659. object entityRefValue = null;
  660. if (query != null)
  661. entityRefValue = Activator.CreateInstance(storageType, query);
  662. else
  663. entityRefValue = Activator.CreateInstance(storageType);
  664. storage.SetMemberValue(entity, entityRefValue);
  665. }
  666. }
  667. /// <summary>
  668. /// 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.
  669. /// Here we set the query source of each EntitySetProperty
  670. /// </summary>
  671. /// <param name="entity"></param>
  672. private void SetEntitySetsQueries(object entity)
  673. {
  674. if (!this.deferredLoadingEnabled)
  675. return;
  676. // BUG: This is ignoring External Mappings from XmlMappingSource.
  677. IEnumerable<MetaAssociation> associationList = Mapping.GetMetaType(entity.GetType()).Associations.Where(a => !a.IsForeignKey);
  678. if (associationList.Any())
  679. {
  680. foreach (MetaAssociation association in associationList)
  681. {
  682. //example of entitySet: Employee.EmployeeTerritories
  683. var memberData = association.ThisMember;
  684. Type otherTableType = association.OtherType.Type;
  685. ParameterExpression p = Expression.Parameter(otherTableType, "other");
  686. //other table:EmployeeTerritories
  687. var otherTable = GetTable(otherTableType);
  688. var otherKeys = memberData.Association.OtherKey;
  689. var thisKeys = memberData.Association.ThisKey;
  690. if (otherKeys.Count != thisKeys.Count)
  691. throw new InvalidOperationException("This keys don't match OtherKey");
  692. BinaryExpression predicate = null;
  693. IEnumerator<MetaDataMember> thisKeyEnumerator = thisKeys.GetEnumerator();
  694. foreach (MetaDataMember otherKey in otherKeys)
  695. {
  696. thisKeyEnumerator.MoveNext();
  697. //other table member:EmployeeTerritories.EmployeeID
  698. var otherTableMember = (PropertyInfo)otherKey.Member;
  699. BinaryExpression keyPredicate;
  700. if (!(otherTableMember.PropertyType.IsNullable()))
  701. {
  702. keyPredicate = Expression.Equal(Expression.MakeMemberAccess(p, otherTableMember),
  703. Expression.Constant(thisKeyEnumerator.Current.Member.GetMemberValue(entity)));
  704. }
  705. else
  706. {
  707. var ValueProperty = otherTableMember.PropertyType.GetProperty("Value");
  708. keyPredicate = Expression.Equal(Expression.MakeMemberAccess(
  709. Expression.MakeMemberAccess(p, otherTableMember),
  710. ValueProperty),
  711. Expression.Constant(thisKeyEnumerator.Current.Member.GetMemberValue(entity)));
  712. }
  713. if (predicate == null)
  714. predicate = keyPredicate;
  715. else
  716. predicate = Expression.And(predicate, keyPredicate);
  717. }
  718. var query = GetOtherTableQuery(predicate, p, otherTableType, otherTable);
  719. var entitySetValue = memberData.Member.GetMemberValue(entity);
  720. if (entitySetValue == null)
  721. {
  722. entitySetValue = Activator.CreateInstance(memberData.Member.GetMemberType());
  723. memberData.Member.SetMemberValue(entity, entitySetValue);
  724. }
  725. var hasLoadedOrAssignedValues = entitySetValue.GetType().GetProperty("HasLoadedOrAssignedValues");
  726. if ((bool)hasLoadedOrAssignedValues.GetValue(entitySetValue, null))
  727. continue;
  728. var setSourceMethod = entitySetValue.GetType().GetMethod("SetSource");
  729. setSourceMethod.Invoke(entitySetValue, new[] { query });
  730. //employee.EmployeeTerritories.SetSource(Table[EmployeesTerritories].Where(other=>other.employeeID="WARTH"))
  731. }
  732. }
  733. }
  734. private static MethodInfo _WhereMethod = typeof(Queryable).GetMethods().First(m => m.Name == "Where");
  735. internal object GetOtherTableQuery(Expression predicate, ParameterExpression parameter, Type otherTableType, IQueryable otherTable)
  736. {
  737. //predicate: other.EmployeeID== "WARTH"
  738. Expression lambdaPredicate = Expression.Lambda(predicate, parameter);
  739. //lambdaPredicate: other=>other.EmployeeID== "WARTH"
  740. Expression call = Expression.Call(_WhereMethod.MakeGenericMethod(otherTableType), otherTable.Expression, lambdaPredicate);
  741. //Table[EmployeesTerritories].Where(other=>other.employeeID="WARTH")
  742. return otherTable.Provider.CreateQuery(call);
  743. }
  744. #endregion
  745. #region Insert/Update/Delete management
  746. /// <summary>
  747. /// Registers an entity for insert
  748. /// </summary>
  749. /// <param name="entity"></param>
  750. internal void RegisterInsert(object entity)
  751. {
  752. CurrentTransactionEntities.RegisterToInsert(entity);
  753. }
  754. private void DoRegisterUpdate(object entity)
  755. {
  756. if (entity == null)
  757. throw new ArgumentNullException("entity");
  758. if (!this.objectTrackingEnabled)
  759. return;
  760. var identityReader = _GetIdentityReader(entity.GetType());
  761. var identityKey = identityReader.GetIdentityKey(entity);
  762. // if we have no key, we can not watch
  763. if (identityKey == null || identityKey.Keys.Count == 0)
  764. return;
  765. // register entity
  766. AllTrackedEntities.RegisterToWatch(entity, identityKey);
  767. }
  768. /// <summary>
  769. /// Registers an entity for update
  770. /// The entity will be updated only if some of its members have changed after the registration
  771. /// </summary>
  772. /// <param name="entity"></param>
  773. internal void RegisterUpdate(object entity)
  774. {
  775. DoRegisterUpdate(entity);
  776. MemberModificationHandler.Register(entity, Mapping);
  777. }
  778. /// <summary>
  779. /// Registers or re-registers an entity and clears its state
  780. /// </summary>
  781. /// <param name="entity"></param>
  782. /// <returns></returns>
  783. internal object Register(object entity)
  784. {
  785. if (! this.objectTrackingEnabled)
  786. return entity;
  787. var registeredEntity = _GetOrRegisterEntity(entity);
  788. // the fact of registering again clears the modified state, so we're... clear with that
  789. MemberModificationHandler.Register(registeredEntity, Mapping);
  790. return registeredEntity;
  791. }
  792. /// <summary>
  793. /// Registers an entity for update
  794. /// The entity will be updated only if some of its members have changed after the registration
  795. /// </summary>
  796. /// <param name="entity"></param>
  797. /// <param name="entityOriginalState"></param>
  798. internal void RegisterUpdate(object entity, object entityOriginalState)
  799. {
  800. if (!this.objectTrackingEnabled)
  801. return;
  802. DoRegisterUpdate(entity);
  803. MemberModificationHandler.Register(entity, entityOriginalState, Mapping);
  804. }
  805. /// <summary>
  806. /// Clears the current state, and marks the object as clean
  807. /// </summary>
  808. /// <param name="entity"></param>
  809. internal void RegisterUpdateAgain(object entity)
  810. {
  811. if (!this.objectTrackingEnabled)
  812. return;
  813. MemberModificationHandler.ClearModified(entity, Mapping);
  814. }
  815. /// <summary>
  816. /// Registers an entity for delete
  817. /// </summary>
  818. /// <param name="entity"></param>
  819. internal void RegisterDelete(object entity)
  820. {
  821. if (!this.objectTrackingEnabled)
  822. return;
  823. CurrentTransactionEntities.RegisterToDelete(entity);
  824. }
  825. /// <summary>
  826. /// Unregisters entity after deletion
  827. /// </summary>
  828. /// <param name="entity"></param>
  829. internal void UnregisterDelete(object entity)
  830. {
  831. if (!this.objectTrackingEnabled)
  832. return;
  833. CurrentTransactionEntities.RegisterDeleted(entity);
  834. }
  835. #endregion
  836. /// <summary>
  837. /// Changed object determine
  838. /// </summary>
  839. /// <returns>Lists of inserted, updated, deleted objects</returns>
  840. public ChangeSet GetChangeSet()
  841. {
  842. var inserts = new List<object>();
  843. var updates = new List<object>();
  844. var deletes = new List<object>();
  845. foreach (var entityTrack in CurrentTransactionEntities.EnumerateAll()
  846. .Concat(AllTrackedEntities.EnumerateAll()))
  847. {
  848. switch (entityTrack.EntityState)
  849. {
  850. case EntityState.ToInsert:
  851. inserts.Add(entityTrack.Entity);
  852. break;
  853. case EntityState.ToWatch:
  854. if (MemberModificationHandler.IsModified(entityTrack.Entity, Mapping))
  855. updates.Add(entityTrack.Entity);
  856. break;
  857. case EntityState.ToDelete:
  858. deletes.Add(entityTrack.Entity);
  859. break;
  860. default:
  861. throw new ArgumentOutOfRangeException();
  862. }
  863. }
  864. return new ChangeSet(inserts, updates, deletes);
  865. }
  866. /// <summary>
  867. /// use ExecuteCommand to call raw SQL
  868. /// </summary>
  869. public int ExecuteCommand(string command, params object[] parameters)
  870. {
  871. var directQuery = QueryBuilder.GetDirectQuery(command, new QueryContext(this));
  872. return QueryRunner.Execute(directQuery, parameters);
  873. }
  874. /// <summary>
  875. /// Execute raw SQL query and return object
  876. /// </summary>
  877. public IEnumerable<TResult> ExecuteQuery<TResult>(string query, params object[] parameters) where TResult : new()
  878. {
  879. if (query == null)
  880. throw new ArgumentNullException("query");
  881. return CreateExecuteQueryEnumerable<TResult>(query, parameters);
  882. }
  883. private IEnumerable<TResult> CreateExecuteQueryEnumerable<TResult>(string query, object[] parameters)
  884. where TResult : new()
  885. {
  886. foreach (TResult result in ExecuteQuery(typeof(TResult), query, parameters))
  887. yield return result;
  888. }
  889. public IEnumerable ExecuteQuery(Type elementType, string query, params object[] parameters)
  890. {
  891. if (elementType == null)
  892. throw new ArgumentNullException("elementType");
  893. if (query == null)
  894. throw new ArgumentNullException("query");
  895. var queryContext = new QueryContext(this);
  896. var directQuery = QueryBuilder.GetDirectQuery(query, queryContext);
  897. return QueryRunner.ExecuteSelect(elementType, directQuery, parameters);
  898. }
  899. /// <summary>
  900. /// Gets or sets the load options
  901. /// </summary>
  902. [DbLinqToDo]
  903. public DataLoadOptions LoadOptions
  904. {
  905. get { throw new NotImplementedException(); }
  906. set { throw new NotImplementedException(); }
  907. }
  908. public DbTransaction Transaction {
  909. get { return (DbTransaction) DatabaseContext.CurrentTransaction; }
  910. set { DatabaseContext.CurrentTransaction = value; }
  911. }
  912. /// <summary>
  913. /// Runs the given reader and returns columns.
  914. /// </summary>
  915. /// <typeparam name="TResult">The type of the result.</typeparam>
  916. /// <param name="reader">The reader.</param>
  917. /// <returns></returns>
  918. public IEnumerable<TResult> Translate<TResult>(DbDataReader reader)
  919. {
  920. if (reader == null)
  921. throw new ArgumentNullException("reader");
  922. return CreateTranslateIterator<TResult>(reader);
  923. }
  924. IEnumerable<TResult> CreateTranslateIterator<TResult>(DbDataReader reader)
  925. {
  926. foreach (TResult result in Translate(typeof(TResult), reader))
  927. yield return result;
  928. }
  929. public IMultipleResults Translate(DbDataReader reader)
  930. {
  931. throw new NotImplementedException();
  932. }
  933. public IEnumerable Translate(Type elementType, DbDataReader reader)
  934. {
  935. if (elementType == null)
  936. throw new ArgumentNullException("elementType");
  937. if (reader == null)
  938. throw new ArgumentNullException("reader");
  939. return QueryRunner.EnumerateResult(elementType, reader, this);
  940. }
  941. public void Dispose()
  942. {
  943. //connection closing should not be done here.
  944. //read: http://msdn2.microsoft.com/en-us/library/bb292288.aspx
  945. //We own the instance of MemberModificationHandler - we must unregister listeners of entities we attached to
  946. MemberModificationHandler.UnregisterAll();
  947. }
  948. [DbLinqToDo]
  949. protected virtual void Dispose(bool disposing)
  950. {
  951. throw new NotImplementedException();
  952. }
  953. /// <summary>
  954. /// Creates a IDbDataAdapter. Used internally by Vendors
  955. /// </summary>
  956. /// <returns></returns>
  957. internal IDbDataAdapter CreateDataAdapter()
  958. {
  959. return DatabaseContext.CreateDataAdapter();
  960. }
  961. /// <summary>
  962. /// Sets a TextWriter where generated SQL commands are written
  963. /// </summary>
  964. public TextWriter Log { get; set; }
  965. /// <summary>
  966. /// Writes text on Log (if not null)
  967. /// Internal helper
  968. /// </summary>
  969. /// <param name="text"></param>
  970. internal void WriteLog(string text)
  971. {
  972. if (Log != null)
  973. Log.WriteLine(text);
  974. }
  975. /// <summary>
  976. /// Write an IDbCommand to Log (if non null)
  977. /// </summary>
  978. /// <param name="command"></param>
  979. internal void WriteLog(IDbCommand command)
  980. {
  981. if (Log != null)
  982. {
  983. Log.WriteLine(command.CommandText);
  984. foreach (IDbDataParameter parameter in command.Parameters)
  985. WriteLog(parameter);
  986. Log.Write("--");
  987. Log.Write(" Context: {0}", Vendor.VendorName);
  988. Log.Write(" Model: {0}", Mapping.GetType().Name);
  989. Log.Write(" Build: {0}", Assembly.GetExecutingAssembly().GetName().Version);
  990. Log.WriteLine();
  991. }
  992. }
  993. /// <summary>
  994. /// Writes and IDbDataParameter to Log (if non null)
  995. /// </summary>
  996. /// <param name="parameter"></param>
  997. internal void WriteLog(IDbDataParameter parameter)
  998. {
  999. if (Log != null)
  1000. {
  1001. // -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [2]
  1002. // -- <name>: <direction> <type> (...) [<value>]
  1003. Log.WriteLine("-- {0}: {1} {2} (Size = {3}; Prec = {4}; Scale = {5}) [{6}]",
  1004. parameter.ParameterName, parameter.Direction, parameter.DbType,
  1005. parameter.Size, parameter.Precision, parameter.Scale, parameter.Value);
  1006. }
  1007. }
  1008. public bool ObjectTrackingEnabled
  1009. {
  1010. get { return this.objectTrackingEnabled; }
  1011. set
  1012. {
  1013. if (this.currentTransactionEntities != null && value != this.objectTrackingEnabled)
  1014. throw new InvalidOperationException("Data context options cannot be modified after results have been returned from a query.");
  1015. this.objectTrackingEnabled = value;
  1016. }
  1017. }
  1018. [DbLinqToDo]
  1019. public int CommandTimeout
  1020. {
  1021. get { throw new NotImplementedException(); }
  1022. set { throw new NotImplementedException(); }
  1023. }
  1024. public bool DeferredLoadingEnabled
  1025. {
  1026. get { return this.deferredLoadingEnabled; }
  1027. set
  1028. {
  1029. if (this.currentTransactionEntities != null && value != this.deferredLoadingEnabled)
  1030. throw new InvalidOperationException("Data context options cannot be modified after results have been returned from a query.");
  1031. this.deferredLoadingEnabled = value;
  1032. }
  1033. }
  1034. [DbLinqToDo]
  1035. public ChangeConflictCollection ChangeConflicts
  1036. {
  1037. get { throw new NotImplementedException(); }
  1038. }
  1039. [DbLinqToDo]
  1040. public DbCommand GetCommand(IQueryable query)
  1041. {
  1042. DbCommand dbCommand = GetIDbCommand(query) as DbCommand;
  1043. if (dbCommand == null)
  1044. throw new InvalidOperationException();
  1045. return dbCommand;
  1046. }
  1047. [DBLinqExtended]
  1048. public IDbCommand GetIDbCommand(IQueryable query)
  1049. {
  1050. if (query == null)
  1051. throw new ArgumentNullException("query");
  1052. var qp = query.Provider as QueryProvider;
  1053. if (qp == null)
  1054. throw new InvalidOperationException();
  1055. if (qp.ExpressionChain.Expressions.Count == 0)
  1056. qp.ExpressionChain.Expressions.Add(CreateDefaultQuery(query));
  1057. return qp.GetQuery(null).GetCommand().Command;
  1058. }
  1059. private Expression CreateDefaultQuery(IQueryable query)
  1060. {
  1061. // Manually create the expression tree for: IQueryable<TableType>.Select(e => e)
  1062. var identityParameter = Expression.Parameter(query.ElementType, "e");
  1063. var identityBody = Expression.Lambda(
  1064. typeof(Func<,>).MakeGenericType(query.ElementType, query.ElementType),
  1065. identityParameter,
  1066. new[] { identityParameter }
  1067. );
  1068. return Expression.Call(
  1069. typeof(Queryable),
  1070. "Select",
  1071. new[] { query.ElementType, query.ElementType },
  1072. query.Expression,
  1073. Expression.Quote(identityBody)
  1074. );
  1075. }

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