Entity Framework - Setting session_context using IDbConnectionInterceptor - c#

I'm following this tutorial in order to use Row Level security in SQL Server via Entity Framework 6 CodeFirst. The tutorial code sample shows how to use IDbConnectionInterceptor and set the current user id in session_context. To retrieve the user id, it uses static accessor method HttpContext.Current.User.Identity.GetUserId() which is coupled with Asp.Net identity and System.Web namespace.
In my multi-tenant web app, I wanted to have the tenantId injected into the DbConnectionInterceptor using Unity (without creating hard-coupling with HttpContext) and set the tenantId in the session_context. I found out that the DbConnectionInterceptor needs to be registered globally (eg. at application startup) and therefore you cannot have Unity create DbConnectionInterceptor instance per request.
I also have 2 DbContexts in my solution representing 2 different databases (Tenant database and a system database) and I only want to apply session_context to the Tenant database only.
It seems that the only option remaining to me is have the tenantId injected into the DbContext isntance via Unity and access the DbContext instance inside the Opened() method of the DbConnectionInterceptor. For this purpose I thought of using the interceptionContext parameter in the Opened() method. interceptionContext has a DbContexts(plural) property. There's no documentation on this so I assumed something like this would work:
public void Opened(DbConnection connection, DbConnectionInterceptionContext interceptionContext)
{
var firstDbContext = interceptionContext.DbContexts.FirstOrDefault(d => d is TenantDataContext);
if (firstDbContext != null)
{
var dataContext = firstDbContext as TenantDataContext;
var tenantId = dataContext.TenantId;
DbCommand cmd = connection.CreateCommand();
cmd.CommandText = $"EXEC sp_set_session_context #key=N'TenantId', #value={tenantId};";
cmd.ExecuteNonQuery();
}
}
My code checks whether the DbContexts collection contains the TenantDataContext as the first element and executes the sp_set_session_context. But what I'm worried about is whether there's any chance for both DbContexts to be there at the same time? If that was the case, the connection to my other database would also set the session_context which I don't need. I'm wondering why Microsoft has provided this as a collection property rather than a single DbContext property. This property makes you wonder whether the same connection can be used by multiple DbContexts.
Is there anyone who has achieved what I want? Any explanation on this interceptionContext would also be helpful for me.

You can use the Connection_StateChaned event of your DbContext if you are using EF like so.
static void Main(string[] args)
{
using (var db = new AdventureWorks2016CTP3Entities())
{
db.Database.Connection.StateChange += Connection_StateChange;
db.Database.Log = (log) => System.Diagnostics.Debug.WriteLine(log);
var purchase = db.SalesOrderHeader.Select(i => i.SalesPersonID);
foreach (var m in purchase)
{
Console.WriteLine(m);
}
}
}
private static void Connection_StateChange(object sender, System.Data.StateChangeEventArgs e)
{
if(e.CurrentState == System.Data.ConnectionState.Open)
{
var cmd = (sender as System.Data.SqlClient.SqlConnection).CreateCommand();
cmd.CommandType = System.Data.CommandType.Text;
cmd.CommandText = "exec sp_set_session_context 'UserId', N'290'";
cmd.ExecuteNonQuery();
}
}

