patterncsharpMinor
Select query helper for nolock and no change tracking
Viewed 0 times
trackinghelperqueryfornolockandselectchange
Problem
I often need to switch connection strings and databases (dev/prod) and I need to be able to execute queries with the
For this purpose I create a few utilities that should make this a little bit easier.
The main class is the
It's supported by two other utilities.
a
NOLOCK flag.For this purpose I create a few utilities that should make this a little bit easier.
The main class is the
QueryService that encapsulates the enitre setup process that I had to repeat for each query/database. Most databases that I use are readonly so I do not need hte change tracking and thus the property allowing to disable it. I use model-first for all of them.public abstract class QueryService where TDbContext : DbContext
{
protected QueryService(string connectionStringName, string environmentName = null)
{
if (string.IsNullOrEmpty(connectionStringName)) { throw new ArgumentNullException("connectionStringName"); }
if (string.IsNullOrEmpty(environmentName)) { throw new ArgumentNullException("environmentName"); }
ConnectionStringName = connectionStringName;
EnvironmentName = environmentName;
AutoDetectChangesEnabled = true;
}
public Action Log { get; set; }
public string ConnectionStringName { get; private set; }
public string EnvironmentName { get; private set; }
public bool AutoDetectChangesEnabled { get; set; }
public TResult Execute(Func query, bool nolock = true)
{
if (query == null) { throw new ArgumentNullException("query"); }
var connectionStringFullName = ConnectionStringName + (string.IsNullOrEmpty(EnvironmentName) ? string.Empty : "." + EnvironmentName);
using (var context = DbContextFactory.Create(connectionStringFullName))
{
context.Configuration.AutoDetectChangesEnabled = AutoDetectChangesEnabled;
context.Database.Log = Log;
return nolock ? context.AsNolock(query) : query(context);
}
}
}It's supported by two other utilities.
a
DbContextFactory - its job is to create the context that must have a constructor accepting a connection string name.Solution
OK, let’s try to approach it in a SOLID way (everything is immutable and thread safe):
Where we use:
With extension class:
And:
And:
And:
And:
P.S. I hope all this stuff works :)
public class FooContext : DbContext
{
public virtual ICollection Strings { get; set; }
}
public static class FooQueries
{
static IConnection Connection => new Connection("connectionName", "environment")
.Log(Console.WriteLine)
.NoTracking();
public static IList GetStrings() => Connection
.Query((FooContext c) => c.Strings.ToList())
.WithNoLock()
.Execute();
}Where we use:
public interface IConnection
{
T ToContext() where T : DbContext;
}
public interface IQuery
{
TResult Execute();
}With extension class:
public static class Queries
{
public static IConnection Log(this IConnection connection, Action log) =>
new ConfiguringConnection(connection, c => c.Database.Log = log);
public static IConnection NoTracking(this IConnection connection) =>
new ConfiguringConnection(connection, c => c.Configuration.AutoDetectChangesEnabled = false);
public static IQuery Query(this IConnection connection, Func selector)
where TContext : DbContext =>
new Query(connection, selector);
public static IQuery WithNoLock(this IQuery query) =>
new NoLockQuery(query);
}And:
public class Connection : IConnection
{
public Connection(string name, string environment = null)
{
Name = name;
Environment = environment;
}
public T ToContext()
where T : DbContext =>
(T)Activator.CreateInstance(typeof(T), ToString());
public override string ToString() =>
string.IsNullOrEmpty(Environment) ? Name : Name + "." + Environment;
string Name { get; }
string Environment { get; }
}And:
class ConfiguringConnection : IConnection
{
public ConfiguringConnection(IConnection parent, Action setup)
{
Parent = parent;
Setup = setup;
}
public T ToContext() where T : DbContext
{
var context = Parent.ToContext();
Setup(context);
return context;
}
protected IConnection Parent { get; }
protected Action Setup { get; }
}And:
class NoLockQuery : IQuery
{
public NoLockQuery(IQuery parent)
{
Parent = parent;
}
public TResult Execute()
{
using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadUncommitted
}))
{
var result = Parent.Execute();
scope.Complete();
return result;
}
}
IQuery Parent { get; }
}And:
class Query : IQuery
where TContext : DbContext
{
public Query(IConnection connection, Func selector)
{
Connection = connection;
Selector = selector;
}
public TResult Execute() => Selector(Connection.ToContext());
IConnection Connection { get; }
Func Selector { get; }
}P.S. I hope all this stuff works :)
Code Snippets
public class FooContext : DbContext
{
public virtual ICollection<string> Strings { get; set; }
}
public static class FooQueries
{
static IConnection Connection => new Connection("connectionName", "environment")
.Log(Console.WriteLine)
.NoTracking();
public static IList<string> GetStrings() => Connection
.Query((FooContext c) => c.Strings.ToList())
.WithNoLock()
.Execute();
}public interface IConnection
{
T ToContext<T>() where T : DbContext;
}
public interface IQuery<TResult>
{
TResult Execute();
}public static class Queries
{
public static IConnection Log(this IConnection connection, Action<string> log) =>
new ConfiguringConnection(connection, c => c.Database.Log = log);
public static IConnection NoTracking(this IConnection connection) =>
new ConfiguringConnection(connection, c => c.Configuration.AutoDetectChangesEnabled = false);
public static IQuery<TResult> Query<TContext, TResult>(this IConnection connection, Func<TContext, TResult> selector)
where TContext : DbContext =>
new Query<TContext, TResult>(connection, selector);
public static IQuery<TResult> WithNoLock<TResult>(this IQuery<TResult> query) =>
new NoLockQuery<TResult>(query);
}public class Connection : IConnection
{
public Connection(string name, string environment = null)
{
Name = name;
Environment = environment;
}
public T ToContext<T>()
where T : DbContext =>
(T)Activator.CreateInstance(typeof(T), ToString());
public override string ToString() =>
string.IsNullOrEmpty(Environment) ? Name : Name + "." + Environment;
string Name { get; }
string Environment { get; }
}class ConfiguringConnection : IConnection
{
public ConfiguringConnection(IConnection parent, Action<DbContext> setup)
{
Parent = parent;
Setup = setup;
}
public T ToContext<T>() where T : DbContext
{
var context = Parent.ToContext<T>();
Setup(context);
return context;
}
protected IConnection Parent { get; }
protected Action<DbContext> Setup { get; }
}Context
StackExchange Code Review Q#132931, answer score: 2
Revisions (0)
No revisions yet.