ContextManager Suggestion

ContextManager Suggestion

Old forum URL: forums.lhotka.net/forums/t/6875.aspx


Mark5000 posted on Wednesday, April 29, 2009

Greetings,

While debugging the other day I noticed that the ContextManager doesn't use the ConnectionManager.  By piggy-backing onto the ConnectionManager the DataContext could minimize open connections and potentially prevent .NET from needlessly promoting a transaction to the distributed transaction coordinator.  This is especially significant if a dataportal is in the context of a transaction and contains a mix of both ADO.NET and LINQ-to-SQL.  Here's the change I made to the ContextManager's constructor to address this issue:

//private ContextManager(string connectionString)

//{

// _connectionString = connectionString;

// _context = (C)(Activator.CreateInstance(typeof(C), connectionString));

//}

private ContextManager(string connectionString)

{

_connectionString = connectionString;

_context = (C)(Activator.CreateInstance(typeof(C), ConnectionManager<System.Data.SqlClient.SqlConnection>.GetManager(connectionString, false).Connection));

}

Does anyone see any potential issues with this solution?  Does it seem a sound enough improvement to add to the Csla wishlist? 

ajj3085 replied on Thursday, April 30, 2009

Yup, here's the problem:

System.Data.SqlClient.SqlConnection

You're now forcing everyone to use Sql Server.

Mark5000 replied on Thursday, April 30, 2009

I don't think this poses a problem.

The ContextManager uses generics to constrain itself to a DataContext object.  DataContext objects are only used with LINQ-To-SQL and LINQ-To-SQL is, in turn, only applicable to SQL Server.  Given this train of thought a SqlConnection IS the only valid option when connecting to a database via the ContextManager.

Let me know if I'm mistaken.

ajj3085 replied on Thursday, April 30, 2009

Ha... good point. Guess I should cut back on my caffeine in the morning.

So I'm guessing that Linq to EF doesn't use DataContext at all, so looks like a neat trick. The only downside is that you now have a ConnectionManager instance floating around if even if you're NOT mixing Ado.Net and Linq... but maybe that doesn't really matter.

RockfordLhotka replied on Thursday, April 30, 2009

For LINQ to SQL this seems reasonable. Unfortunately this is not a future-looking technology, but it is fine for the short term.

For the EF ObjectContextManager I suspect an extra type parameter would be required to do the same thing, because EF can talk to other database types.

However, there's another wish list item that might conflict with all of this. And that is a request to add a "label" or "name" parameter to GetManager so it is possible to have more than one connection open at a time. Bascially:

var ctxA = ConnectionManager<SqlConnection>.GetManager(dbName, "ConnectionA");
var ctxB = ConnectionManager<SqlConnection>.GetManager(dbName, "ConnectionB");

That request is fairly important, because it enables some scenarios where multiple connections are required to do simultaneous work against the database (or multiple databases).

So if we assume that other wish list item comes into being, the question then becomes how this request would work. Would the name of the ContextManager be linked to the name of the underlying ConnectionManager? Is that also true for ObjectContextManager?

Mark5000 replied on Friday, May 01, 2009

I can definitely see how being able to specify an optional "label" or "identifier" could be helpful when more than one connection is explicitly needed (eg. such as when a long running sproc is chugging away but the app still needs to do other database work).  To accomodate this I could see the ConnectionManagerT and the ContextManager looking something like the code at the bottom of this post (where the "identifier" is being shared between the two objects - as you suggested).

Unfortunately it appears to be rather difficult to get an ObjectContext to share a DbConnection.  On top of that an exception gets thrown if the DbConnection has already been opened when it's passed to the EntityConnection. Bummer.  This is where I'm at right now: http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/dd7b1c41-e428-4e29-ab83-448d3f529ba4/ 

Any ideas?

 

using System;

using System.Configuration;

using System.Data;

using Csla.Properties;

namespace Csla.Data

