I have some relatively simple transactions that run against my application like "start user session" which typically takes a couple seconds when running a VS project through SilkPerformer. When I try to execute the same transaction using the VS 2015 performance testing tools (locally on the controller) the response times are roughly twelve seconds longer, repeatedly.
I have a transaction logger that captures the response times of each transaction so I can see them individually during the execution in VS and I'm wondering if I'm missing a setting somewhere or there is a configuration change I can make to the project because it almost seems as if the project is pacing the execution of the transactions. I get the same behavior whether running on an agent or the controller directly so it doesn't appear to be network related.
This is a sample of the code that's executing:
public interface ITestTransactionTimer : IDisposable { }
public class TestTransactionTimer : ITestTransactionTimer
{
public TestContext TestContext { get; private set; }
public string TransactionName { get; private set; }
public TestTransactionTimer(TestContext testContext, string transactionName)
{
this.TestContext = testContext;
this.TransactionName = transactionName;
this.TestContext.BeginTimer(this.TransactionName);
}
public void Dispose()
{
this.TestContext.EndTimer(this.TransactionName);
}
}
public class NoOpTestTransactionTimer : ITestTransactionTimer
{
public void Dispose() { }
}
protected ITestTransactionTimer LogTransaction(string transactionName)
{
if ((null == this.TestContext) || !this.TestContext.Properties.Contains("$LoadTestUserContext"))
{
return new NoOpTestTransactionTimer();
}
return new TestTransactionTimer(this.TestContext, transactionName);
}
private TestContext testContextInstance;
public TestContext TestContext
{
get { return testContextInstance; }
set { testContextInstance = value; }
}
//starting User Session
[DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV", "C:\\Path\\Data.csv", "Data#csv", DataAccessMethod.Sequential), DeploymentItem("C:\\Path\\Data.csv"), TestMethod]
public void smokeTest()
{
sUserName = "domain\\"+TestContext.DataRow["user"].ToString();
sPassword = TestContext.DataRow["password"].ToString();
prospectClientId = TestContext.DataRow["prospectClientId"].ToString();
accountNumber = TestContext.DataRow["accountNumber"].ToString();
correlationId = "startUserSession - " + obj.ToString();
// Start User Session
using (this.LogTransaction("t1_startUserSession"))
{
ChannelFactory<IAppShellService> cfsus = null;
try
{
using (ChannelBuilder.mPContext(ref appShellServiceClient, "IAppShellService", sUserName, sPassword, 0, null, correlationId, false, effectiveDate, ref cfsus))
{
var userSessionResult = appShellServiceClient.StartUserSession();
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
finally
{
cfsus.Close();
}
}
}
The only call of EndTimer is in the Dispose method and that is not called explicitly in your code. Hence the Dispose method will only be called when the garbage collector wants to call it.
I believe you need to add an explicit call of EndTimer. Perhaps by adding a call of your Dispose method into the finally block. See also this answer.
Related
I am using Entity Framework. Below is an example of a list method for an Actors context in my ActorsDao class. If you imagine my application is like imdb, there will be CRUD methods for various other contexts such as Movies, Directors, Genres, Reviews, Studios etc.
Regardless of the method or context, I handle errors in the same way. Due to my many methods across many contexts, my catch section is always exactly the same.
Obviously, I could create an error handling class, put the code in there, and just call a method in that class from the catch block.
However, I'm wondering if there a way to omit the TRY...CATCH from each method and set up a global error handler for the methods in my entity framework layer?
I would only want this global error handler to handle these errors and not errors from the rest of the application.
I seem to remember in Java Spring, you could annotate a class or method with the name of a method, and all errors would be passed to that without the need of a TRY...CATCH. I'm wondering if there is something similar for .NET (or a third party library with such functionality)?
public List<Actor> ListActors()
{
List<Actor> actorList = new List<Actor>();
using (var context = new ActorContext())
{
try
{
actorList = context.Actors.ToList<Actor>();
}
catch (Exception e)
{
//Handle error code
}
}
return actorList;
}
EDIT
I did some more research and found this code from here https://stackoverflow.com/a/4851985/1753877
private void GlobalTryCatch(Action action)
{
try
{
action.Invoke();
}
catch (ExpectedException1 e)
{
throw MyCustomException("Something bad happened", e);
}
catch (ExpectedException2 e)
{
throw MyCustomException("Something really bad happened", e);
}
}
public void DoSomething()
{
GlobalTryCatch(() =>
{
// Method code goes here
});
}
Would using a delegate like this be OK? It certainly meets my requirements.
You can create a class like this and extend the controller from this class.
Error Handler class looks like this :
package com.wes.essex.rest;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import com.wes.essex.bean.ErrorResponse;
public class SkyNewsController {
private static final Logger LOGGER = LoggerFactory.getLogger(SkyNewsController.class);
#ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleError(Exception ex) {
LOGGER.info("start");
LOGGER.error(ex.getMessage(), ex);
ErrorResponse error = new ErrorResponse();
error.setTimestamp(ZonedDateTime.now().format(DateTimeFormatter.ISO_INSTANT));
LOGGER.debug("error : {} ", error);
ResponseEntity<ErrorResponse> response = null;
if (ex instanceof ConstraintViolationException) {
error.setReasonCode(HttpStatus.BAD_REQUEST.value());
ConstraintViolationException constraintException = (ConstraintViolationException) ex;
Set<ConstraintViolation<?>> set = constraintException.getConstraintViolations();
String errorMessage = "Input Validation Failed:";
for (ConstraintViolation<?> constraintViolation : set) {
errorMessage += constraintViolation.getMessageTemplate() + ",";
}
errorMessage = errorMessage.substring(0, errorMessage.length() - 1);
error.setErrorMessage(errorMessage);
response = new ResponseEntity<ErrorResponse>(error, HttpStatus.BAD_REQUEST);
} else {
error.setReasonCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
error.setErrorMessage(ex.getMessage());
response = new ResponseEntity<ErrorResponse>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
return response;
}
}
This would be the baean class for error response :
package com.wes.essex.bean;
public class ErrorResponse {
private static final long serialVersionUID = 5776681206288518465L;
private String timestamp;
private String errorMessage;
private int reasonCode;
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public int getReasonCode() {
return reasonCode;
}
public void setReasonCode(int reasonCode) {
this.reasonCode = reasonCode;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public static long getSerialversionuid() {
return serialVersionUID;
}
}
I need to migrate some data in an Orchard module running within a Orchard 1.9.0 installation. The issue here is that the data is stored in a foreign DB on another server, not in the Orchard DB. So when the migration class methods get called, internally Orchard uses the Orchard.Data.ISessionLocator interface to retrieve the DB connection. Sadly overriding this behavior is not possible but i had the idea to hook into the session locator thing by creating a custom session locator.
The custom session locator looks like this and is based on the existing class Orchard.Data.SessionLocator:
public class CustomSessionLocator : Orchard.Data.ISessionLocator, Orchard.Data.ITransactionManager, System.IDisposable
{
// public
public CustomSessionLocator(Orchard.Data.ISessionFactoryHolder aSessionFactoryHolder)
{
Logger = Orchard.Logging.NullLogger.Instance;
IsolationLevel = System.Data.IsolationLevel.ReadCommitted;
mSessionFactoryHolder = aSessionFactoryHolder;
mSessions = new System.Collections.Generic.Dictionary<SessionScope, NHibernate.ISession>();
if (mForeignDBConnection == null)
{
string lConnectionString = "data source=myServer;initial catalog=myDB;persist security info=True;user id=xxx;password=xxx;MultipleActiveResultSets=True;";
mForeignDBConnection = new System.Data.SqlClient.SqlConnection(lConnectionString);
}
}
public NHibernate.ISession For(System.Type aEntityType)
{
Logger.Debug("Acquiring session for {0}", aEntityType);
Demand();
return mSessions[CurrentSessionScope];
}
public void Demand()
{
EnsureSession(IsolationLevel);
}
public void RequireNew()
{
RequireNew(IsolationLevel);
}
public void RequireNew(System.Data.IsolationLevel aLevel)
{
DisposeSession();
EnsureSession(aLevel);
}
public void Cancel()
{
NHibernate.ISession lSession;
if (mSessions.TryGetValue(CurrentSessionScope, out lSession) && lSession != null && !lSession.Transaction.WasRolledBack && lSession.Transaction.IsActive)
{
Logger.Debug("Rolling back transaction");
lSession.Transaction.Rollback();
}
}
public void Dispose()
{
DisposeSession();
}
public enum SessionScope
{
OrchardDefault,
ForeignDB
}
public Orchard.Logging.ILogger Logger { get; set; }
public System.Data.IsolationLevel IsolationLevel { get; set; }
public SessionScope CurrentSessionScope { private get; set; }
// private
private void DisposeSession()
{
NHibernate.ISession lSession;
if (mSessions.TryGetValue(CurrentSessionScope, out lSession) && lSession != null)
{
try
{
if (!lSession.Transaction.WasRolledBack && lSession.Transaction.IsActive)
{
Logger.Debug("Committing transaction");
lSession.Transaction.Commit();
}
}
finally
{
Logger.Debug("Disposing session");
var lConnection = lSession.Connection;
lSession.Close();
lSession.Dispose();
lSession = null;
mSessions[CurrentSessionScope] = null;
}
}
}
private void EnsureSession(System.Data.IsolationLevel aLevel)
{
NHibernate.ISession lSession;
if (mSessions.TryGetValue(CurrentSessionScope, out lSession) && lSession != null)
return;
var lSessionFactory = mSessionFactoryHolder.GetSessionFactory();
Logger.Debug("Opening NHibernate session");
if (CurrentSessionScope == SessionScope.ForeignDB)
{
lSession = lSessionFactory.OpenSession(mForeignDBConnection);
// open connection otherwise the following lSession.BeginTransaction() fails with exception
if (mForeignDBConnection.State == System.Data.ConnectionState.Closed)
mForeignDBConnection.Open();
}
else
lSession = lSessionFactory.OpenSession();
mSessions[CurrentSessionScope] = lSession;
lSession.BeginTransaction(aLevel);
}
private readonly Orchard.Data.ISessionFactoryHolder mSessionFactoryHolder;
private System.Collections.Generic.Dictionary<SessionScope, NHibernate.ISession> mSessions;
private static System.Data.SqlClient.SqlConnection mForeignDBConnection;
}
Then i have a migration data interpreter that looks like this:
public class ForeignDataMigrationInterpreter : Orchard.Data.Migration.Interpreters.DefaultDataMigrationInterpreter
{
public ForeignDataMigrationInterpreter(
Orchard.Environment.Configuration.ShellSettings aShellSettings,
Orchard.Data.ISessionLocator aSessionLocator,
System.Collections.Generic.IEnumerable<Orchard.Data.Migration.Interpreters.ICommandInterpreter> aCommandInterpreters,
Orchard.Data.ISessionFactoryHolder aSessionFactoryHolder,
Orchard.Reports.Services.IReportsCoordinator aReportsCoordinator)
: base(aShellSettings, aSessionLocator, aCommandInterpreters, aSessionFactoryHolder, aReportsCoordinator)
{
mSessionLocator = aSessionLocator as CustomSessionLocator;
}
public override void Visit(Orchard.Data.Migration.Schema.CreateTableCommand aCommand)
{
#if LIVE
if (IsForeignDBCommand(aCommand.Name, ""))
mSessionLocator.CurrentSessionScope = CustomSessionLocator.SessionScope.ForeignDB;
else
mSessionLocator.CurrentSessionScope = CustomSessionLocator.SessionScope.OrchardDefault;
#endif
base.Visit(aCommand);
}
...
private bool IsForeignDBCommand(...)
{
return ...;
}
private CustomSessionLocator mSessionLocator;
}
As you can see, the basic procedure with foreign data is
Start Orchard
Migration class method is called which contains SchemaBuilder.CreateTable()
ForeignDataMigrationInterpreter.Visit(CreateTableCommand) is called
CurrentSessionScope of custom session locator is updated to SessionScope.ForeignDB
CreateTableCommand is passed into base class
CustomSessionLocator.For() is called which
ends in CustomSessionLocator.EnsureSession() which
returns the session X for scope SessionScope.ForeignDB
base class enlists CreateTableCommand to transaction of session X
fast forward some unrelated additional steps and the transaction is commited BUT it never returns and a timeout exception occures
My questions are
Is it even possible to migrate foreign data this way?
Why does timeout occur?
We're using a library that uses pooled objects (ServiceStack.Redis's PooledRedisClientManager). Objects are created and reused for multiple web requests. However, Dispose should be called after each use to release the object back into the pool.
By default, Ninject only deactivates an object reference if it has not been deactivated before.
What happens is that the pool instantiates an object and marks it as active. Ninject then runs the activation pipeline. At the end of the request (a web request), Ninject runs the deactivation pipeline which calls Dispose (and thus the pool marks the object as inactive). The next request: the first pooled instance is used and the pool marks it as active. However, at the end of the request, Ninject does not run its deactivation pipeline because the ActivationCache has already marked this instance as deactivated (this is in the Pipeline).
Here's a simple sample that we've added in a new MVC project to demonstrate this problem:
public interface IFooFactory
{
IFooClient GetClient();
void DisposeClient(FooClient client);
}
public class PooledFooClientFactory : IFooFactory
{
private readonly List<FooClient> pool = new List<FooClient>();
public IFooClient GetClient()
{
lock (pool)
{
var client = pool.SingleOrDefault(c => !c.Active);
if (client == null)
{
client = new FooClient(pool.Count + 1);
client.Factory = this;
pool.Add(client);
}
client.Active = true;
return client;
}
}
public void DisposeClient(FooClient client)
{
client.Active = false;
}
}
public interface IFooClient
{
void Use();
}
public class FooClient : IFooClient, IDisposable
{
internal IFooFactory Factory { get; set; }
internal bool Active { get; set; }
internal int Id { get; private set; }
public FooClient(int id)
{
this.Id = id;
}
public void Dispose()
{
if (Factory != null)
{
Factory.DisposeClient(this);
}
}
public void Use()
{
Console.WriteLine("Using...");
}
}
public class HomeController : Controller
{
private IFooClient foo;
public HomeController(IFooClient foo)
{
this.foo = foo;
}
public ActionResult Index()
{
foo.Use();
return View();
}
public ActionResult About()
{
return View();
}
}
// In the Ninject configuration (NinjectWebCommon.cs)
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IFooFactory>()
.To<PooledFooClientFactory>()
.InSingletonScope();
kernel.Bind<IFooClient>()
.ToMethod(ctx => ctx.Kernel.Get<IFooFactory>().GetClient())
.InRequestScope();
}
The solutions that we've come up with thus far are:
Mark these objects as InTransientScope() and use other deactivation mechanism (like an MVC ActionFilter to dispose of the object after each request). We'd lose the benefits of Ninject's deactivation process and require an indirect approach to disposing of the object.
Write a custom IActivationCache that checks the pool to see if the object is active. Here's what I've written so far, but I'd like some one else's eyes to see how robust it is:
public class PooledFooClientActivationCache : DisposableObject, IActivationCache, INinjectComponent, IDisposable, IPruneable
{
private readonly ActivationCache realCache;
public PooledFooClientActivationCache(ICachePruner cachePruner)
{
realCache = new ActivationCache(cachePruner);
}
public void AddActivatedInstance(object instance)
{
realCache.AddActivatedInstance(instance);
}
public void AddDeactivatedInstance(object instance)
{
realCache.AddDeactivatedInstance(instance);
}
public void Clear()
{
realCache.Clear();
}
public bool IsActivated(object instance)
{
lock (realCache)
{
var fooClient = instance as FooClient;
if (fooClient != null) return fooClient.Active;
return realCache.IsActivated(instance);
}
}
public bool IsDeactivated(object instance)
{
lock (realCache)
{
var fooClient = instance as FooClient;
if (fooClient != null) return !fooClient.Active;
return realCache.IsDeactivated(instance);
}
}
public Ninject.INinjectSettings Settings
{
get
{
return realCache.Settings;
}
set
{
realCache.Settings = value;
}
}
public void Prune()
{
realCache.Prune();
}
}
// Wire it up:
kernel.Components.RemoveAll<IActivationCache>();
kernel.Components.Add<IActivationCache, PooledFooClientActivationCache>();
Specifically for ServiceStack.Redis's: use the PooledRedisClientManager.DisposablePooledClient<RedisClient> wrapper so we always get a new object instance. Then let the client object become transient since the wrapper takes care of disposing it. This approach does not tackle the broader concept of pooled objects with Ninject and only fixes it for ServiceStack.Redis.
var clientManager = new PooledRedisClientManager();
kernel.Bind<PooledRedisClientManager.DisposablePooledClient<RedisClient>>()
.ToMethod(ctx => clientManager.GetDisposableClient<RedisClient>())
.InRequestScope();
kernel.Bind<IRedisClient>()
.ToMethod(ctx => ctx.Kernel.Get<PooledRedisClientManager.DisposablePooledClient<RedisClient>>().Client)
.InTransientScope();
Is one of these approaches more appropriate than the other?
I have not use Redis so far so I can not tell you how to do it correctly. But I can give you some input in general:
Disposing is not the only thing that is done by the ActivationPipeline. (E.g. it also does property/method injection and excuting activation/deactivation actions.) By using a custom activation cache that returns false even though it has been activated before will cause that these other actions are executed again (E.g. resulting in property injection done again.)
I'm calling my custom factory that I created (PhotoServiceFactory), which is a singleton that allows me to get at a specific custom service type back (in this case FacebookService). FacebookService is also a singleton. In FacebookService I've exposed an instance of FacebookAlbumPhoto through a property. I did this because then I don't have to have a ton of the same code over and over again creating a new instance of FacebookAlbumPhoto...I can get an instance using the FacebookService's property.
PhotoServiceFactory service = PhotoServiceFactory.CurrentPhotoServiceFactory;
FacebookService facebookService = (FacebookService)service.GetAPIService(APIType.Facebook);
FacebookAlbumPhoto facebookPhoto = facebookService.FacebookAlbumPhoto.GetFacebookAlbumPhoto(selectedPhotoID);
So this is all set up now, I created all this and just testing it now.
What's happening is my code is bombing out at this line:
FacebookAlbumPhoto facebookPhoto = facebookService.FacebookAlbumPhoto.GetFacebookAlbumPhoto(selectedPhotoID);
The error I get is when I try to reference the facebookService.FacebookAlbumPhoto instance:
CurrentSession = '_singletonInstance.CurrentSession' threw an exception of type 'System.Threading.ThreadAbortException'
So I don't know if it's because the service singleton is on one thread and then it tries to reference another singleton that's on a completely different thread and that's just not possible? That it's not possible to nest singletons like this? Or could this be another issue altogether? Cause I can't see it.
Here's my ServiceFactory:
public class PhotoServiceFactory
{
private static PhotoServiceFactory _singletonInstance;
private PhotoServiceFactory(){}
public static PhotoServiceFactory CurrentPhotoServiceFactory
{
get
{
_singletonInstance = _singletonInstance ?? (_singletonInstance = new PhotoServiceFactory());
return _singletonInstance;
}
}
public object GetAPIService(APIType apiType)
{
object apiService = null;
switch (apiType)
{
case APIType.Facebook:
apiService = FacebookService.CurrentService;
break;
// rest of code
}
return apiService;
}
So the main singleton here Service has a property to get its related Session:
Here's the FacebookServiceClass:
public class FacebookService
{
private static FacebookService _singletonInstance;
private FacebookService(){}
public FacebookSession CurrentSession
{
get
{
return FacebookSession.GetCurrentSession();
}
}
/// <summary>
/// Gets the current facebook service singleton instance.
/// </summary>
/// <value>The current facebook service.</value>
public static FacebookService CurrentService
{
get
{
_singletonInstance = _singletonInstance ?? (_singletonInstance = new FacebookService());
return _singletonInstance;
}
}
public FacebookAlbumPhoto FacebookAlbumPhoto
{
get
{
return new FacebookAlbumPhoto(); // create an instance automatically so we can start working with this object
}
}
}
Here's the session class:
public class FacebookSession
{
const string loginCallbackUrl = "http://localhost/PhotoUpload/FacebookOauth.aspx";
private FacebookSession()
{
}
public string UserID { get; private set; }
public static FacebookSession GetCurrentSession()
{
//....bunch of other logic is here
FacebookSession facebookSession = CreateNewSession();
return facebookSession;
}
public FacebookSession CreateNewSession()
{
//...some code here
FacebookSession newFacebookSession = new FacebookSession
//... rest of code...
return newFacebookSession;
}
// ... rest of code
}
UPDATED:
As requested here's my FacebookAlbumPhoto class that I created:
public class FacebookAlbumPhoto : FacebookPhotoBase
{
private FacebookSession currentSession;
public FacebookAlbumPhoto()
{
currentSession = FacebookService.CurrentService.CurrentSession;
}
#region Methods
public FacebookAlbumPhoto GetFacebookAlbumPhoto(string photoID)
{
...more code
FacebookPhotoRequest request = new FacebookPhotoRequest(currentSession.UserID, photoID);
...more code
FacebookAlbumPhoto facebookPhoto = ParseFacebookPhoto(json);
return facebookPhoto;
}
...rest of code
}
Two things. First, remember to read over Skeet's catalogue of singleton implementations.
Second, try breaking your code just before the spot where the exception occurs, and then bring up your "Exception" dialogue (ctrl-alt-e). Click the "throw" checkbox next to the CLR (second row of dialogue) and hit ok. Continue debugging your code. The results may tell you where the real problem is.
Don't forgot to go back to the Exception dialogue and remove that check from the check box, after you are done. :)
separate instance creation from initialization
I was wondering what the best way to use transations with the entity framework.
Say I have three repositories:
Repo1(ObjectContext context)
Repo2(ObjectContext context)
Repo3(ObjectContext context)
and a service object that takes the three repositories:
Service(Repo1 repo1,Repo2 repo2, Repo3 repo3)
Serive.CreateNewObject <- calls repo1, repo2, repo3 to do stuff.
So when I create the service I create three repositories first and pass them down, each repositry takes a object context so my code looks something like this:
MyObjectContext context = new MyObjectContext();
Repo1 repo = new Repo1(context);
// etc
Now I have a controller class that is responsible for calling different services and compants of my application, showing the right forms etc. Now what I want to be able to do is wrap everything that happens in one of the controller methods in a transaction so that if some thing goes wrong I can rollback back.
The controller takes a few different Service objects, but doesn't know anything about the object context.
My questions are:
Should the context be passed in to the service layer also.
How do I implement a transaction in the controller so that anything that happens in the service
layers arn't commited untill everything has passed.
Sorry if it's a bit hard to understand..
Why doesn't your controller know about the ObjectContext?
This is where I would put it. Check out - http://msdn.microsoft.com/en-us/magazine/dd882510.aspx - here the Command is what will commit/rollback the UnitOfWork(ObjectContext).
If you don't want to have your Controller know exactly about the EF (good design) then you want to abstract your ObjectContext into an interface similar to the approach in the above link.
How about using a custom TransactionScope, one that commits when all of your services have committed?
public class TransactionScope : Scope<IDbTransaction>
{
public TransactionScope()
{
InitialiseScope(ConnectionScope.CurrentKey);
}
protected override IDbTransaction CreateItem()
{
return ConnectionScope.Current.BeginTransaction();
}
public void Commit()
{
if (CurrentScopeItem.UserCount == 1)
{
TransactionScope.Current.Commit();
}
}
}
So the transaction is only committed when the UserCount is 1, meaning the last service has committed.
The scope classes are (shame we can't do attachements...):
public abstract class Scope<T> : IDisposable
where T : IDisposable
{
private bool disposed = false;
[ThreadStatic]
private static Stack<ScopeItem<T>> stack = null;
public static T Current
{
get { return stack.Peek().Item; }
}
internal static string CurrentKey
{
get { return stack.Peek().Key; }
}
protected internal ScopeItem<T> CurrentScopeItem
{
get { return stack.Peek(); }
}
protected void InitialiseScope(string key)
{
if (stack == null)
{
stack = new Stack<ScopeItem<T>>();
}
// Only create a new item on the stack if this
// is different to the current ambient item
if (stack.Count == 0 || stack.Peek().Key != key)
{
stack.Push(new ScopeItem<T>(1, CreateItem(), key));
}
else
{
stack.Peek().UserCount++;
}
}
protected abstract T CreateItem();
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// If there are no users for the current item
// in the stack, pop it
if (stack.Peek().UserCount == 1)
{
stack.Pop().Item.Dispose();
}
else
{
stack.Peek().UserCount--;
}
}
// There are no unmanaged resources to release, but
// if we add them, they need to be released here.
}
disposed = true;
}
}
public class ScopeItem<T> where T : IDisposable
{
private int userCount;
private T item;
private string key;
public ScopeItem(int userCount, T item, string key)
{
this.userCount = userCount;
this.item = item;
this.key = key;
}
public int UserCount
{
get { return this.userCount; }
set { this.userCount = value; }
}
public T Item
{
get { return this.item; }
set { this.item = value; }
}
public string Key
{
get { return this.key; }
set { this.key = value; }
}
}
public class ConnectionScope : Scope<IDbConnection>
{
private readonly string connectionString = "";
private readonly string providerName = "";
public ConnectionScope(string connectionString, string providerName)
{
this.connectionString = connectionString;
this.providerName = providerName;
InitialiseScope(string.Format("{0}:{1}", connectionString, providerName));
}
public ConnectionScope(IConnectionDetailsProvider connectionDetails)
: this(connectionDetails.ConnectionString, connectionDetails.ConnectionProvider)
{
}
protected override IDbConnection CreateItem()
{
IDbConnection connection = DbProviderFactories.GetFactory(providerName).CreateConnection();
connection.ConnectionString = connectionString;
connection.Open();
return connection;
}
}
Wrap the operation in a TransactionScope.
You might want to implement the transaction model used by the Workflow Foundation. It basically has an interface that all "components" implement. After each does the main work successfully, then the host calls the "commit" method on each. If one failed, it calls the "rollback" method.