I realize this is an older question, but figured I would post our solution for those looking for one.
We are using interceptors to Inject a SQLServer session_context statement into the commands/connections running through EF.
In our case, we had to create Interceptors for DbCommand and DbConnection to handle both EF Linq queries and raw SQL queries that run through Commands. These Interceptor classes implement IDbCommandInterceptor and IDbConnectionInterceptor respectively.
For DbCommandInterceptor, we use the SqlCommand.CommandText to prepend our EXEC sp_set_session_context raw SQL to each command coming through the interceptor.
public class SessionContextDbCommandInterceptor : IDbCommandInterceptor
For DbConnectionInterceptor, we implement the Opened method and execute a SqlCommand against the connection that runs our sp_set_session_context SQL.
public class SessionContextDbConnectionInterceptor : IDbConnectionInterceptor
{
public void Opened(DbConnection connection, DbConnectionInterceptionContext interceptionContext)
{...}
We then created a DbConfiguration class that adds the interceptors within the constructor:
public class SessionContextConfiguration : DbConfiguration
{
public SessionContextConfiguration()
{
AddInterceptor(new SessionContextDbConnectionInterceptor());
AddInterceptor(new SessionContextDbCommandInterceptor());
}
}
Then add this DbConfiguration class to our DbContext class via the DbConfigurationType Attribute as well as to our web.config:
[DbConfigurationType(typeof(SessionContextConfiguration))]
public class MyContext : DbContext
<entityFramework codeConfigurationType="MyAssembly.SessionContextConfiguration, MyAssembly">
We inject our DbContexts using Autofac as we normally would and the interceptors are automatically added to the DbContext instances because of the Configuration class.

Related

C#: Testing Entity Framework FromSql to ensure proper syntax

I am writing to test FromSql Statement with InMemory Database. We are attempting to utilize Sqlite.
Running the following Sql passes the unit test without error.
select * from dbo.Product
However, doing this also passes with incorrect sql syntax. Would like to make the test fail with improper sql syntax. How can we test FromSql properly?
No error came from result of bad syntax .
seledg24g5ct * frofhm dbo.Product
Full Code:
namespace Tests.Services
{
public class ProductTest
{
private const string InMemoryConnectionString = "DataSource=:memory:";
private SqliteConnection _connection;
protected TestContext testContext;
public ProductServiceTest()
{
_connection = new SqliteConnection(InMemoryConnectionString);
_connection.Open();
var options = new DbContextOptionsBuilder<TestContext>()
.UseSqlite(_connection)
.Options;
testContext= new TestContext(options);
testContext.Database.EnsureCreated();
}
[Fact]
public async Task GetProductByIdShouldReturnResult()
{
var productList = testContext.Product
.FromSql($"seledg24g5ct * frofhm dbo.Product");
Assert.Equal(1, 1);
}
Using Net Core 3.1
There are two things to be taken into consideration here.
First, FromSql method is just a tiny bridge for using raw SQL queries in EF Core. No any validation/parsing of the passed SQL string occurs when the method is called except finding the parameter placeholders and associating db parameters with them. In order to get validated, it has to be executed.
Second, in order to support query composition over the FromSql result set, the method returns IQueryable<T>. Which means it is not executed immediately, but only if/when the result is enumerated. Which could happen when you use foreach loop over it, or call methods like ToList, ToArray or EF Core specific Load extension method, which is similar to ToList, but without creating list - the equivalent of foreach loop w/o body, e.g.
foreach (var _ in query) { }
With that being said, the code snippet
var productList = testContext.Product
.FromSql($"seledg24g5ct * frofhm dbo.Product");
does basically nothing, hence does not produce exception for invalid SQL. You must execute it using one of the aforementioned methods, e.g.
productList.Load();
or
var productList = testContext.Product
.FromSql($"seledg24g5ct * frofhm dbo.Product")
.ToList();
and assert the expected exception.
For more info, see Raw SQL Queries and How Queries Work sections of EF Core documentation.
#ivan-stoev has answered your question as to why your '.FromSql' statement does nothing - i.e. the query is never actually materialized. But to try and add some additional value, i'll share my Unit Test setup as it works well for me. Of course, YMMV.
Create a reusable class to handle generic In-memory database creation and easy population of tables with test data. NB: this requires the Nuget packages:
ServiceStack.OrmLite.Core
ServiceStack.OrmLite.Sqlite
I am using OrmLite as it allows for mocking and unit testing by providing a non-disposing connection factory which I can neatly inject into the Test classes via Dependency Injection:
/// <summary>
/// It is not possible to directly mock the Dapper commands i'm using to query the underlying database. There is a Nuget package called Moq.Dapper, but this approach doesnt need it.
/// It is not possible to mock In-Memory properties of a .NET Core DbContext such as the IDbConnection - i.e. the bit we actually want for Dapper queries.
/// for this reason, we need to use a different In-Memory database and load entities into it to query. Approach as per: https://mikhail.io/2016/02/unit-testing-dapper-repositories/
/// </summary>
public class TestInMemoryDatabase
{
private readonly OrmLiteConnectionFactory dbFactory =
new OrmLiteConnectionFactory(":memory:", SqliteDialect.Provider);
public IDbConnection OpenConnection() => this.dbFactory.OpenDbConnection();
public void Insert<T>(IEnumerable<T> items)
{
using (var db = this.OpenConnection())
{
db.CreateTableIfNotExist<T>();
foreach (var item in items)
{
db.Insert(item);
}
}
}
}
A 'DbConnectionManager<EFContext>' class to provide the wrapper to the database connection using the EF Context you will already have created. This grabs the database connection from the EF Context and abstracts away the opening/closing operations:
public class DbConnectionManager<TContext> : IDbConnectionManager<TContext>, IDisposable
where TContext : DbContext
{
private TContext _context;
public DbConnectionManager(TContext context)
{
_context = context;
}
public async Task<IDbConnection> GetDbConnectionFromContextAsync()
{
var dbConnection = _context.Database.GetDbConnection();
if (dbConnection.State.Equals(ConnectionState.Closed))
{
await dbConnection.OpenAsync();
}
return dbConnection;
}
public void Dispose()
{
var dbConnection = _context.Database.GetDbConnection();
if (dbConnection.State.Equals(ConnectionState.Open))
{
dbConnection.Close();
}
}
}
Accompanying injectable Interface for the above:
public interface IDbConnectionManager<TContext>
where TContext : DbContext
{
Task<IDbConnection> GetDbConnectionFromContextAsync();
void Dispose();
}
In your .NET Project Startup class, register this interface with the inbuilt DI container (or whatever one you're using):
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped(typeof(IDbConnectionManager<>), typeof(DbConnectionManager<>));
}
Now our Unit Test class looks like this:
/// <summary>
/// All tests to follow the naming convention: MethodName_StateUnderTest_ExpectedBehaviour
/// </summary>
[ExcludeFromCodeCoverage]
public class ProductTests
{
//private static Mock<ILoggerAdapter<Db2DbViewAccess>> _logger;
//private static Mock<IOptions<AppSettings>> _configuration;
private readonly Mock<IDbConnectionManager<Db2Context>> _dbConnection;
private readonly List<Product> _listProducts = new List<Product>
{
new Product
{
Id = 1,
Name = "Product1"
},
new Product
{
Id = 2,
Name = "Product2"
},
new Product
{
Id = 3,
Name = "Product3"
},
};
public ProductTests()
{
//_logger = new Mock<ILoggerAdapter<Db2DbViewAccess>>();
//_configuration = new Mock<IOptions<AppSettings>>();
_dbConnection = new Mock<IDbConnectionManager<Db2Context>>();
}
[Fact]
public async Task GetProductAsync_ResultsFound_ReturnListOfAllProducts()
{
// Arrange
// Using a SQL Lite in-memory database to test the DbContext.
var testInMemoryDatabase = new TestInMemoryDatabase();
testInMemoryDatabase.Insert(_listProducts);
_dbConnection.Setup(c => c.GetDbConnectionFromContextAsync())
.ReturnsAsync(testInMemoryDatabase.OpenConnection());
//_configuration.Setup(x => x.Value).Returns(appSettings);
var productAccess = new ProductAccess(_configuration.Object); //, _logger.Object, _dbConnection.Object);
// Act
var result = await productAccess.GetProductAsync("SELECT * FROM Product");
// Assert
result.Count.Should().Equals(_listProducts.Count);
}
}
Notes on the above:
You can see i'm testing a 'ProductAccess' Data Access class which wraps my database calls but that should be easy enough to change for your setup. My ProductAccess class is expecting other services such as logging and Configuration to be injected in, but i have commented these out for this minimal example.
Note the setup of the in-memory database and populating it with your test list of entities to query is now a simple 2 lines (you could even do this just once in the Test class constructor if you want the same test dataset to use across tests):
var testInMemoryDatabase = new TestInMemoryDatabase();
testInMemoryDatabase.Insert(_listProducts);

Using multiple connection strings for the same DBContext

I am working on a asp.net core project which uses MongoDB and I am using dependency injection as well.
How current system works
There is only one database and the connection string is saved in the appsettings.json. Some devices will be constantly sending data through the API to save it in the MongoDB database which works as it should :)
Now I have to enhance it, there will be multiple databases and data should be saved in the relevant database based on the API request (i will be getting the database name in the API request).
My question is how that can I change the database based on the API request? while using the same DbContext. It is not an option to create multiple DbContext.
I am somewhat new to MongoDb and asp.net core so any help or guidance is much appreciated.
This is my DbContext class
public class DbContext
{
private IMongoDatabase _database;
protected readonly MongoClient mongoClient;
public DbContext(MongoSetting dbConnSettings)
{
var mongoUrl = new MongoUrl(dbConnSettings.ConnectionString);
var mongoClientSettings = MongoClientSettings.FromUrl(mongoUrl);
mongoClient = new MongoClient(mongoClientSettings);
if (mongoClient != null)
_database = mongoClient.GetDatabase(dbConnSettings.database);
}...
Section of my StartUp class
services.AddTransient<CareHomeContext>();
services.AddSingleton(provider => provider.GetService<IOptions<MongoSetting>>().Value);
If I understand your usecase correctly, it might be needed to use different databases in each request as well.
Hence I suggest to make database name as an optional parameter (so that you can use a default value from the configuration in case the database name is not provided in the request) to the DbContext class methods and create a method to get the database object (instead of getting it in the constructor) in the DbContext class as below.
private IMongoDatabase GetDatabase(string databaseName) => mongoClient.GetDatabase(databaseName ?? defaultDatabaseName);
Invoke the above method in each DbContext class method. e.g.
public async Task InsertAsync(string collectionName, Dictionary<string, object> fields, string databaseName = null) {
var database = GetDatabase(databaseName);
// Insert Code here
}
I hope this would help.

Is there any opportunities to override SQLite Connection Open method in C#?

I need to execute a SQliteCommand every time when Connection opened. And I'm going to to inherit SQliteConnection class and register custom class using Autofac, but this way doesn't work work for me.
Instead of an is a relationship use an has a relationship - Don't inherit SQliteConnection, instead, encapsulate it inside your class.
Something like this should get you started:
public class DBHelper
{
private readonly string _connectionString,
_sqlToExecuteOnConnectionOpen;
public DBHelper(string connectionString)
{
_connectionString = connectionString;
_sqlToExecuteOnConnectionOpen = "Your sql goes here";
}
public void ExecuteSql(Action<SQliteConnection> action)
{
using(var con = new SQliteConnection(_connectionString))
{
using(var cmd = new SQliteCommand(_sqlToExecuteOnConnectionOpen, con)
{
con.Open();
// of course, any parameters goes here...
cmd.ExecuteNonQuery();
}
action(con);
}
}
}
Now you have a method that opens and disposes an instance of SqliteConnection, execute your pre-detenrmined sql, and whatever other action you want to execute with that connection.
You can even take it one step further and have this method private, but expose public methods for ExecuteNonQuery, ExecuteScalar, ExecuteReader and even filling a dataset or data table - and have them all execute this method.
This can save you a lot of the repetitive plumbing code usually written when using ADO.Net.
In fact, I've published a project on GitHub that does exactly that (except your constant sql statement, of course) - You can clone it, view it's code, and just generally take ideas from it and implement them in your own code.
You can use AOP principle to intercept the Open method.
Let's start by implementing your custom interceptor.
public class PrepareConnectionInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
if (!(invocation.InvocationTarget is IDbConnection
&& invocation.Method.Name == nameof(IDbConnection.Open)))
{
invocation.Proceed();
return;
}
invocation.Proceed();
IDbConnection connection = (IDbConnection)invocation.InvocationTarget;
using(IDbCommand command = connection.CreateCommand())
{
command.CommandText = "SQL statement";
command.ExecuteNonQuery();
}
}
}
Then register your connection and interceptor using autofac
builder.RegisterType<PrepareConnectionInterceptor>()
.AsSelf();
builder.RegisterType<SQLiteConnection>()
.As<IDbConnection>()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(PrepareConnectionInterceptor));
Now each time you call the Open method on a resolved IDbConnection the interceptor will be triggered and custom SQL will be executed.
You can find more information on the Autofac Interceptor documentation

What is DbContext doing in ADO.NET

I have code that make connection to the database and perform CRUD operations
Please see the code below:
We have in the code used "DbContext".
Is it generic and can be used with all kinds of databases or is it made for SQLServer and what is its purpose/mission?
I thought DBContext was only used with the Entity Framework
public class UserRepository : Repository<User>
{
private DbContext _context;
public UserRepository(DbContext context)
: base(context)
{
_context = context;
}
public IList<User> GetUsers()
{
using (var command = _context.CreateCommand())
{
command.CommandText = "exec [dbo].[uspGetUsers]";
return this.ToList(command).ToList();
}
}
public User CreateUser(User user)
{
using (var command = _context.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "uspSignUp";
command.Parameters.Add(command.CreateParameter("#pFirstName", user.FirstName));
command.Parameters.Add(command.CreateParameter("#pLastName", user.LastName));
command.Parameters.Add(command.CreateParameter("#pUserName", user.UserName));
command.Parameters.Add(command.CreateParameter("#pPassword", user.Password));
command.Parameters.Add(command.CreateParameter("#pEmail", user.Email));
return this.ToList(command).FirstOrDefault();
}
}
public User LoginUser(string id, string password)
{
using (var command = _context.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "uspSignIn";
command.Parameters.Add(command.CreateParameter("#pId", id));
command.Parameters.Add(command.CreateParameter("#pPassword", password));
return this.ToList(command).FirstOrDefault();
}
}
public User GetUserByUsernameOrEmail(string username, string email)
{
using (var command = _context.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "uspGetUserByUsernameOrEmail";
command.Parameters.Add(command.CreateParameter("#pUsername", username));
command.Parameters.Add(command.CreateParameter("#pEmail", email));
return this.ToList(command).FirstOrDefault();
}
}
}
Here is DbContext class:
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace DataAccessLayer
{
public class DbContext
{
private readonly IDbConnection _connection;
private readonly IConnectionFactory _connectionFactory;
private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
private readonly LinkedList<AdoNetUnitOfWork> _uows = new LinkedList<AdoNetUnitOfWork>();
public DbContext(IConnectionFactory connectionFactory)
{
_connectionFactory = connectionFactory;
_connection = _connectionFactory.Create();
}
public IUnitOfWork CreateUnitOfWork()
{
var transaction = _connection.BeginTransaction();
var uow = new AdoNetUnitOfWork(transaction, RemoveTransaction, RemoveTransaction);
_rwLock.EnterWriteLock();
_uows.AddLast(uow);
_rwLock.ExitWriteLock();
return uow;
}
public IDbCommand CreateCommand()
{
var cmd = _connection.CreateCommand();
_rwLock.EnterReadLock();
if (_uows.Count > 0)
cmd.Transaction = _uows.First.Value.Transaction;
_rwLock.ExitReadLock();
return cmd;
}
private void RemoveTransaction(AdoNetUnitOfWork obj)
{
_rwLock.EnterWriteLock();
_uows.Remove(obj);
_rwLock.ExitWriteLock();
}
public void Dispose()
{
_connection.Dispose();
}
}
}
You can use EntityFramework to execute raw SQL, but looks like DbContext in your example is not the one from EntityFramework. It can be some other library or custom implementation in your project. You should be able to tell that by examining using imports, or by navigating to the DbContext definition.
I thought DBContext was only used with the Entity Framework
You're right, DBContext is a EntityFramework class that combines unit of work and repository pattern.
However, you seem to have a custom DBContext created using ado.net. Though, I wouldn't really call it a DBContext. Since normally DBContext follows ObjectContext concept of referring to a domain object i.e. within a context. This particular custom class is more like a DbCommand factory.
Is it generic and can be used with all kinds of databases or is it made for SQLServer[...]?
It's not SQL server specific. So it could be used with other databases like Oracle Database, Microsoft SQL Server, MySQL, PostgreSQL, SQLite, and some others. But, that would depend on correct configuration and correct query usage for each database. For example, parameters are not always prefixed by # for all databases. But, this particular DBContext passes that problem on to the class that calls it and avoids dealing with it as we can see in UserRepository use of the class.
... and what is its purpose/mission?
The purpose of the DBContext you've shown us, is to get a connection using a factory, instantiate and return a DBCommand with an optional transaction. And, then the Repository base class function ToList seems to be in charge of executing the Command and mapping the objects User.
It looks like this was an attempt to create a repository pattern using ado.net. And, not knowing the history of your application, I can only assume there was a decision made to avoid using Entity Framework. There are many different ways I've seen this done. And, plenty of people will argue about the correct implementation. But, I think that would be out of the scope of this particular question.

System.data.SQLite entity framework code first programmatic providername specification

I've spent a while on this now and have only found a workaround solution that I'd rather not do...
I have a context as shown below. Is there a programmatic way to specify the database to connect to via the constructor, and get it to use the System.Data.SQLite entity framework provider to connect to a SQLite database? This is working via the app.config (with a connectionstring called "Context"), but not via any programmatic way I can find of supplying it. I have tried using an entity connectionstring builder and that produces the following string:
provider=System.Data.SQLite;provider connection string='data
source="datafile.db"'
When the context is first queried with this string I get a message "Keyword not supported: 'provider'."
public class Context : DbContext
{
public IDbSet<Test> Tests { get; set; }
public Context()
: base("Context")
{
}
}
*Edit.
I may have solved this by implementing my own connectionfactory:
public class ConnectionFactory : IDbConnectionFactory
{
public DbConnection CreateConnection(string nameOrConnectionString)
{
return new SQLiteConnection(nameOrConnectionString);
}
}
Then setting:
Database.DefaultConnectionFactory = new ConnectionFactory();
However, it seems like there should be a built in way to do this, and also one that does not involve overriding the global default connection factory.

Categories