{

/// <summary>

/// Provides an automated way to reuse open

/// database connections within the context

/// of a single data portal operation.

/// </summary>

/// <typeparam name="C">

/// Type of database connection object to use.

/// </typeparam>

/// <remarks>

/// This type stores the open database connection

/// in <see cref="Csla.ApplicationContext.LocalContext" />

/// and uses reference counting through

/// <see cref="IDisposable" /> to keep the connection

/// open for reuse by child objects, and to automatically

/// dispose the connection when the last consumer

/// has called Dispose."

/// </remarks>

public class ConnectionManagerV2<C> : IDisposable where C : IDbConnection, new()

{

private static object _lock = new object();

private C _connection;

private string _connectionString;

private string _connectionIdentifier;

/// <summary>

/// Gets the default ConnectionManager object for the database.

/// </summary>

/// <param name="database">

/// Database name as shown in the config file.

/// </param>

public static ConnectionManagerV2<C> GetManager(string database)

{

return GetManager(database, true, null);

}

/// <summary>

/// Gets the default ConnectionManager object for the database.

/// </summary>

/// <param name="database">Database name or connection string.</param>

/// <param name="isDatabaseName">

/// True to indicate that the connection string

/// should be retrieved from the config file. If

/// False, the database parameter is directly

/// used as a connection string.

/// </param>

public static ConnectionManagerV2<C> GetManager(string database, bool isDatabaseName)

{

return GetManager(database, isDatabaseName, null);

}

/// <summary>

/// Gets a specific ConnectionManager object for the database.

/// </summary>

/// <param name="database">Database name as shown in the config file.</param>

/// <param name="connectionIdentifier">Identifier for the connection. Null, if the

/// default connection is to be used.</param>

public static ConnectionManagerV2<C> GetManager(string database, string connectionIdentifier)

{

return GetManager(database, true, connectionIdentifier);

}

/// <summary>

/// Gets a specific ConnectionManager object for the database.

/// </summary>

/// <param name="database">

/// The database name or connection string.

/// </param>

/// <param name="isDatabaseName">

/// True to indicate that the connection string

/// should be retrieved from the config file. If

/// False, the database parameter is directly

/// used as a connection string.

/// </param>

/// <param name="connectionIdentifier">

/// Identifier for the connection. Null, if the

/// default connection is to be used.</param>

/// </param>

/// <returns>ConnectionManager object for the name.</returns>

public static ConnectionManagerV2<C> GetManager(string database, bool isDatabaseName, string connectionIdentifier)

{

if (isDatabaseName)

{

var connection = ConfigurationManager.ConnectionStrings[database];

if (connection == null)

throw new ConfigurationErrorsException(String.Format(Resources.DatabaseNameNotFound, database));

var conn = ConfigurationManager.ConnectionStrings[database].ConnectionString;

if (string.IsNullOrEmpty(conn))

throw new ConfigurationErrorsException(String.Format(Resources.DatabaseNameNotFound, database));

database = conn;

}

lock (_lock)

{

ConnectionManagerV2<C> mgr = null;

string databaseKey = "__db:" + database;

if (connectionIdentifier != null)

databaseKey = databaseKey + "_" + connectionIdentifier;

if (ApplicationContext.LocalContext.Contains(databaseKey))

{

mgr = (ConnectionManagerV2<C>)(ApplicationContext.LocalContext[databaseKey]);

}

else

{

mgr = new ConnectionManagerV2<C>(database, connectionIdentifier);

ApplicationContext.LocalContext[databaseKey] = mgr;

}

mgr.AddRef();

return mgr;

}

}

private ConnectionManagerV2(string connectionString, string connectionIdentifier)

{

_connectionString = connectionString;

_connectionIdentifier = connectionIdentifier;

// open connection

_connection = new C();

_connection.ConnectionString = connectionString;

_connection.Open();

}

/// <summary>

/// Gets the open database connection object.

/// </summary>

public C Connection

{

get

{

return _connection;

}

}

#region Reference counting

private int mRefCount;

private void AddRef()

{

mRefCount += 1;

}

private void DeRef()

{

lock (_lock)

{

mRefCount -= 1;

if (mRefCount == 0)

{

_connection.Dispose();

string databaseKey = "__db:" + _connectionString;

if (_connectionIdentifier != null)

databaseKey = databaseKey + "_" + _connectionIdentifier;

ApplicationContext.LocalContext.Remove(databaseKey);

}

}

}

#endregion

#region IDisposable

/// <summary>

/// Dispose object, dereferencing or

/// disposing the connection it is

/// managing.

/// </summary>

public void Dispose()

{

DeRef();

}

#endregion

}

}

 

 

