Guidance on replacing ConnectionManager #2979
-
I'm still in process of converting my existing code to CSLA 6, and my DAL is using ADO.Net and the Connection Manager. I came across issue #2633 that now marks ConnectionManager as obsolete, so I'm working on the recommended approach of using DI. I'm looking for guidance on whether I'm on the right track before I go about converting all of my DAL objects. Here is a sample of what my Csla 5 DAL looks like using CslaGenFork: public partial class AdvGenderLookupCollectionDal : IAdvGenderLookupCollectionDal
{
private readonly string _connectionString;
#region Constructor
public AdvGenderLookupCollectionDal(IDalConfigDad dalConfig)
{
_connectionString = dalConfig.ConnectionString;
}
#endregion
#region DAL methods
/// <summary>
/// Loads a AdvGenderLookupCollection collection from the database.
/// </summary>
/// <param name="checkDate">The checkDate parameter of the AdvGenderLookupCollection to fetch.</param>
/// <param name="advGenderID">The advGenderID parameter of the AdvGenderLookupCollection to fetch.</param>
/// <param name="advocacyGroupID">The advocacyGroupID parameter of the AdvGenderLookupCollection to fetch.</param>
/// <param name="timeZone">The timeZone parameter of the AdvGenderLookupCollection to fetch.</param>
/// <returns>A list of <see cref="AdvGenderLookupDto"/>.</returns>
public List<AdvGenderLookupDto> Fetch(SmartDate checkDate, int advGenderID, int advocacyGroupID, short timeZone)
{
using (var ctx = ConnectionManager<SqlConnection>.GetManager(_connectionString, false))
{
using (var cmd = new SqlCommand("procAdmin_AdvGendersByActiveDate_Select", ctx.Connection))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@CheckDate", checkDate.DBValue).DbType = DbType.DateTime;
cmd.Parameters.AddWithValue("@AdvGenderID", advGenderID).DbType = DbType.Int32;
cmd.Parameters.AddWithValue("@AdvocacyGroupID", advocacyGroupID).DbType = DbType.Int32;
cmd.Parameters.AddWithValue("@TimeZone", timeZone).DbType = DbType.Int16;
var dr = cmd.ExecuteReader();
return LoadCollection(dr);
}
}
}
private List<AdvGenderLookupDto> LoadCollection(IDataReader data)
{
var advGenderLookupCollection = new List<AdvGenderLookupDto>();
using (var dr = new SafeDataReader(data))
{
while (dr.Read())
{
advGenderLookupCollection.Add(Fetch(dr));
}
}
return advGenderLookupCollection;
}
private AdvGenderLookupDto Fetch(SafeDataReader dr)
{
var advGenderLookup = new AdvGenderLookupDto();
// Value properties
advGenderLookup.AdvGenderID = dr.GetInt32("AdvGenderID");
advGenderLookup.Name = dr.GetString("Name");
advGenderLookup.IsDefault = dr.GetBoolean("IsDefault");
return advGenderLookup;
}
#endregion
} And the startup code for that object and the dependant IDalConfigDad object is defined as: services.AddSingleton<IDalConfigDad, DalConfigDad>(options => new DalConfigDad(connectionString));
services.AddTransient<IAdvGenderDal, AdvGenderDal>(); Switching to Csla 6 I'm using the following guidance: I created an interfaced based off of IDbConnection: public interface IDalConnection : IDbConnection
{
} and defining the service in the startup as: services.AddScoped<IDalConnection>((provider) => (IDalConnection)(IDbConnection)(new Microsoft.Data.SqlClient.SqlConnection(connectionString))); Note that I needed to explicity cast first to an IDbConnection and then to IDallConnection in order to get it to compile. This is different than Rocky's sample in #2633, in which it looks with his code, the implicit cast works. However, when I run my code, I end up with an exception that it is unable to explicitly cast a SqlConnection type to IDalConnection. If I just use a scoped IDbConnection in my startup like this: services.AddScoped<IDbConnection>((provider) => new Microsoft.Data.SqlClient.SqlConnection(connectionString)); It works without issue. Here is what the DAL Object was changed to: public partial class AdvGenderLookupCollectionDal : IAdvGenderLookupCollectionDal
{
private readonly SqlConnection _connection;
#region Constructor
public AdvGenderLookupCollectionDal(IDbConnection dalConnection)
{
_connection = (SqlConnection)dalConnection;
}
#endregion
#region DAL methods
/// <summary>
/// Loads a AdvGenderLookupCollection collection from the database.
/// </summary>
/// <param name="checkDate">The checkDate parameter of the AdvGenderLookupCollection to fetch.</param>
/// <param name="advGenderID">The advGenderID parameter of the AdvGenderLookupCollection to fetch.</param>
/// <param name="advocacyGroupID">The advocacyGroupID parameter of the AdvGenderLookupCollection to fetch.</param>
/// <param name="timeZone">The timeZone parameter of the AdvGenderLookupCollection to fetch.</param>
/// <returns>A list of <see cref="AdvGenderLookupDto"/>.</returns>
public List<AdvGenderLookupDto> Fetch(SmartDate checkDate, int advGenderID, int advocacyGroupID, short timeZone)
{
using (_connection)
{
_connection.Open();
using (var cmd = new SqlCommand("procAdmin_AdvGendersByActiveDate_Select", _connection))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@CheckDate", checkDate.DBValue).DbType = DbType.DateTime;
cmd.Parameters.AddWithValue("@AdvGenderID", advGenderID).DbType = DbType.Int32;
cmd.Parameters.AddWithValue("@AdvocacyGroupID", advocacyGroupID).DbType = DbType.Int32;
cmd.Parameters.AddWithValue("@TimeZone", timeZone).DbType = DbType.Int16;
var dr = cmd.ExecuteReader();
return LoadCollection(dr);
}
}
}
private List<AdvGenderLookupDto> LoadCollection(IDataReader data)
{
var advGenderLookupCollection = new List<AdvGenderLookupDto>();
using (var dr = new SafeDataReader(data))
{
while (dr.Read())
{
advGenderLookupCollection.Add(Fetch(dr));
}
}
return advGenderLookupCollection;
}
private AdvGenderLookupDto Fetch(SafeDataReader dr)
{
var advGenderLookup = new AdvGenderLookupDto();
// Value properties
advGenderLookup.AdvGenderID = dr.GetInt32("AdvGenderID");
advGenderLookup.Name = dr.GetString("Name");
advGenderLookup.IsDefault = dr.GetBoolean("IsDefault");
return advGenderLookup;
}
#endregion
} So here are my questions:
I'm intending to update the CslaGenFork templates to output this DAL object, so I'm looking for a best practice here. |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 9 replies
-
I answered a stackoverflow question similar to this: https://stackoverflow.com/questions/72295421/upgrade-to-csla-6-connectionmanager-problem Maybe that'll help? |
Beta Was this translation helpful? Give feedback.
-
I think there are several ways to use DI to manage database connections. The key thing to understand is that each server-side data portal request runs in its own DI scope, so all scoped services are created for the lifetime of the request and are disposed/closed at the end of the request. You can't subclass using Microsoft.Data.SqlClient;
namespace CslaDataAccess
{
/// <summary>
/// Base class for scoped db connection provider
/// </summary>
public class DbProvider
{
public virtual SqlConnection? DbConnection { get; protected set; }
public virtual SqlTransaction? DbTransaction { get; protected set; }
}
} And then a subclass for each specific database connection string you need to inject into your code. using Microsoft.Data.SqlClient;
namespace CslaDataAccess
{
public class MainDb : DbProvider
{
public MainDb()
{
DbConnection = new SqlConnection("main db connection string");
DbTransaction = DbConnection.BeginTransaction();
}
}
public class LoggerDb : DbProvider
{
public LoggerDb() => DbConnection = new SqlConnection("logger db connection string");
}
} In your services.AddScoped<MainDb>();
services.AddScoped<LoggerDb>(); Then inject Or maybe you say that each time you use services.AddScoped<MainDb>();
services.AddTransient<LoggerDb>(); That way, each time you inject a Conceptually, your code that uses these services does this: using (var scope = provider.CreateScope())
{
var mainDb = scope.ServiceProvider.GetRequiredService<MainDb>();
// do stuff here
mainDb.DbTransaction?.Commit();
} Though in reality you rely on the data portal to create/dispose the scope, so your DAL code can just inject the namespace CslaDataAccess
{
/// <summary>
/// This is a transient service
/// </summary>
public class PersonDal
{
public PersonDal(MainDb mainDb)
{
if (mainDb is null) throw new NullReferenceException(nameof(mainDb));
MainDb = mainDb;
}
private MainDb MainDb { get; set; }
public async Task<int> Insert(PersonData data)
{
int newId = -1;
using (var command = MainDb.DbTransaction.Connection.CreateCommand())
{
// set up command
using (var reader = await command.ExecuteReaderAsync())
{
// get newid from reader
}
}
await MainDb.DbTransaction?.CommitAsync();
return newId;
}
}
} |
Beta Was this translation helpful? Give feedback.
I think there are several ways to use DI to manage database connections. The key thing to understand is that each server-side data portal request runs in its own DI scope, so all scoped services are created for the lifetime of the request and are disposed/closed at the end of the request.
You can't subclass
SqlConnection
, so it needs to be wrapped in another class. For example, you could have a base class like this: