-#region MIT license
-//
-// MIT license
-//
-// Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-//
-#endregion
-
-using System;
-using System.Collections;
-using System.Data;
-using System.Data.Common;
-using System.Data.Linq.Mapping;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-
-#if MONO_STRICT
-using System.Data.Linq.Implementation;
-using System.Data.Linq.Sugar;
-using System.Data.Linq.Identity;
-using DbLinq.Util;
-using AttributeMappingSource = System.Data.Linq.Mapping.AttributeMappingSource;
-using MappingContext = System.Data.Linq.Mapping.MappingContext;
-using DbLinq;
-#else
-using DbLinq.Data.Linq.Implementation;
-using DbLinq.Data.Linq.Sugar;
-using DbLinq.Data.Linq.Identity;
-using DbLinq.Util;
-using AttributeMappingSource = DbLinq.Data.Linq.Mapping.AttributeMappingSource;
-using MappingContext = DbLinq.Data.Linq.Mapping.MappingContext;
-using System.Data.Linq;
-#endif
-
-using DbLinq.Factory;
-using DbLinq.Vendor;
-using DbLinq.Data.Linq.Database;
-using DbLinq.Data.Linq.Database.Implementation;
-using System.Linq.Expressions;
-using System.Reflection.Emit;
-
-#if MONO_STRICT
-namespace System.Data.Linq
-#else
-namespace DbLinq.Data.Linq
-#endif
-{
- public partial class DataContext : IDisposable
- {
- //private readonly Dictionary<string, ITable> _tableMap = new Dictionary<string, ITable>();
- private readonly Dictionary<Type, ITable> _tableMap = new Dictionary<Type, ITable>();
-
- public MetaModel Mapping { get; private set; }
- // PC question: at ctor, we get a IDbConnection and the Connection property exposes a DbConnection
- // WTF?
- public DbConnection Connection { get { return DatabaseContext.Connection as DbConnection; } }
-
- // all properties below are set public to optionally be injected
- internal IVendor Vendor { get; set; }
- internal IQueryBuilder QueryBuilder { get; set; }
- internal IQueryRunner QueryRunner { get; set; }
- internal IMemberModificationHandler MemberModificationHandler { get; set; }
- internal IDatabaseContext DatabaseContext { get; private set; }
- // /all properties...
-
- private readonly EntityTracker entityTracker = new EntityTracker();
-
- private IIdentityReaderFactory identityReaderFactory;
- private readonly IDictionary<Type, IIdentityReader> identityReaders = new Dictionary<Type, IIdentityReader>();
-
- /// <summary>
- /// The default behavior creates one MappingContext.
- /// </summary>
- [DBLinqExtended]
- internal virtual MappingContext _MappingContext { get; set; }
-
- [DBLinqExtended]
- internal IVendorProvider _VendorProvider { get; set; }
-
- public DataContext(IDbConnection connection, MappingSource mapping)
- {
- Init(new DatabaseContext(connection), mapping, null);
- }
-
- public DataContext(IDbConnection connection)
- {
- Init(new DatabaseContext(connection), null, null);
- }
-
- [DbLinqToDo]
- public DataContext(string fileOrServerOrConnection, MappingSource mapping)
- {
- throw new NotImplementedException();
- }
-
- /// <summary>
- /// Construct DataContext, given a connectionString.
- /// To determine which DB type to go against, we look for 'DbLinqProvider=xxx' substring.
- /// If not found, we assume that we are dealing with MS Sql Server.
- ///
- /// Valid values are names of provider DLLs (or any other DLL containing an IVendor implementation)
- /// DbLinqProvider=Mysql
- /// DbLinqProvider=Oracle etc.
- /// </summary>
- /// <param name="connectionString">specifies file or server connection</param>
- [DbLinqToDo]
- public DataContext(string connectionString)
- {
- #region DataContext connectionString ctor
- if (connectionString == null)
- throw new ArgumentNullException("connectionString");
-
- System.Text.RegularExpressions.Regex reProvider
- = new System.Text.RegularExpressions.Regex(@"DbLinqProvider=([\w\.]+)");
-
- int startPos = connectionString.IndexOf("DbLinqProvider=");
- string assemblyToLoad;
- string vendorClassToLoad;
- if (!reProvider.IsMatch(connectionString))
- {
- assemblyToLoad = "DbLinq.SqlServer.dll";
- vendorClassToLoad = "SqlServerVendor";
- }
- else
- {
- System.Text.RegularExpressions.Match match = reProvider.Match(connectionString);
-#if MONO_STRICT
- //Pascal says on the forum:
- //[in MONO] "all vendors are (will be) embedded in the System.Data.Linq assembly"
- assemblyToLoad = "System.Data.Linq.dll";
- vendorClassToLoad = match.Groups[1].Value; //eg. "MySql"
-#else
- //plain DbLinq - non MONO:
- //IVendor classes are in DLLs such as "DbLinq.MySql.dll"
- assemblyToLoad = match.Groups[1].Value; //eg. assemblyToLoad="DbLinq.MySql.dll"
- if (assemblyToLoad.Contains("."))
- {
- //already fully qualified DLL name?
- throw new ArgumentException("Please provide a short name, such as 'MySql', not '" + assemblyToLoad + "'");
- }
- else
- {
- //we were given short name, such as MySql
- vendorClassToLoad = assemblyToLoad + "Vendor"; //eg. MySqlVendor
- assemblyToLoad = "DbLinq." + assemblyToLoad + ".dll"; //eg. DbLinq.MySql.dll
- }
-#endif
- //shorten: "DbLinqProvider=X;Server=Y" -> ";Server=Y"
- string shortenedConnStr = reProvider.Replace(connectionString, "");
- connectionString = shortenedConnStr;
- }
-
- Assembly assy;
- try
- {
-#if MONO_STRICT
- assy = typeof (DataContext).Assembly; // System.Data.Linq.dll
-#else
- //TODO: check if DLL is already loaded?
- assy = Assembly.LoadFrom(assemblyToLoad);
-#endif
- }
- catch (Exception ex)
- {
- //TODO: add proper logging here
- Console.WriteLine("DataContext ctor: Assembly load failed for " + assemblyToLoad + ": " + ex);
- throw ex;
- }
-
- //find IDbProvider class in this assembly:
- var ctors = (from mod in assy.GetModules()
- from cls in mod.GetTypes()
- where cls.GetInterfaces().Contains(typeof(IVendor))
- && cls.Name.ToLower() == vendorClassToLoad.ToLower()
- let ctorInfo = cls.GetConstructor(Type.EmptyTypes)
- where ctorInfo != null
- select ctorInfo).ToList();
- if (ctors.Count == 0)
- {
- string msg = "Found no IVendor class in assembly " + assemblyToLoad + " having a string ctor";
- throw new ArgumentException(msg);
- }
- else if (ctors.Count > 1)
- {
- string msg = "Found more than one IVendor class in assembly " + assemblyToLoad + " having a string ctor";
- throw new ArgumentException(msg);
- }
- ConstructorInfo ctorInfo2 = ctors[0];
-
- object ivendorObject;
- try
- {
- ivendorObject = ctorInfo2.Invoke(new object[]{});
- }
- catch (Exception ex)
- {
- //TODO: add proper logging here
- Console.WriteLine("DataContext ctor: Failed to invoke IVendor ctor " + ctorInfo2.Name + ": " + ex);
- throw ex;
- }
- IVendor ivendor = (IVendor)ivendorObject;
- IDbConnection dbConnection = ivendor.CreateDbConnection(connectionString);
- Init(new DatabaseContext(dbConnection), null, ivendor);
- #endregion
- }
-
- private void Init(IDatabaseContext databaseContext, MappingSource mappingSource, IVendor vendor)
- {
- if (databaseContext == null)
- throw new ArgumentNullException("databaseContext");
-
- _VendorProvider = ObjectFactory.Get<IVendorProvider>();
- if (vendor == null)
- Vendor = _VendorProvider.FindVendorByProviderType(typeof(SqlClient.Sql2005Provider));
- else
- Vendor = vendor;
-
- DatabaseContext = databaseContext;
-
- MemberModificationHandler = ObjectFactory.Create<IMemberModificationHandler>(); // not a singleton: object is stateful
- QueryBuilder = ObjectFactory.Get<IQueryBuilder>();
- QueryRunner = ObjectFactory.Get<IQueryRunner>();
-
- //EntityMap = ObjectFactory.Create<IEntityMap>();
- identityReaderFactory = ObjectFactory.Get<IIdentityReaderFactory>();
-
- _MappingContext = new MappingContext();
-
- // initialize the mapping information
- if (mappingSource == null)
- mappingSource = new AttributeMappingSource();
- Mapping = mappingSource.GetModel(GetType());
- }
-
- /// <summary>
- /// Checks if the table is allready mapped or maps it if not.
- /// </summary>
- /// <param name="tableType">Type of the table.</param>
- /// <exception cref="InvalidOperationException">Thrown if the table is not mappable.</exception>
- private void CheckTableMapping(Type tableType)
- {
- //This will throw an exception if the table is not found
- if(Mapping.GetTable(tableType) == null)
- {
- throw new InvalidOperationException("The type '" + tableType.Name + "' is not mapped as a Table.");
- }
- }
-
- /// <summary>
- /// Returns a Table for the type TEntity.
- /// </summary>
- /// <exception cref="InvalidOperationException">If the type TEntity is not mappable as a Table.</exception>
- /// <typeparam name="TEntity">The table type.</typeparam>
- public Table<TEntity> GetTable<TEntity>() where TEntity : class
- {
- return (Table<TEntity>)GetTable(typeof(TEntity));
- }
-
- /// <summary>
- /// Returns a Table for the given type.
- /// </summary>
- /// <param name="type">The table type.</param>
- /// <exception cref="InvalidOperationException">If the type is not mappable as a Table.</exception>
- public ITable GetTable(Type type)
- {
- lock (_tableMap)
- {
- ITable tableExisting;
- if (_tableMap.TryGetValue(type, out tableExisting))
- return tableExisting;
-
- //Check for table mapping
- CheckTableMapping(type);
-
- var tableNew = Activator.CreateInstance(
- typeof(Table<>).MakeGenericType(type)
- , BindingFlags.NonPublic | BindingFlags.Instance
- , null
- , new object[] { this }
- , System.Globalization.CultureInfo.CurrentCulture) as ITable;
-
- _tableMap[type] = tableNew;
- return tableNew;
- }
- }
-
- public void SubmitChanges()
- {
- SubmitChanges(ConflictMode.FailOnFirstConflict);
- }
-
- /// <summary>
- /// Pings database
- /// </summary>
- /// <returns></returns>
- public bool DatabaseExists()
- {
- try
- {
- return Vendor.Ping(this);
- }
- catch (Exception)
- {
- return false;
- }
- }
-
- /// <summary>
- /// Commits all pending changes to database
- /// </summary>
- /// <param name="failureMode"></param>
- public virtual void SubmitChanges(ConflictMode failureMode)
- {
- using (DatabaseContext.OpenConnection()) //ConnMgr will close connection for us
- using (IDatabaseTransaction transaction = DatabaseContext.Transaction())
- {
- var queryContext = new QueryContext(this);
- var entityTracks = entityTracker.EnumerateAll().ToList();
- foreach (var entityTrack in entityTracks)
- {
- switch (entityTrack.EntityState)
- {
- case EntityState.ToInsert:
- var insertQuery = QueryBuilder.GetInsertQuery(entityTrack.Entity, queryContext);
- QueryRunner.Insert(entityTrack.Entity, insertQuery);
- Register(entityTrack.Entity);
- break;
- case EntityState.ToWatch:
- if (MemberModificationHandler.IsModified(entityTrack.Entity, Mapping))
- {
- var modifiedMembers = MemberModificationHandler.GetModifiedProperties(entityTrack.Entity, Mapping);
- var updateQuery = QueryBuilder.GetUpdateQuery(entityTrack.Entity, modifiedMembers, queryContext);
- QueryRunner.Update(entityTrack.Entity, updateQuery, modifiedMembers);
-
- RegisterUpdateAgain(entityTrack.Entity);
- }
- break;
- case EntityState.ToDelete:
- var deleteQuery = QueryBuilder.GetDeleteQuery(entityTrack.Entity, queryContext);
- QueryRunner.Delete(entityTrack.Entity, deleteQuery);
-
- UnregisterDelete(entityTrack.Entity);
- break;
- default:
- throw new ArgumentOutOfRangeException();
- }
- }
- // TODO: handle conflicts (which can only occur when concurrency mode is implemented)
- transaction.Commit();
- }
- }
-
- /// <summary>
- /// TODO - allow generated methods to call into stored procedures
- /// </summary>
- [DBLinqExtended]
- internal IExecuteResult _ExecuteMethodCall(DataContext context, System.Reflection.MethodInfo method, params object[] sqlParams)
- {
- using (DatabaseContext.OpenConnection())
- {
- System.Data.Linq.IExecuteResult result = Vendor.ExecuteMethodCall(context, method, sqlParams);
- return result;
- }
- }
-
- [DbLinqToDo]
- protected IExecuteResult ExecuteMethodCall(object instance, System.Reflection.MethodInfo methodInfo, params object[] parameters)
- {
- throw new NotImplementedException();
- }
-
- #region Identity management
-
- [DBLinqExtended]
- internal IIdentityReader _GetIdentityReader(Type t)
- {
- IIdentityReader identityReader;
- lock (identityReaders)
- {
- if (!identityReaders.TryGetValue(t, out identityReader))
- {
- identityReader = identityReaderFactory.GetReader(t, this);
- identityReaders[t] = identityReader;
- }
- }
- return identityReader;
- }
-
- [DBLinqExtended]
- internal object _GetRegisteredEntity(object entity)
- {
- // TODO: check what is faster: by identity or by ref
- var identityReader = _GetIdentityReader(entity.GetType());
- var identityKey = identityReader.GetIdentityKey(entity);
- if (identityKey == null) // if we don't have an entitykey here, it means that the entity has no PK
- return entity;
- // even
- var registeredEntityTrack = entityTracker.FindByIdentity(identityKey);
- if (registeredEntityTrack != null)
- return registeredEntityTrack.Entity;
- return null;
- }
-
- //internal object GetRegisteredEntityByKey(IdentityKey identityKey)
- //{
- // return EntityMap[identityKey];
- //}
-
- /// <summary>
- /// Registers an entity in a watch state
- /// </summary>
- /// <param name="entity"></param>
- /// <returns></returns>
- [DBLinqExtended]
- internal object _GetOrRegisterEntity(object entity)
- {
- var identityReader = _GetIdentityReader(entity.GetType());
- var identityKey = identityReader.GetIdentityKey(entity);
- SetEntitySetsQueries(entity);
- SetEntityRefQueries(entity);
-
- // if we have no identity, we can't track it
- if (identityKey == null)
- return entity;
-
- // try to find an already registered entity and return it
- var registeredEntityTrack = entityTracker.FindByIdentity(identityKey);
- if (registeredEntityTrack != null)
- return registeredEntityTrack.Entity;
-
- // otherwise, register and return
- entityTracker.RegisterToWatch(entity, identityKey);
- return entity;
- }
-
- readonly IDataMapper DataMapper = ObjectFactory.Get<IDataMapper>();
- private void SetEntityRefQueries(object entity)
- {
- Type thisType = entity.GetType();
- IList<MemberInfo> properties = DataMapper.GetEntityRefAssociations(thisType);
-
-
- foreach (PropertyInfo prop in properties)
- {
- //example of entityRef:Order.Employee
- AssociationAttribute associationInfo = prop.GetAttribute<AssociationAttribute>();
- Type otherTableType = prop.PropertyType;
- IList<MemberInfo> otherPKs = DataMapper.GetPrimaryKeys(Mapping.GetTable(otherTableType));
-
- if (otherPKs.Count > 1)
- throw new NotSupportedException("Multiple keys object not supported yet.");
-
- var otherTable = GetTable(otherTableType);
-
- //ie:EmployeeTerritories.EmployeeID
-
- var thisForeignKeyProperty = thisType.GetProperty(associationInfo.ThisKey);
- object thisForeignKeyValue = thisForeignKeyProperty.GetValue(entity, null);
-
- IEnumerable query = null;
- if (thisForeignKeyValue != null)
- {
- ParameterExpression p = Expression.Parameter(otherTableType, "other");
- Expression predicate;
- if (!(thisForeignKeyProperty.PropertyType.IsNullable()))
- {
- predicate = Expression.Equal(Expression.MakeMemberAccess(p, otherPKs.First()),
- Expression.Constant(thisForeignKeyValue));
- }
- else
- {
- var ValueProperty = thisForeignKeyProperty.PropertyType.GetProperty("Value");
- predicate = Expression.Equal(Expression.MakeMemberAccess(p, otherPKs.First()),
- Expression.Constant(ValueProperty.GetValue(thisForeignKeyValue, null)));
- }
-
- query = GetOtherTableQuery(predicate, p, otherTableType, otherTable) as IEnumerable;
- //it would be interesting surround the above query with a .Take(1) expression for performance.
- }
-
-
- FieldInfo entityRefField = entity.GetType().GetField(associationInfo.Storage, BindingFlags.NonPublic | BindingFlags.Instance);
- object entityRefValue = null;
- if (query != null)
- entityRefValue = Activator.CreateInstance(entityRefField.FieldType, query);
- else
- entityRefValue = Activator.CreateInstance(entityRefField.FieldType);
- entityRefField.SetValue(entity, entityRefValue);
- }
- }
-
- /// <summary>
- /// 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.
- /// Here we set the query source of each EntitySetProperty
- /// </summary>
- /// <param name="entity"></param>
- private void SetEntitySetsQueries(object entity)
- {
- IList<MemberInfo> properties = DataMapper.GetEntitySetAssociations(entity.GetType());
-
- if (properties.Any()) {
- IList<MemberInfo> thisPKs = DataMapper.GetPrimaryKeys(Mapping.GetTable(entity.GetType()));
-
- if (thisPKs.Count > 1)
- throw new NotSupportedException("Multiple keys object not supported yet.");
-
- object primaryKeyValue = (thisPKs.First() as PropertyInfo).GetValue(entity, null);
-
-
- foreach (PropertyInfo prop in properties)
- {
- //example of entitySet: Employee.EmployeeTerritories
- var associationInfo = prop.GetAttribute<AssociationAttribute>();
- Type otherTableType = prop.PropertyType.GetGenericArguments().First();
-
- //other table:EmployeeTerritories
- var otherTable = GetTable(otherTableType);
- //other table member:EmployeeTerritories.EmployeeID
- var otherTableMember = otherTableType.GetProperty(associationInfo.OtherKey);
-
-
- ParameterExpression p = Expression.Parameter(otherTableType, "other");
- Expression predicate;
- if (!(otherTableMember.PropertyType.IsNullable()))
- {
- predicate = Expression.Equal(Expression.MakeMemberAccess(p, otherTableMember),
- Expression.Constant(primaryKeyValue));
- }
- else
- {
- var ValueProperty = otherTableMember.PropertyType.GetProperty("Value");
- predicate = Expression.Equal(Expression.MakeMemberAccess(
- Expression.MakeMemberAccess(p, otherTableMember),
- ValueProperty),
- Expression.Constant(primaryKeyValue));
- }
-
- var query = GetOtherTableQuery(predicate, p, otherTableType, otherTable);
-
- var entitySetValue = prop.GetValue(entity, null);
-
- if (entitySetValue == null)
- {
- entitySetValue = Activator.CreateInstance(prop.PropertyType);
- prop.SetValue(entity, entitySetValue, null);
- }
-
- var setSourceMethod = entitySetValue.GetType().GetMethod("SetSource");
- setSourceMethod.Invoke(entitySetValue, new[] { query });
- //employee.EmployeeTerritories.SetSource(Table[EmployeesTerritories].Where(other=>other.employeeID="WARTH"))
- }
- }
- }
-
- private object GetOtherTableQuery(Expression predicate, ParameterExpression parameter, Type otherTableType, IQueryable otherTable)
- {
- //predicate: other.EmployeeID== "WARTH"
- Expression lambdaPredicate = Expression.Lambda(predicate, parameter);
- //lambdaPredicate: other=>other.EmployeeID== "WARTH"
-
- var whereMethod = typeof(Queryable)
- .GetMethods().First(m => m.Name == "Where")
- .MakeGenericMethod(otherTableType);
-
-
- Expression call = Expression.Call(whereMethod, otherTable.Expression, lambdaPredicate);
- //Table[EmployeesTerritories].Where(other=>other.employeeID="WARTH")
-
- return otherTable.Provider.CreateQuery(call);
- }
-
- #endregion
-
- #region Insert/Update/Delete management
-
- /// <summary>
- /// Registers an entity for insert
- /// </summary>
- /// <param name="entity"></param>
- internal void RegisterInsert(object entity)
- {
- entityTracker.RegisterToInsert(entity);
- }
-
- /// <summary>
- /// Registers an entity for update
- /// The entity will be updated only if some of its members have changed after the registration
- /// </summary>
- /// <param name="entity"></param>
- internal void RegisterUpdate(object entity)
- {
- var identityReader = _GetIdentityReader(entity.GetType());
- var identityKey = identityReader.GetIdentityKey(entity);
- // if we have no key, we can not watch
- if (identityKey == null)
- return;
- // register entity
- entityTracker.RegisterToWatch(entity, identityKey);
- }
-
- /// <summary>
- /// Registers or re-registers an entity and clears its state
- /// </summary>
- /// <param name="entity"></param>
- /// <returns></returns>
- internal object Register(object entity)
- {
- var registeredEntity = _GetOrRegisterEntity(entity);
- // the fact of registering again clears the modified state, so we're... clear with that
- MemberModificationHandler.Register(registeredEntity, Mapping);
- return registeredEntity;
- }
-
- /// <summary>
- /// Registers an entity for update
- /// The entity will be updated only if some of its members have changed after the registration
- /// </summary>
- /// <param name="entity"></param>
- /// <param name="entityOriginalState"></param>
- internal void RegisterUpdate(object entity, object entityOriginalState)
- {
- RegisterUpdate(entity);
- MemberModificationHandler.Register(entity, entityOriginalState, Mapping);
- }
-
- /// <summary>
- /// Clears the current state, and marks the object as clean
- /// </summary>
- /// <param name="entity"></param>
- internal void RegisterUpdateAgain(object entity)
- {
- MemberModificationHandler.ClearModified(entity, Mapping);
- }
-
- /// <summary>
- /// Registers an entity for delete
- /// </summary>
- /// <param name="entity"></param>
- internal void RegisterDelete(object entity)
- {
- entityTracker.RegisterToDelete(entity);
- }
-
- /// <summary>
- /// Unregisters entity after deletion
- /// </summary>
- /// <param name="entity"></param>
- internal void UnregisterDelete(object entity)
- {
- entityTracker.RegisterDeleted(entity);
- }
-
- #endregion
-
- /// <summary>
- /// Changed object determine
- /// </summary>
- /// <returns>Lists of inserted, updated, deleted objects</returns>
- public ChangeSet GetChangeSet()
- {
- var inserts = new List<object>();
- var updates = new List<object>();
- var deletes = new List<object>();
- foreach (var entityTrack in entityTracker.EnumerateAll())
- {
- switch (entityTrack.EntityState)
- {
- case EntityState.ToInsert:
- inserts.Add(entityTrack.Entity);
- break;
- case EntityState.ToWatch:
- if (MemberModificationHandler.IsModified(entityTrack.Entity, Mapping))
- updates.Add(entityTrack.Entity);
- break;
- case EntityState.ToDelete:
- deletes.Add(entityTrack.Entity);
- break;
- default:
- throw new ArgumentOutOfRangeException();
- }
- }
- return new ChangeSet(inserts, updates, deletes);
- }
-
- /// <summary>
- /// use ExecuteCommand to call raw SQL
- /// </summary>
- public int ExecuteCommand(string command, params object[] parameters)
- {
- var directQuery = QueryBuilder.GetDirectQuery(command, new QueryContext(this));
- return QueryRunner.Execute(directQuery, parameters);
- }
-
- /// <summary>
- /// Execute raw SQL query and return object
- /// </summary>
- public IEnumerable<TResult> ExecuteQuery<TResult>(string query, params object[] parameters) where TResult : class, new()
- {
- //GetTable<TResult>();
- foreach (TResult result in ExecuteQuery(typeof(TResult), query, parameters))
- yield return result;
- }
-
- public IEnumerable ExecuteQuery(Type elementType, string query, params object[] parameters)
- {
- var queryContext = new QueryContext(this);
- var directQuery = QueryBuilder.GetDirectQuery(query, queryContext);
- return QueryRunner.ExecuteSelect(elementType, directQuery, parameters);
- }
-
- /// <summary>
- /// Gets or sets the load options
- /// </summary>
- [DbLinqToDo]
- public DataLoadOptions LoadOptions { get; set; }
-
- public DbTransaction Transaction { get; set; }
-
- /// <summary>
- /// Runs the given reader and returns columns.
- /// </summary>
- /// <typeparam name="TResult">The type of the result.</typeparam>
- /// <param name="reader">The reader.</param>
- /// <returns></returns>
- public IEnumerable<TResult> Translate<TResult>(DbDataReader reader)
- {
- foreach (TResult result in Translate(typeof(TResult), reader))
- yield return result;
- }
-
- public IMultipleResults Translate(DbDataReader reader)
- {
- throw new NotImplementedException();
- }
-
- public IEnumerable Translate(Type elementType, DbDataReader reader)
- {
- return QueryRunner.EnumerateResult(elementType, reader, this);
- }
-
- public void Dispose()
- {
- //connection closing should not be done here.
- //read: http://msdn2.microsoft.com/en-us/library/bb292288.aspx
- }
-
- [DbLinqToDo]
- protected virtual void Dispose(bool disposing)
- {
- throw new NotImplementedException();
- }
-
- /// <summary>
- /// Creates a IDbDataAdapter. Used internally by Vendors
- /// </summary>
- /// <returns></returns>
- internal IDbDataAdapter CreateDataAdapter()
- {
- return DatabaseContext.CreateDataAdapter();
- }
-
- /// <summary>
- /// Sets a TextWriter where generated SQL commands are written
- /// </summary>
- public TextWriter Log { get; set; }
-
- /// <summary>
- /// Writes text on Log (if not null)
- /// Internal helper
- /// </summary>
- /// <param name="text"></param>
- internal void WriteLog(string text)
- {
- if (Log != null)
- Log.WriteLine(text);
- }
-
- /// <summary>
- /// Write an IDbCommand to Log (if non null)
- /// </summary>
- /// <param name="command"></param>
- internal void WriteLog(IDbCommand command)
- {
- if (Log != null)
- {
- Log.WriteLine(command.CommandText);
- foreach (IDbDataParameter parameter in command.Parameters)
- WriteLog(parameter);
- Log.Write("--");
- Log.Write(" Context: {0}", Vendor.VendorName);
- Log.Write(" Model: {0}", Mapping.GetType().Name);
- Log.Write(" Build: {0}", Assembly.GetExecutingAssembly().GetName().Version);
- Log.WriteLine();
- }
- }
-
- /// <summary>
- /// Writes and IDbDataParameter to Log (if non null)
- /// </summary>
- /// <param name="parameter"></param>
- internal void WriteLog(IDbDataParameter parameter)
- {
- if (Log != null)
- {
- // -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [2]
- // -- <name>: <direction> <type> (...) [<value>]
- Log.WriteLine("-- {0}: {1} {2} (Size = {3}; Prec = {4}; Scale = {5}) [{6}]",
- parameter.ParameterName, parameter.Direction, parameter.DbType,
- parameter.Size, parameter.Precision, parameter.Scale, parameter.Value);
- }
- }
-
-
- [DbLinqToDo]
- public bool ObjectTrackingEnabled
- {
- get { throw new NotImplementedException(); }
- set { throw new NotImplementedException(); }
- }
-
- [DbLinqToDo]
- public int CommandTimeout
- {
- get { throw new NotImplementedException(); }
- set { throw new NotImplementedException(); }
- }
-
- [DbLinqToDo]
- public bool DeferredLoadingEnabled
- {
- get { throw new NotImplementedException(); }
- set { throw new NotImplementedException(); }
- }
-
- [DbLinqToDo]
- public ChangeConflictCollection ChangeConflicts
- {
- get { throw new NotImplementedException(); }
- }
-
- [DbLinqToDo]
- public DbCommand GetCommand(IQueryable query)
- {
- var qp = query.Provider as QueryProvider;
- if (qp == null)
- throw new InvalidOperationException();
-
- IDbCommand dbCommand = qp.GetQuery(null).GetCommand().Command;
- if (!(dbCommand is DbCommand))
- throw new InvalidOperationException();
-
- return (DbCommand)dbCommand;
- }
-
- [DbLinqToDo]
- public void Refresh(RefreshMode mode, IEnumerable entities)
- {
- throw new NotImplementedException();
- }
-
- [DbLinqToDo]
- public void Refresh(RefreshMode mode, params object[] entities)
- {
- throw new NotImplementedException();
- }
-
- [DbLinqToDo]
- public void Refresh(RefreshMode mode, object entity)
- {
- throw new NotImplementedException();
- }
-
- [DbLinqToDo]
- public void DeleteDatabase()
- {
- throw new NotImplementedException();
- }
-
- [DbLinqToDo]
- public void CreateDatabase()
- {
- throw new NotImplementedException();
- }
-
- [DbLinqToDo]
- protected internal IQueryable<TResult> CreateMethodCallQuery<TResult>(object instance, MethodInfo methodInfo, params object[] parameters)
- {
- throw new NotImplementedException();
- }
-
- [DbLinqToDo]
- protected internal void ExecuteDynamicDelete(object entity)
- {
- throw new NotImplementedException();
- }
-
- [DbLinqToDo]
- protected internal void ExecuteDynamicInsert(object entity)
- {
- throw new NotImplementedException();
- }
-
- [DbLinqToDo]
- protected internal void ExecuteDynamicUpdate(object entity)
- {
- throw new NotImplementedException();
- }
- }
-}
+#region MIT license\r
+// \r
+// MIT license\r
+//\r
+// Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne\r
+// \r
+// Permission is hereby granted, free of charge, to any person obtaining a copy\r
+// of this software and associated documentation files (the "Software"), to deal\r
+// in the Software without restriction, including without limitation the rights\r
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
+// copies of the Software, and to permit persons to whom the Software is\r
+// furnished to do so, subject to the following conditions:\r
+// \r
+// The above copyright notice and this permission notice shall be included in\r
+// all copies or substantial portions of the Software.\r
+// \r
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
+// THE SOFTWARE.\r
+// \r
+#endregion\r
+\r
+using System;\r
+using System.Collections;\r
+using System.Data;\r
+using System.Data.Common;\r
+using System.Data.Linq.Mapping;\r
+using System.Collections.Generic;\r
+using System.IO;\r
+using System.Linq;\r
+using System.Reflection;\r
+\r
+#if MONO_STRICT\r
+using System.Data.Linq.Implementation;\r
+using System.Data.Linq.Sugar;\r
+using System.Data.Linq.Identity;\r
+using DbLinq.Util;\r
+using AttributeMappingSource = System.Data.Linq.Mapping.AttributeMappingSource;\r
+using MappingContext = System.Data.Linq.Mapping.MappingContext;\r
+using DbLinq;\r
+#else\r
+using DbLinq.Data.Linq.Implementation;\r
+using DbLinq.Data.Linq.Sugar;\r
+using DbLinq.Data.Linq.Identity;\r
+using DbLinq.Util;\r
+using AttributeMappingSource = DbLinq.Data.Linq.Mapping.AttributeMappingSource;\r
+using MappingContext = DbLinq.Data.Linq.Mapping.MappingContext;\r
+using System.Data.Linq;\r
+#endif\r
+\r
+using DbLinq.Factory;\r
+using DbLinq.Vendor;\r
+using DbLinq.Data.Linq.Database;\r
+using DbLinq.Data.Linq.Database.Implementation;\r
+using System.Linq.Expressions;\r
+using System.Reflection.Emit;\r
+\r
+#if MONO_STRICT\r
+namespace System.Data.Linq\r
+#else\r
+namespace DbLinq.Data.Linq\r
+#endif\r
+{\r
+ public partial class DataContext : IDisposable\r
+ {\r
+ //private readonly Dictionary<string, ITable> _tableMap = new Dictionary<string, ITable>();\r
+ private readonly Dictionary<Type, ITable> _tableMap = new Dictionary<Type, ITable>();\r
+\r
+ public MetaModel Mapping { get; private set; }\r
+ // PC question: at ctor, we get a IDbConnection and the Connection property exposes a DbConnection\r
+ // WTF?\r
+ public DbConnection Connection { get { return DatabaseContext.Connection as DbConnection; } }\r
+\r
+ // all properties below are set public to optionally be injected\r
+ internal IVendor Vendor { get; set; }\r
+ internal IQueryBuilder QueryBuilder { get; set; }\r
+ internal IQueryRunner QueryRunner { get; set; }\r
+ internal IMemberModificationHandler MemberModificationHandler { get; set; }\r
+ internal IDatabaseContext DatabaseContext { get; private set; }\r
+ // /all properties...\r
+\r
+ private readonly EntityTracker entityTracker = new EntityTracker();\r
+\r
+ private IIdentityReaderFactory identityReaderFactory;\r
+ private readonly IDictionary<Type, IIdentityReader> identityReaders = new Dictionary<Type, IIdentityReader>();\r
+\r
+ /// <summary>\r
+ /// The default behavior creates one MappingContext.\r
+ /// </summary>\r
+ [DBLinqExtended]\r
+ internal virtual MappingContext _MappingContext { get; set; }\r
+\r
+ [DBLinqExtended]\r
+ internal IVendorProvider _VendorProvider { get; set; }\r
+\r
+ public DataContext(IDbConnection connection, MappingSource mapping)\r
+ {\r
+ Init(new DatabaseContext(connection), mapping, null);\r
+ }\r
+\r
+ public DataContext(IDbConnection connection)\r
+ {\r
+ Init(new DatabaseContext(connection), null, null);\r
+ }\r
+\r
+ [DbLinqToDo]\r
+ public DataContext(string fileOrServerOrConnection, MappingSource mapping)\r
+ {\r
+ throw new NotImplementedException();\r
+ }\r
+\r
+ /// <summary>\r
+ /// Construct DataContext, given a connectionString.\r
+ /// To determine which DB type to go against, we look for 'DbLinqProvider=xxx' substring.\r
+ /// If not found, we assume that we are dealing with MS Sql Server.\r
+ /// \r
+ /// Valid values are names of provider DLLs (or any other DLL containing an IVendor implementation)\r
+ /// DbLinqProvider=Mysql\r
+ /// DbLinqProvider=Oracle etc.\r
+ /// </summary>\r
+ /// <param name="connectionString">specifies file or server connection</param>\r
+ [DbLinqToDo]\r
+ public DataContext(string connectionString)\r
+ {\r
+ #region DataContext connectionString ctor\r
+ if (connectionString == null)\r
+ throw new ArgumentNullException("connectionString");\r
+\r
+ System.Text.RegularExpressions.Regex reProvider\r
+ = new System.Text.RegularExpressions.Regex(@"DbLinqProvider=([\w\.]+)");\r
+\r
+ int startPos = connectionString.IndexOf("DbLinqProvider=");\r
+ string assemblyToLoad;\r
+ string vendorClassToLoad;\r
+ if (!reProvider.IsMatch(connectionString))\r
+ {\r
+ assemblyToLoad = "DbLinq.SqlServer.dll";\r
+ vendorClassToLoad = "SqlServerVendor";\r
+ }\r
+ else\r
+ {\r
+ System.Text.RegularExpressions.Match match = reProvider.Match(connectionString);\r
+#if MONO_STRICT\r
+ //Pascal says on the forum: \r
+ //[in MONO] "all vendors are (will be) embedded in the System.Data.Linq assembly"\r
+ assemblyToLoad = "System.Data.Linq.dll";\r
+ vendorClassToLoad = match.Groups[1].Value; //eg. "MySql"\r
+#else\r
+ //plain DbLinq - non MONO: \r
+ //IVendor classes are in DLLs such as "DbLinq.MySql.dll"\r
+ assemblyToLoad = match.Groups[1].Value; //eg. assemblyToLoad="DbLinq.MySql.dll"\r
+ if (assemblyToLoad.Contains("."))\r
+ {\r
+ //already fully qualified DLL name?\r
+ throw new ArgumentException("Please provide a short name, such as 'MySql', not '" + assemblyToLoad + "'");\r
+ }\r
+ else\r
+ {\r
+ //we were given short name, such as MySql\r
+ vendorClassToLoad = assemblyToLoad + "Vendor"; //eg. MySqlVendor\r
+ assemblyToLoad = "DbLinq." + assemblyToLoad + ".dll"; //eg. DbLinq.MySql.dll\r
+ }\r
+#endif\r
+ //shorten: "DbLinqProvider=X;Server=Y" -> ";Server=Y"\r
+ string shortenedConnStr = reProvider.Replace(connectionString, "");\r
+ connectionString = shortenedConnStr;\r
+ }\r
+\r
+ Assembly assy;\r
+ try\r
+ {\r
+#if MONO_STRICT\r
+ assy = typeof (DataContext).Assembly; // System.Data.Linq.dll\r
+#else\r
+ //TODO: check if DLL is already loaded?\r
+ assy = Assembly.LoadFrom(assemblyToLoad);\r
+#endif\r
+ }\r
+ catch (Exception ex)\r
+ {\r
+ //TODO: add proper logging here\r
+ Console.WriteLine("DataContext ctor: Assembly load failed for " + assemblyToLoad + ": " + ex);\r
+ throw ex;\r
+ }\r
+\r
+ //find IDbProvider class in this assembly:\r
+ var ctors = (from mod in assy.GetModules()\r
+ from cls in mod.GetTypes()\r
+ where cls.GetInterfaces().Contains(typeof(IVendor))\r
+ && cls.Name.ToLower() == vendorClassToLoad.ToLower()\r
+ let ctorInfo = cls.GetConstructor(Type.EmptyTypes)\r
+ where ctorInfo != null\r
+ select ctorInfo).ToList();\r
+ if (ctors.Count == 0)\r
+ {\r
+ string msg = "Found no IVendor class in assembly " + assemblyToLoad + " having a string ctor";\r
+ throw new ArgumentException(msg);\r
+ }\r
+ else if (ctors.Count > 1)\r
+ {\r
+ string msg = "Found more than one IVendor class in assembly " + assemblyToLoad + " having a string ctor";\r
+ throw new ArgumentException(msg);\r
+ }\r
+ ConstructorInfo ctorInfo2 = ctors[0];\r
+\r
+ object ivendorObject;\r
+ try\r
+ {\r
+ ivendorObject = ctorInfo2.Invoke(new object[]{});\r
+ }\r
+ catch (Exception ex)\r
+ {\r
+ //TODO: add proper logging here\r
+ Console.WriteLine("DataContext ctor: Failed to invoke IVendor ctor " + ctorInfo2.Name + ": " + ex);\r
+ throw ex;\r
+ }\r
+ IVendor ivendor = (IVendor)ivendorObject;\r
+ IDbConnection dbConnection = ivendor.CreateDbConnection(connectionString);\r
+ Init(new DatabaseContext(dbConnection), null, ivendor);\r
+ #endregion\r
+ }\r
+\r
+ private void Init(IDatabaseContext databaseContext, MappingSource mappingSource, IVendor vendor)\r
+ {\r
+ if (databaseContext == null)\r
+ throw new ArgumentNullException("databaseContext");\r
+\r
+ _VendorProvider = ObjectFactory.Get<IVendorProvider>();\r
+ if (vendor == null)\r
+ Vendor = _VendorProvider.FindVendorByProviderType(typeof(SqlClient.Sql2005Provider));\r
+ else\r
+ Vendor = vendor;\r
+\r
+ DatabaseContext = databaseContext;\r
+\r
+ MemberModificationHandler = ObjectFactory.Create<IMemberModificationHandler>(); // not a singleton: object is stateful\r
+ QueryBuilder = ObjectFactory.Get<IQueryBuilder>();\r
+ QueryRunner = ObjectFactory.Get<IQueryRunner>();\r
+\r
+ //EntityMap = ObjectFactory.Create<IEntityMap>();\r
+ identityReaderFactory = ObjectFactory.Get<IIdentityReaderFactory>();\r
+\r
+ _MappingContext = new MappingContext();\r
+\r
+ // initialize the mapping information\r
+ if (mappingSource == null)\r
+ mappingSource = new AttributeMappingSource();\r
+ Mapping = mappingSource.GetModel(GetType());\r
+ }\r
+\r
+ /// <summary>\r
+ /// Checks if the table is allready mapped or maps it if not.\r
+ /// </summary>\r
+ /// <param name="tableType">Type of the table.</param>\r
+ /// <exception cref="InvalidOperationException">Thrown if the table is not mappable.</exception>\r
+ private void CheckTableMapping(Type tableType)\r
+ {\r
+ //This will throw an exception if the table is not found\r
+ if(Mapping.GetTable(tableType) == null)\r
+ {\r
+ throw new InvalidOperationException("The type '" + tableType.Name + "' is not mapped as a Table.");\r
+ }\r
+ }\r
+\r
+ /// <summary>\r
+ /// Returns a Table for the type TEntity.\r
+ /// </summary>\r
+ /// <exception cref="InvalidOperationException">If the type TEntity is not mappable as a Table.</exception>\r
+ /// <typeparam name="TEntity">The table type.</typeparam>\r
+ public Table<TEntity> GetTable<TEntity>() where TEntity : class\r
+ {\r
+ return (Table<TEntity>)GetTable(typeof(TEntity));\r
+ }\r
+\r
+ /// <summary>\r
+ /// Returns a Table for the given type.\r
+ /// </summary>\r
+ /// <param name="type">The table type.</param>\r
+ /// <exception cref="InvalidOperationException">If the type is not mappable as a Table.</exception>\r
+ public ITable GetTable(Type type)\r
+ {\r
+ lock (_tableMap)\r
+ {\r
+ ITable tableExisting;\r
+ if (_tableMap.TryGetValue(type, out tableExisting))\r
+ return tableExisting;\r
+\r
+ //Check for table mapping\r
+ CheckTableMapping(type);\r
+\r
+ var tableNew = Activator.CreateInstance(\r
+ typeof(Table<>).MakeGenericType(type)\r
+ , BindingFlags.NonPublic | BindingFlags.Instance\r
+ , null\r
+ , new object[] { this }\r
+ , System.Globalization.CultureInfo.CurrentCulture) as ITable;\r
+\r
+ _tableMap[type] = tableNew;\r
+ return tableNew;\r
+ }\r
+ }\r
+\r
+ public void SubmitChanges()\r
+ {\r
+ SubmitChanges(ConflictMode.FailOnFirstConflict);\r
+ }\r
+\r
+ /// <summary>\r
+ /// Pings database\r
+ /// </summary>\r
+ /// <returns></returns>\r
+ public bool DatabaseExists()\r
+ {\r
+ try\r
+ {\r
+ return Vendor.Ping(this);\r
+ }\r
+ catch (Exception)\r
+ {\r
+ return false;\r
+ }\r
+ }\r
+\r
+ /// <summary>\r
+ /// Commits all pending changes to database \r
+ /// </summary>\r
+ /// <param name="failureMode"></param>\r
+ public virtual void SubmitChanges(ConflictMode failureMode)\r
+ {\r
+ using (DatabaseContext.OpenConnection()) //ConnMgr will close connection for us\r
+ using (IDatabaseTransaction transaction = DatabaseContext.Transaction())\r
+ {\r
+ var queryContext = new QueryContext(this);\r
+ var entityTracks = entityTracker.EnumerateAll().ToList();\r
+ foreach (var entityTrack in entityTracks)\r
+ {\r
+ switch (entityTrack.EntityState)\r
+ {\r
+ case EntityState.ToInsert:\r
+ var insertQuery = QueryBuilder.GetInsertQuery(entityTrack.Entity, queryContext);\r
+ QueryRunner.Insert(entityTrack.Entity, insertQuery);\r
+ Register(entityTrack.Entity);\r
+ break;\r
+ case EntityState.ToWatch:\r
+ if (MemberModificationHandler.IsModified(entityTrack.Entity, Mapping))\r
+ {\r
+ var modifiedMembers = MemberModificationHandler.GetModifiedProperties(entityTrack.Entity, Mapping);\r
+ var updateQuery = QueryBuilder.GetUpdateQuery(entityTrack.Entity, modifiedMembers, queryContext);\r
+ QueryRunner.Update(entityTrack.Entity, updateQuery, modifiedMembers);\r
+\r
+ RegisterUpdateAgain(entityTrack.Entity);\r
+ }\r
+ break;\r
+ case EntityState.ToDelete:\r
+ var deleteQuery = QueryBuilder.GetDeleteQuery(entityTrack.Entity, queryContext);\r
+ QueryRunner.Delete(entityTrack.Entity, deleteQuery);\r
+\r
+ UnregisterDelete(entityTrack.Entity);\r
+ break;\r
+ default:\r
+ throw new ArgumentOutOfRangeException();\r
+ }\r
+ }\r
+ // TODO: handle conflicts (which can only occur when concurrency mode is implemented)\r
+ transaction.Commit();\r
+ }\r
+ }\r
+\r
+ /// <summary>\r
+ /// TODO - allow generated methods to call into stored procedures\r
+ /// </summary>\r
+ [DBLinqExtended]\r
+ internal IExecuteResult _ExecuteMethodCall(DataContext context, System.Reflection.MethodInfo method, params object[] sqlParams)\r
+ {\r
+ using (DatabaseContext.OpenConnection())\r
+ {\r
+ System.Data.Linq.IExecuteResult result = Vendor.ExecuteMethodCall(context, method, sqlParams);\r
+ return result;\r
+ }\r
+ }\r
+\r
+ [DbLinqToDo]\r
+ protected IExecuteResult ExecuteMethodCall(object instance, System.Reflection.MethodInfo methodInfo, params object[] parameters)\r
+ {\r
+ throw new NotImplementedException();\r
+ }\r
+\r
+ #region Identity management\r
+\r
+ [DBLinqExtended]\r
+ internal IIdentityReader _GetIdentityReader(Type t)\r
+ {\r
+ IIdentityReader identityReader;\r
+ lock (identityReaders)\r
+ {\r
+ if (!identityReaders.TryGetValue(t, out identityReader))\r
+ {\r
+ identityReader = identityReaderFactory.GetReader(t, this);\r
+ identityReaders[t] = identityReader;\r
+ }\r
+ }\r
+ return identityReader;\r
+ }\r
+\r
+ [DBLinqExtended]\r
+ internal object _GetRegisteredEntity(object entity)\r
+ {\r
+ // TODO: check what is faster: by identity or by ref\r
+ var identityReader = _GetIdentityReader(entity.GetType());\r
+ var identityKey = identityReader.GetIdentityKey(entity);\r
+ if (identityKey == null) // if we don't have an entitykey here, it means that the entity has no PK\r
+ return entity;\r
+ // even \r
+ var registeredEntityTrack = entityTracker.FindByIdentity(identityKey);\r
+ if (registeredEntityTrack != null)\r
+ return registeredEntityTrack.Entity;\r
+ return null;\r
+ }\r
+\r
+ //internal object GetRegisteredEntityByKey(IdentityKey identityKey)\r
+ //{\r
+ // return EntityMap[identityKey];\r
+ //}\r
+\r
+ /// <summary>\r
+ /// Registers an entity in a watch state\r
+ /// </summary>\r
+ /// <param name="entity"></param>\r
+ /// <returns></returns>\r
+ [DBLinqExtended]\r
+ internal object _GetOrRegisterEntity(object entity)\r
+ {\r
+ var identityReader = _GetIdentityReader(entity.GetType());\r
+ var identityKey = identityReader.GetIdentityKey(entity);\r
+ SetEntitySetsQueries(entity);\r
+ SetEntityRefQueries(entity);\r
+\r
+ // if we have no identity, we can't track it\r
+ if (identityKey == null)\r
+ return entity;\r
+\r
+ // try to find an already registered entity and return it\r
+ var registeredEntityTrack = entityTracker.FindByIdentity(identityKey);\r
+ if (registeredEntityTrack != null)\r
+ return registeredEntityTrack.Entity;\r
+\r
+ // otherwise, register and return\r
+ entityTracker.RegisterToWatch(entity, identityKey);\r
+ return entity;\r
+ }\r
+\r
+ readonly IDataMapper DataMapper = ObjectFactory.Get<IDataMapper>();\r
+ private void SetEntityRefQueries(object entity)\r
+ {\r
+ Type thisType = entity.GetType();\r
+ IList<MemberInfo> properties = DataMapper.GetEntityRefAssociations(thisType);\r
+\r
+\r
+ foreach (PropertyInfo prop in properties)\r
+ {\r
+ //example of entityRef:Order.Employee\r
+ AssociationAttribute associationInfo = prop.GetAttribute<AssociationAttribute>();\r
+ Type otherTableType = prop.PropertyType;\r
+ IList<MemberInfo> otherPKs = DataMapper.GetPrimaryKeys(Mapping.GetTable(otherTableType));\r
+\r
+ if (otherPKs.Count > 1)\r
+ throw new NotSupportedException("Multiple keys object not supported yet.");\r
+\r
+ var otherTable = GetTable(otherTableType);\r
+\r
+ //ie:EmployeeTerritories.EmployeeID\r
+\r
+ var thisForeignKeyProperty = thisType.GetProperty(associationInfo.ThisKey);\r
+ object thisForeignKeyValue = thisForeignKeyProperty.GetValue(entity, null);\r
+\r
+ IEnumerable query = null;\r
+ if (thisForeignKeyValue != null)\r
+ {\r
+ ParameterExpression p = Expression.Parameter(otherTableType, "other");\r
+ Expression predicate;\r
+ if (!(thisForeignKeyProperty.PropertyType.IsNullable()))\r
+ {\r
+ predicate = Expression.Equal(Expression.MakeMemberAccess(p, otherPKs.First()),\r
+ Expression.Constant(thisForeignKeyValue));\r
+ }\r
+ else\r
+ {\r
+ var ValueProperty = thisForeignKeyProperty.PropertyType.GetProperty("Value");\r
+ predicate = Expression.Equal(Expression.MakeMemberAccess(p, otherPKs.First()),\r
+ Expression.Constant(ValueProperty.GetValue(thisForeignKeyValue, null)));\r
+ }\r
+\r
+ query = GetOtherTableQuery(predicate, p, otherTableType, otherTable) as IEnumerable;\r
+ //it would be interesting surround the above query with a .Take(1) expression for performance.\r
+ }\r
+\r
+\r
+ FieldInfo entityRefField = entity.GetType().GetField(associationInfo.Storage, BindingFlags.NonPublic | BindingFlags.Instance);\r
+ object entityRefValue = null;\r
+ if (query != null)\r
+ entityRefValue = Activator.CreateInstance(entityRefField.FieldType, query);\r
+ else\r
+ entityRefValue = Activator.CreateInstance(entityRefField.FieldType);\r
+ entityRefField.SetValue(entity, entityRefValue);\r
+ }\r
+ }\r
+\r
+ /// <summary>\r
+ /// 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.\r
+ /// Here we set the query source of each EntitySetProperty\r
+ /// </summary>\r
+ /// <param name="entity"></param>\r
+ private void SetEntitySetsQueries(object entity)\r
+ {\r
+ IList<MemberInfo> properties = DataMapper.GetEntitySetAssociations(entity.GetType());\r
+ \r
+ if (properties.Any()) {\r
+ IList<MemberInfo> thisPKs = DataMapper.GetPrimaryKeys(Mapping.GetTable(entity.GetType()));\r
+\r
+ if (thisPKs.Count > 1)\r
+ throw new NotSupportedException("Multiple keys object not supported yet.");\r
+\r
+ object primaryKeyValue = (thisPKs.First() as PropertyInfo).GetValue(entity, null);\r
+\r
+\r
+ foreach (PropertyInfo prop in properties)\r
+ {\r
+ //example of entitySet: Employee.EmployeeTerritories\r
+ var associationInfo = prop.GetAttribute<AssociationAttribute>();\r
+ Type otherTableType = prop.PropertyType.GetGenericArguments().First();\r
+\r
+ //other table:EmployeeTerritories\r
+ var otherTable = GetTable(otherTableType);\r
+ //other table member:EmployeeTerritories.EmployeeID\r
+ var otherTableMember = otherTableType.GetProperty(associationInfo.OtherKey);\r
+\r
+\r
+ ParameterExpression p = Expression.Parameter(otherTableType, "other");\r
+ Expression predicate;\r
+ if (!(otherTableMember.PropertyType.IsNullable()))\r
+ {\r
+ predicate = Expression.Equal(Expression.MakeMemberAccess(p, otherTableMember),\r
+ Expression.Constant(primaryKeyValue));\r
+ }\r
+ else\r
+ {\r
+ var ValueProperty = otherTableMember.PropertyType.GetProperty("Value");\r
+ predicate = Expression.Equal(Expression.MakeMemberAccess(\r
+ Expression.MakeMemberAccess(p, otherTableMember),\r
+ ValueProperty),\r
+ Expression.Constant(primaryKeyValue));\r
+ }\r
+\r
+ var query = GetOtherTableQuery(predicate, p, otherTableType, otherTable);\r
+\r
+ var entitySetValue = prop.GetValue(entity, null);\r
+\r
+ if (entitySetValue == null)\r
+ {\r
+ entitySetValue = Activator.CreateInstance(prop.PropertyType);\r
+ prop.SetValue(entity, entitySetValue, null);\r
+ }\r
+\r
+ var setSourceMethod = entitySetValue.GetType().GetMethod("SetSource");\r
+ setSourceMethod.Invoke(entitySetValue, new[] { query });\r
+ //employee.EmployeeTerritories.SetSource(Table[EmployeesTerritories].Where(other=>other.employeeID="WARTH"))\r
+ }\r
+ }\r
+ }\r
+\r
+ private object GetOtherTableQuery(Expression predicate, ParameterExpression parameter, Type otherTableType, IQueryable otherTable)\r
+ {\r
+ //predicate: other.EmployeeID== "WARTH"\r
+ Expression lambdaPredicate = Expression.Lambda(predicate, parameter);\r
+ //lambdaPredicate: other=>other.EmployeeID== "WARTH"\r
+\r
+ var whereMethod = typeof(Queryable)\r
+ .GetMethods().First(m => m.Name == "Where")\r
+ .MakeGenericMethod(otherTableType);\r
+\r
+\r
+ Expression call = Expression.Call(whereMethod, otherTable.Expression, lambdaPredicate);\r
+ //Table[EmployeesTerritories].Where(other=>other.employeeID="WARTH")\r
+\r
+ return otherTable.Provider.CreateQuery(call);\r
+ }\r
+\r
+ #endregion\r
+\r
+ #region Insert/Update/Delete management\r
+\r
+ /// <summary>\r
+ /// Registers an entity for insert\r
+ /// </summary>\r
+ /// <param name="entity"></param>\r
+ internal void RegisterInsert(object entity)\r
+ {\r
+ entityTracker.RegisterToInsert(entity);\r
+ }\r
+\r
+ /// <summary>\r
+ /// Registers an entity for update\r
+ /// The entity will be updated only if some of its members have changed after the registration\r
+ /// </summary>\r
+ /// <param name="entity"></param>\r
+ internal void RegisterUpdate(object entity)\r
+ {\r
+ var identityReader = _GetIdentityReader(entity.GetType());\r
+ var identityKey = identityReader.GetIdentityKey(entity);\r
+ // if we have no key, we can not watch\r
+ if (identityKey == null)\r
+ return;\r
+ // register entity\r
+ entityTracker.RegisterToWatch(entity, identityKey);\r
+ }\r
+\r
+ /// <summary>\r
+ /// Registers or re-registers an entity and clears its state\r
+ /// </summary>\r
+ /// <param name="entity"></param>\r
+ /// <returns></returns>\r
+ internal object Register(object entity)\r
+ {\r
+ var registeredEntity = _GetOrRegisterEntity(entity);\r
+ // the fact of registering again clears the modified state, so we're... clear with that\r
+ MemberModificationHandler.Register(registeredEntity, Mapping);\r
+ return registeredEntity;\r
+ }\r
+\r
+ /// <summary>\r
+ /// Registers an entity for update\r
+ /// The entity will be updated only if some of its members have changed after the registration\r
+ /// </summary>\r
+ /// <param name="entity"></param>\r
+ /// <param name="entityOriginalState"></param>\r
+ internal void RegisterUpdate(object entity, object entityOriginalState)\r
+ {\r
+ RegisterUpdate(entity);\r
+ MemberModificationHandler.Register(entity, entityOriginalState, Mapping);\r
+ }\r
+\r
+ /// <summary>\r
+ /// Clears the current state, and marks the object as clean\r
+ /// </summary>\r
+ /// <param name="entity"></param>\r
+ internal void RegisterUpdateAgain(object entity)\r
+ {\r
+ MemberModificationHandler.ClearModified(entity, Mapping);\r
+ }\r
+\r
+ /// <summary>\r
+ /// Registers an entity for delete\r
+ /// </summary>\r
+ /// <param name="entity"></param>\r
+ internal void RegisterDelete(object entity)\r
+ {\r
+ entityTracker.RegisterToDelete(entity);\r
+ }\r
+\r
+ /// <summary>\r
+ /// Unregisters entity after deletion\r
+ /// </summary>\r
+ /// <param name="entity"></param>\r
+ internal void UnregisterDelete(object entity)\r
+ {\r
+ entityTracker.RegisterDeleted(entity);\r
+ }\r
+\r
+ #endregion\r
+\r
+ /// <summary>\r
+ /// Changed object determine \r
+ /// </summary>\r
+ /// <returns>Lists of inserted, updated, deleted objects</returns>\r
+ public ChangeSet GetChangeSet()\r
+ {\r
+ var inserts = new List<object>();\r
+ var updates = new List<object>();\r
+ var deletes = new List<object>();\r
+ foreach (var entityTrack in entityTracker.EnumerateAll())\r
+ {\r
+ switch (entityTrack.EntityState)\r
+ {\r
+ case EntityState.ToInsert:\r
+ inserts.Add(entityTrack.Entity);\r
+ break;\r
+ case EntityState.ToWatch:\r
+ if (MemberModificationHandler.IsModified(entityTrack.Entity, Mapping))\r
+ updates.Add(entityTrack.Entity);\r
+ break;\r
+ case EntityState.ToDelete:\r
+ deletes.Add(entityTrack.Entity);\r
+ break;\r
+ default:\r
+ throw new ArgumentOutOfRangeException();\r
+ }\r
+ }\r
+ return new ChangeSet(inserts, updates, deletes);\r
+ }\r
+\r
+ /// <summary>\r
+ /// use ExecuteCommand to call raw SQL\r
+ /// </summary>\r
+ public int ExecuteCommand(string command, params object[] parameters)\r
+ {\r
+ var directQuery = QueryBuilder.GetDirectQuery(command, new QueryContext(this));\r
+ return QueryRunner.Execute(directQuery, parameters);\r
+ }\r
+\r
+ /// <summary>\r
+ /// Execute raw SQL query and return object\r
+ /// </summary>\r
+ public IEnumerable<TResult> ExecuteQuery<TResult>(string query, params object[] parameters) where TResult : class, new()\r
+ {\r
+ //GetTable<TResult>();\r
+ foreach (TResult result in ExecuteQuery(typeof(TResult), query, parameters))\r
+ yield return result;\r
+ }\r
+\r
+ public IEnumerable ExecuteQuery(Type elementType, string query, params object[] parameters)\r
+ {\r
+ var queryContext = new QueryContext(this);\r
+ var directQuery = QueryBuilder.GetDirectQuery(query, queryContext);\r
+ return QueryRunner.ExecuteSelect(elementType, directQuery, parameters);\r
+ }\r
+\r
+ /// <summary>\r
+ /// Gets or sets the load options\r
+ /// </summary>\r
+ [DbLinqToDo]\r
+ public DataLoadOptions LoadOptions { get; set; }\r
+\r
+ public DbTransaction Transaction { get; set; }\r
+\r
+ /// <summary>\r
+ /// Runs the given reader and returns columns.\r
+ /// </summary>\r
+ /// <typeparam name="TResult">The type of the result.</typeparam>\r
+ /// <param name="reader">The reader.</param>\r
+ /// <returns></returns>\r
+ public IEnumerable<TResult> Translate<TResult>(DbDataReader reader)\r
+ {\r
+ foreach (TResult result in Translate(typeof(TResult), reader))\r
+ yield return result;\r
+ }\r
+\r
+ public IMultipleResults Translate(DbDataReader reader)\r
+ {\r
+ throw new NotImplementedException();\r
+ }\r
+\r
+ public IEnumerable Translate(Type elementType, DbDataReader reader)\r
+ {\r
+ return QueryRunner.EnumerateResult(elementType, reader, this);\r
+ }\r
+\r
+ public void Dispose()\r
+ {\r
+ //connection closing should not be done here.\r
+ //read: http://msdn2.microsoft.com/en-us/library/bb292288.aspx\r
+ }\r
+\r
+ [DbLinqToDo]\r
+ protected virtual void Dispose(bool disposing)\r
+ {\r
+ throw new NotImplementedException();\r
+ }\r
+\r
+ /// <summary>\r
+ /// Creates a IDbDataAdapter. Used internally by Vendors\r
+ /// </summary>\r
+ /// <returns></returns>\r
+ internal IDbDataAdapter CreateDataAdapter()\r
+ {\r
+ return DatabaseContext.CreateDataAdapter();\r
+ }\r
+\r
+ /// <summary>\r
+ /// Sets a TextWriter where generated SQL commands are written\r
+ /// </summary>\r
+ public TextWriter Log { get; set; }\r
+\r
+ /// <summary>\r
+ /// Writes text on Log (if not null)\r
+ /// Internal helper\r
+ /// </summary>\r
+ /// <param name="text"></param>\r
+ internal void WriteLog(string text)\r
+ {\r
+ if (Log != null)\r
+ Log.WriteLine(text);\r
+ }\r
+\r
+ /// <summary>\r
+ /// Write an IDbCommand to Log (if non null)\r
+ /// </summary>\r
+ /// <param name="command"></param>\r
+ internal void WriteLog(IDbCommand command)\r
+ {\r
+ if (Log != null)\r
+ {\r
+ Log.WriteLine(command.CommandText);\r
+ foreach (IDbDataParameter parameter in command.Parameters)\r
+ WriteLog(parameter);\r
+ Log.Write("--");\r
+ Log.Write(" Context: {0}", Vendor.VendorName);\r
+ Log.Write(" Model: {0}", Mapping.GetType().Name);\r
+ Log.Write(" Build: {0}", Assembly.GetExecutingAssembly().GetName().Version);\r
+ Log.WriteLine();\r
+ }\r
+ }\r
+\r
+ /// <summary>\r
+ /// Writes and IDbDataParameter to Log (if non null)\r
+ /// </summary>\r
+ /// <param name="parameter"></param>\r
+ internal void WriteLog(IDbDataParameter parameter)\r
+ {\r
+ if (Log != null)\r
+ {\r
+ // -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [2]\r
+ // -- <name>: <direction> <type> (...) [<value>]\r
+ Log.WriteLine("-- {0}: {1} {2} (Size = {3}; Prec = {4}; Scale = {5}) [{6}]",\r
+ parameter.ParameterName, parameter.Direction, parameter.DbType,\r
+ parameter.Size, parameter.Precision, parameter.Scale, parameter.Value);\r
+ }\r
+ }\r
+\r
+\r
+ [DbLinqToDo]\r
+ public bool ObjectTrackingEnabled\r
+ {\r
+ get { throw new NotImplementedException(); }\r
+ set { throw new NotImplementedException(); }\r
+ }\r
+\r
+ [DbLinqToDo]\r
+ public int CommandTimeout\r
+ {\r
+ get { throw new NotImplementedException(); }\r
+ set { throw new NotImplementedException(); }\r
+ }\r
+\r
+ [DbLinqToDo]\r
+ public bool DeferredLoadingEnabled\r
+ {\r
+ get { throw new NotImplementedException(); }\r
+ set { throw new NotImplementedException(); }\r
+ }\r
+\r
+ [DbLinqToDo]\r
+ public ChangeConflictCollection ChangeConflicts\r
+ {\r
+ get { throw new NotImplementedException(); }\r
+ }\r
+\r
+ [DbLinqToDo]\r
+ public DbCommand GetCommand(IQueryable query)\r
+ {\r
+ var qp = query.Provider as QueryProvider;\r
+ if (qp == null)\r
+ throw new InvalidOperationException();\r
+\r
+ IDbCommand dbCommand = qp.GetQuery(null).GetCommand().Command;\r
+ if (!(dbCommand is DbCommand))\r
+ throw new InvalidOperationException();\r
+\r
+ return (DbCommand)dbCommand;\r
+ }\r
+\r
+ [DbLinqToDo]\r
+ public void Refresh(RefreshMode mode, IEnumerable entities)\r
+ {\r
+ throw new NotImplementedException();\r
+ }\r
+\r
+ [DbLinqToDo]\r
+ public void Refresh(RefreshMode mode, params object[] entities)\r
+ {\r
+ throw new NotImplementedException();\r
+ }\r
+\r
+ [DbLinqToDo]\r
+ public void Refresh(RefreshMode mode, object entity)\r
+ {\r
+ throw new NotImplementedException();\r
+ }\r
+\r
+ [DbLinqToDo]\r
+ public void DeleteDatabase()\r
+ {\r
+ throw new NotImplementedException();\r
+ }\r
+\r
+ [DbLinqToDo]\r
+ public void CreateDatabase()\r
+ {\r
+ throw new NotImplementedException();\r
+ }\r
+\r
+ [DbLinqToDo]\r
+ protected internal IQueryable<TResult> CreateMethodCallQuery<TResult>(object instance, MethodInfo methodInfo, params object[] parameters)\r
+ {\r
+ throw new NotImplementedException();\r
+ }\r
+\r
+ [DbLinqToDo]\r
+ protected internal void ExecuteDynamicDelete(object entity)\r
+ {\r
+ throw new NotImplementedException();\r
+ }\r
+\r
+ [DbLinqToDo]\r
+ protected internal void ExecuteDynamicInsert(object entity)\r
+ {\r
+ throw new NotImplementedException();\r
+ }\r
+\r
+ [DbLinqToDo]\r
+ protected internal void ExecuteDynamicUpdate(object entity)\r
+ {\r
+ throw new NotImplementedException();\r
+ }\r
+ }\r
+}\r