#if !CLIENTONLY

using System;

using System.Configuration;

using System.Data.Linq;

using Csla.Properties;

namespace Csla.Data

{

/// <summary>

/// Provides an automated way to reuse

/// LINQ data context objects within the context

/// of a single data portal operation.

/// </summary>

/// <typeparam name="C">

/// Type of database

/// LINQ data context objects object to use.

/// </typeparam>

/// <remarks>

/// This type stores the LINQ data context object

/// in <see cref="Csla.ApplicationContext.LocalContext" />

/// and uses reference counting through

/// <see cref="IDisposable" /> to keep the data context object

/// open for reuse by child objects, and to automatically

/// dispose the object when the last consumer

/// has called Dispose."

/// </remarks>

public class ContextManagerV2<C> : IDisposable where C : DataContext

{

private static object _lock = new object();

private C _context;

private string _connectionString;

private string _connectionIdentifier;

/// <summary>

/// Gets the ContextManager object for the

/// specified database.

/// </summary>

/// <param name="database">

/// Database name as shown in the config file.

/// </param>

public static ContextManagerV2<C> GetManager(string database)

{

return GetManager(database, true, null);

}

public static ContextManagerV2<C> GetManager(string database, bool isDatabaseName)

{

return GetManager(database, isDatabaseName, null);

}

public static ContextManagerV2<C> GetManager(string database, string connectionIdentifier)

{

return GetManager(database, true, connectionIdentifier);

}

/// <summary>

/// Gets the ContextManager object for the

/// specified database.

/// </summary>

/// <param name="database">

/// The database name or connection string.

/// </param>

/// <param name="isDatabaseName">

/// True to indicate that the connection string

/// should be retrieved from the config file. If

/// False, the database parameter is directly

/// used as a connection string.

/// </param>

/// <returns>ContextManager object for the name.</returns>

public static ContextManagerV2<C> GetManager(string database, bool isDatabaseName, string connectionIdentifier)

{

if (isDatabaseName)

{

var connection = ConfigurationManager.ConnectionStrings[database];

if (connection == null)

throw new ConfigurationErrorsException(String.Format(Resources.DatabaseNameNotFound, database));

var conn = ConfigurationManager.ConnectionStrings[database].ConnectionString;

if (string.IsNullOrEmpty(conn))

throw new ConfigurationErrorsException(String.Format(Resources.DatabaseNameNotFound, database));

database = conn;

}

lock (_lock)

{

ContextManagerV2<C> mgr = null;

string contextKey = "__ctx:" + database;

if (connectionIdentifier != null)

contextKey = contextKey + "_" + connectionIdentifier;

if (ApplicationContext.LocalContext.Contains(contextKey))

{

mgr = (ContextManagerV2<C>)(ApplicationContext.LocalContext[contextKey]);

}

else

{

mgr = new ContextManagerV2<C>(database, connectionIdentifier);

ApplicationContext.LocalContext[contextKey] = mgr;

}

mgr.AddRef();

return mgr;

}

}

private ContextManagerV2(string connectionString, string connectionIdentifier)

{

_connectionString = connectionString;

_connectionIdentifier = connectionIdentifier;

_context = (C)(Activator.CreateInstance(typeof(C), ConnectionManagerV2<System.Data.SqlClient.SqlConnection>.GetManager(connectionString, false, connectionIdentifier).Connection));

}

/// <summary>

/// Gets the LINQ data context object.

/// </summary>

public C DataContext

{

get

{

return _context;

}

}

#region Reference counting

private int mRefCount;

private void AddRef()

{

mRefCount += 1;

}

private void DeRef()

{

lock (_lock)

{

mRefCount -= 1;

if (mRefCount == 0)

{

_context.Dispose();

string contextKey = "__ctx:" + _connectionString;

if (_connectionIdentifier != null)

contextKey = contextKey + "_" + _connectionIdentifier;

ApplicationContext.LocalContext.Remove(contextKey);

}

}

}

#endregion

#region IDisposable

/// <summary>

/// Dispose object, dereferencing or

/// disposing the context it is

/// managing.

/// </summary>

public void Dispose()

{

DeRef();

}

#endregion

}

}

#endif

Copyright (c) Marimer LLC