PageRenderTime 49ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/mcs/tools/sqlmetal/src/DbLinq/Data/Linq/DataContext.cs

http://github.com/mono/mono
C# | 1282 lines | 918 code | 158 blank | 206 comment | 147 complexity | fecb956f56965df978a192a53dd217ad MD5 | raw file
Possible License(s): GPL-2.0, CC-BY-SA-3.0, LGPL-2.0, MPL-2.0-no-copyleft-exception, LGPL-2.1, Unlicense, Apache-2.0
  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 fileOrServerOrConnection)
  173. {
  174. Profiler.At("START DataContext(string)");
  175. IVendor ivendor = GetVendor(ref fileOrServerOrConnection);
  176. IDbConnection dbConnection = ivendor.CreateDbConnection(fileOrServerOrConnection);
  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;
  735. internal object GetOtherTableQuery(Expression predicate, ParameterExpression parameter, Type otherTableType, IQueryable otherTable)
  736. {
  737. if (_WhereMethod == null)
  738. System.Threading.Interlocked.CompareExchange (ref _WhereMethod, typeof(Queryable).GetMethods().First(m => m.Name == "Where"), null);
  739. //predicate: other.EmployeeID== "WARTH"
  740. Expression lambdaPredicate = Expression.Lambda(predicate, parameter);
  741. //lambdaPredicate: other=>other.EmployeeID== "WARTH"
  742. Expression call = Expression.Call(_WhereMethod.MakeGenericMethod(otherTableType), otherTable.Expression, lambdaPredicate);
  743. //Table[EmployeesTerritories].Where(other=>other.employeeID="WARTH")
  744. return otherTable.Provider.CreateQuery(call);
  745. }
  746. #endregion
  747. #region Insert/Update/Delete management
  748. /// <summary>
  749. /// Registers an entity for insert
  750. /// </summary>
  751. /// <param name="entity"></param>
  752. internal void RegisterInsert(object entity)
  753. {
  754. CurrentTransactionEntities.RegisterToInsert(entity);
  755. }
  756. private void DoRegisterUpdate(object entity)
  757. {
  758. if (entity == null)
  759. throw new ArgumentNullException("entity");
  760. if (!this.objectTrackingEnabled)
  761. return;
  762. var identityReader = _GetIdentityReader(entity.GetType());
  763. var identityKey = identityReader.GetIdentityKey(entity);
  764. // if we have no key, we can not watch
  765. if (identityKey == null || identityKey.Keys.Count == 0)
  766. return;
  767. // register entity
  768. AllTrackedEntities.RegisterToWatch(entity, identityKey);
  769. }
  770. /// <summary>
  771. /// Registers an entity for update
  772. /// The entity will be updated only if some of its members have changed after the registration
  773. /// </summary>
  774. /// <param name="entity"></param>
  775. internal void RegisterUpdate(object entity)
  776. {
  777. DoRegisterUpdate(entity);
  778. MemberModificationHandler.Register(entity, Mapping);
  779. }
  780. /// <summary>
  781. /// Registers or re-registers an entity and clears its state
  782. /// </summary>
  783. /// <param name="entity"></param>
  784. /// <returns></returns>
  785. internal object Register(object entity)
  786. {
  787. if (! this.objectTrackingEnabled)
  788. return entity;
  789. var registeredEntity = _GetOrRegisterEntity(entity);
  790. // the fact of registering again clears the modified state, so we're... clear with that
  791. MemberModificationHandler.Register(registeredEntity, Mapping);
  792. return registeredEntity;
  793. }
  794. /// <summary>
  795. /// Registers an entity for update
  796. /// The entity will be updated only if some of its members have changed after the registration
  797. /// </summary>
  798. /// <param name="entity"></param>
  799. /// <param name="entityOriginalState"></param>
  800. internal void RegisterUpdate(object entity, object entityOriginalState)
  801. {
  802. if (!this.objectTrackingEnabled)
  803. return;
  804. DoRegisterUpdate(entity);
  805. MemberModificationHandler.Register(entity, entityOriginalState, Mapping);
  806. }
  807. /// <summary>
  808. /// Clears the current state, and marks the object as clean
  809. /// </summary>
  810. /// <param name="entity"></param>
  811. internal void RegisterUpdateAgain(object entity)
  812. {
  813. if (!this.objectTrackingEnabled)
  814. return;
  815. MemberModificationHandler.ClearModified(entity, Mapping);
  816. }
  817. /// <summary>
  818. /// Registers an entity for delete
  819. /// </summary>
  820. /// <param name="entity"></param>
  821. internal void RegisterDelete(object entity)
  822. {
  823. if (!this.objectTrackingEnabled)
  824. return;
  825. CurrentTransactionEntities.RegisterToDelete(entity);
  826. }
  827. /// <summary>
  828. /// Unregisters entity after deletion
  829. /// </summary>
  830. /// <param name="entity"></param>
  831. internal void UnregisterDelete(object entity)
  832. {
  833. if (!this.objectTrackingEnabled)
  834. return;
  835. CurrentTransactionEntities.RegisterDeleted(entity);
  836. }
  837. #endregion
  838. /// <summary>
  839. /// Changed object determine
  840. /// </summary>
  841. /// <returns>Lists of inserted, updated, deleted objects</returns>
  842. public ChangeSet GetChangeSet()
  843. {
  844. var inserts = new List<object>();
  845. var updates = new List<object>();
  846. var deletes = new List<object>();
  847. foreach (var entityTrack in CurrentTransactionEntities.EnumerateAll()
  848. .Concat(AllTrackedEntities.EnumerateAll()))
  849. {
  850. switch (entityTrack.EntityState)
  851. {
  852. case EntityState.ToInsert:
  853. inserts.Add(entityTrack.Entity);
  854. break;
  855. case EntityState.ToWatch:
  856. if (MemberModificationHandler.IsModified(entityTrack.Entity, Mapping))
  857. updates.Add(entityTrack.Entity);
  858. break;
  859. case EntityState.ToDelete:
  860. deletes.Add(entityTrack.Entity);
  861. break;
  862. default:
  863. throw new ArgumentOutOfRangeException();
  864. }
  865. }
  866. return new ChangeSet(inserts, updates, deletes);
  867. }
  868. /// <summary>
  869. /// use ExecuteCommand to call raw SQL
  870. /// </summary>
  871. public int ExecuteCommand(string command, params object[] parameters)
  872. {
  873. var directQuery = QueryBuilder.GetDirectQuery(command, new QueryContext(this));
  874. return QueryRunner.Execute(directQuery, parameters);
  875. }
  876. /// <summary>
  877. /// Execute raw SQL query and return object
  878. /// </summary>
  879. public IEnumerable<TResult> ExecuteQuery<TResult>(string query, params object[] parameters) where TResult : new()
  880. {
  881. if (query == null)
  882. throw new ArgumentNullException("query");
  883. return CreateExecuteQueryEnumerable<TResult>(query, parameters);
  884. }
  885. private IEnumerable<TResult> CreateExecuteQueryEnumerable<TResult>(string query, object[] parameters)
  886. where TResult : new()
  887. {
  888. foreach (TResult result in ExecuteQuery(typeof(TResult), query, parameters))
  889. yield return result;
  890. }
  891. public IEnumerable ExecuteQuery(Type elementType, string query, params object[] parameters)
  892. {
  893. if (elementType == null)
  894. throw new ArgumentNullException("elementType");
  895. if (query == null)
  896. throw new ArgumentNullException("query");
  897. var queryContext = new QueryContext(this);
  898. var directQuery = QueryBuilder.GetDirectQuery(query, queryContext);
  899. return QueryRunner.ExecuteSelect(elementType, directQuery, parameters);
  900. }
  901. /// <summary>
  902. /// Gets or sets the load options
  903. /// </summary>
  904. [DbLinqToDo]
  905. public DataLoadOptions LoadOptions
  906. {
  907. get { throw new NotImplementedException(); }
  908. set { throw new NotImplementedException(); }
  909. }
  910. public DbTransaction Transaction {
  911. get { return (DbTransaction) DatabaseContext.CurrentTransaction; }
  912. set { DatabaseContext.CurrentTransaction = value; }
  913. }
  914. /// <summary>
  915. /// Runs the given reader and returns columns.
  916. /// </summary>
  917. /// <typeparam name="TResult">The type of the result.</typeparam>
  918. /// <param name="reader">The reader.</param>
  919. /// <returns></returns>
  920. public IEnumerable<TResult> Translate<TResult>(DbDataReader reader)
  921. {
  922. if (reader == null)
  923. throw new ArgumentNullException("reader");
  924. return CreateTranslateIterator<TResult>(reader);
  925. }
  926. IEnumerable<TResult> CreateTranslateIterator<TResult>(DbDataReader reader)
  927. {
  928. foreach (TResult result in Translate(typeof(TResult), reader))
  929. yield return result;
  930. }
  931. public IMultipleResults Translate(DbDataReader reader)
  932. {
  933. throw new NotImplementedException();
  934. }
  935. public IEnumerable Translate(Type elementType, DbDataReader reader)
  936. {
  937. if (elementType == null)
  938. throw new ArgumentNullException("elementType");
  939. if (reader == null)
  940. throw new ArgumentNullException("reader");
  941. return QueryRunner.EnumerateResult(elementType, reader, this);
  942. }
  943. public void Dispose()
  944. {
  945. //connection closing should not be done here.
  946. //read: http://msdn2.microsoft.com/en-us/library/bb292288.aspx
  947. //We own the instance of MemberModificationHandler - we must unregister listeners of entities we attached to
  948. MemberModificationHandler.UnregisterAll();
  949. }
  950. [DbLinqToDo]
  951. protected virtual void Dispose(bool disposing)
  952. {
  953. throw new NotImplementedException();
  954. }
  955. /// <summary>
  956. /// Creates a IDbDataAdapter. Used internally by Vendors
  957. /// </summary>
  958. /// <returns></returns>
  959. internal IDbDataAdapter CreateDataAdapter()
  960. {
  961. return DatabaseContext.CreateDataAdapter();
  962. }
  963. /// <summary>
  964. /// Sets a TextWriter where generated SQL commands are written
  965. /// </summary>
  966. public TextWriter Log { get; set; }
  967. /// <summary>
  968. /// Writes text on Log (if not null)
  969. /// Internal helper
  970. /// </summary>
  971. /// <param name="text"></param>
  972. internal void WriteLog(string text)
  973. {
  974. if (Log != null)
  975. Log.WriteLine(text);
  976. }
  977. /// <summary>
  978. /// Write an IDbCommand to Log (if non null)
  979. /// </summary>
  980. /// <param name="command"></param>
  981. internal void WriteLog(IDbCommand command)
  982. {
  983. if (Log != null)
  984. {
  985. Log.WriteLine(command.CommandText);
  986. foreach (IDbDataParameter parameter in command.Parameters)
  987. WriteLog(parameter);
  988. Log.Write("--");
  989. Log.Write(" Context: {0}", Vendor.VendorName);
  990. Log.Write(" Model: {0}", Mapping.GetType().Name);
  991. Log.Write(" Build: {0}", Assembly.GetExecutingAssembly().GetName().Version);
  992. Log.WriteLine();
  993. }
  994. }
  995. /// <summary>
  996. /// Writes and IDbDataParameter to Log (if non null)
  997. /// </summary>
  998. /// <param name="parameter"></param>
  999. internal void WriteLog(IDbDataParameter parameter)
  1000. {
  1001. if (Log != null)
  1002. {
  1003. // -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [2]
  1004. // -- <name>: <direction> <type> (...) [<value>]
  1005. Log.WriteLine("-- {0}: {1} {2} (Size = {3}; Prec = {4}; Scale = {5}) [{6}]",
  1006. parameter.ParameterName, parameter.Direction, parameter.DbType,
  1007. parameter.Size, parameter.Precision, parameter.Scale, parameter.Value);
  1008. }
  1009. }
  1010. public bool ObjectTrackingEnabled
  1011. {
  1012. get { return this.objectTrackingEnabled; }
  1013. set
  1014. {
  1015. if (this.currentTransactionEntities != null && value != this.objectTrackingEnabled)
  1016. throw new InvalidOperationException("Data context options cannot be modified after results have been returned from a query.");
  1017. this.objectTrackingEnabled = value;
  1018. }
  1019. }
  1020. [DbLinqToDo]
  1021. public int CommandTimeout
  1022. {
  1023. get { throw new NotImplementedException(); }
  1024. set { throw new NotImplementedException(); }
  1025. }
  1026. public bool DeferredLoadingEnabled
  1027. {
  1028. get { return this.deferredLoadingEnabled; }
  1029. set
  1030. {
  1031. if (this.currentTransactionEntities != null && value != this.deferredLoadingEnabled)
  1032. throw new InvalidOperationException("Data context options cannot be modified after results have been returned from a query.");
  1033. this.deferredLoadingEnabled = value;
  1034. }
  1035. }
  1036. [DbLinqToDo]
  1037. public ChangeConflictCollection ChangeConflicts
  1038. {
  1039. get { throw new NotImplementedException(); }
  1040. }
  1041. [DbLinqToDo]
  1042. public DbCommand GetCommand(IQueryable query)
  1043. {
  1044. DbCommand dbCommand = GetIDbCommand(query) as DbCommand;
  1045. if (dbCommand == null)
  1046. throw new InvalidOperationException();
  1047. return dbCommand;
  1048. }
  1049. [DBLinqExtended]
  1050. public IDbCommand GetIDbCommand(IQueryable query)
  1051. {
  1052. if (query == null)
  1053. throw new ArgumentNullException("query");
  1054. var qp = query.Provider as QueryProvider;
  1055. if (qp == null)
  1056. throw new InvalidOperationException();
  1057. if (qp.ExpressionChain.Expressions.Count == 0)
  1058. qp.ExpressionChain.Expressions.Add(CreateDefaultQuery(query));
  1059. return qp.GetQuery(null).GetCommand().Command;
  1060. }
  1061. private Expression CreateDefaultQuery(IQueryable query)
  1062. {
  1063. // Manually create the expression tree for: IQueryable<TableType>.Select(e => e)
  1064. var identityParameter = Expression.Parameter(query.ElementType, "e");
  1065. var identityBody = Expression.Lambda(
  1066. typeof(Func<,>).MakeGenericType(query.ElementType, query.ElementType),
  1067. identityParameter,
  1068. new[] { identityParameter }
  1069. );
  1070. return Expression.Call(
  1071. typeof(Queryable),
  1072. "Select",
  1073. new[] { query.ElementType, query.ElementType },
  1074. query.Expression,
  1075. Expression.Quote(identityBody)
  1076. );
  1077. }
  1078. [DbLinqToDo]
  1079. public void Refresh(RefreshMode mode, IEnumerable entities)
  1080. {
  1081. throw new NotImplementedException();
  1082. }
  1083. [DbLinqToDo]
  1084. public void Refresh(RefreshMode mode, params object[] entities)
  1085. {
  1086. throw new NotImplementedException();
  1087. }
  1088. [DbLinqToDo]
  1089. public void Refresh(RefreshMode mode, object entity)
  1090. {
  1091. throw new NotImplementedException();
  1092. }
  1093. [DbLinqToDo]
  1094. public void DeleteDatabase()
  1095. {
  1096. throw new NotImplementedException();
  1097. }
  1098. [DbLinqToDo]
  1099. public void CreateDatabase()
  1100. {
  1101. throw new NotImplementedException();
  1102. }
  1103. [DbLinqToDo]
  1104. protected internal IQueryable<TResult> CreateMethodCallQuery<TResult>(object instance, MethodInfo methodInfo, params object[] parameters)
  1105. {
  1106. throw new NotImplementedException();
  1107. }
  1108. [DbLinqToDo]
  1109. protected internal void ExecuteDynamicDelete(object entity)
  1110. {
  1111. throw new NotImplementedException();
  1112. }
  1113. [DbLinqToDo]
  1114. protected internal void ExecuteDynamicInsert(object entity)
  1115. {
  1116. throw new NotImplementedException();
  1117. }
  1118. [DbLinqToDo]
  1119. protected internal void ExecuteDynamicUpdate(object entity)
  1120. {
  1121. throw new NotImplementedException();
  1122. }
  1123. }
  1124. }