I'm trying to build a data layer in my application based on a .NET Core class library using Dapper. The data classes look like this:
//FieldRepository.cs
using Dapper.Contrib.Extensions;
public class FieldRepository : IRepository<TblField>
{
private IDbConnection connection;
public FieldRepository(string connectionString)
{
connection = new SqlConnection(connectionString);
}
public IEnumerable<TblField> GetAll()
{
return connection.GetAll<TblField>();
}
}
//IRepository.cs
public interface IRepository<T>
{
IEnumerable<T> GetAll();
}
//TblField
public class TblField
{
public string FieldText { get; set; }
public int Id { get; set; }
}
Then I tried to run a test against these classes as below:
public void ThereShouldBeFields()
{
var repo = new FieldRepository("valid connection string");
var fields = repo.GetAll();
fields.Should().NotBeNull();
}
When I ran this test, I got a FileNotFound exception at the FieldRepository constructor for System.Data.SqlClient, version 4.2.0.0, which is installed in the data layer project.
I know I'm missing something simple here, but what is it?
Looks like it was because the reference needed to be added to both the Test project and the Data layer project.
The more you know!
Related
I'm working on a new project that uses CosmosDB and Entity Framework Core (via the Microsoft.EntityFrameworkCore.Cosmos NuGet package, version 5.0.7; the project itself is .NET Core 5). I'm new to both, and running into an issue I can't sort out.
In short, I need to save a complex object to the database. It's a big model that will have multiple collections of classes underneath it, each with their own properties and some with collections underneath them as well. I'm trying to configure EF with OwnsOne and OwnsMany to store these child objects underneath the top-level one. The code compiles, and will save to the database so long as all the owned objects are left empty. But whenever I put anything into an owned object, either with OwnsOne or OwnsMany, I get a pair of NullReferenceExceptions.
I've tried to strip my code down to the very basics. Here's how it currently looks.
Owner and owned classes:
public class Questionnaire
{
// Constructors
private Questionnaire() { }
public Questionnaire(Guid id)
{
Test = "Test property.";
TV = new TestQ();
Id = id;
}
public Guid Id { get; set; }
public string Test { get; set; }
public TestQ TV { get; set; }
// Public Methods
public void AddForm(Form f)
{
// not currently using this method
//Forms.Add(f);
}
}
public class TestQ
{
public TestQ()
{
TestValue = "test ownsone value";
}
public string TestValue { get; set; }
}
DbContext:
public class QuestionnaireDbContext : DbContext
{
public DbSet<Questionnaire> Questionnaires { get; set; }
public QuestionnaireDbContext(DbContextOptions<QuestionnaireDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultContainer(nameof(Questionnaires));
modelBuilder.Entity<Questionnaire>().HasKey(q => q.Id);
modelBuilder.Entity<Questionnaire>().OwnsOne(q => q.TV);
}
}
And the code from the service that calls the dbContext (note that this is based on a generic service that I didn't set up originally). The actual exceptions are thrown here.
public virtual TEntity Add(TEntity entity)
{
_context.Entry(entity).State = EntityState.Added;
_context.SaveChanges();
return entity;
}
Ultimately I need this to work with OwnsMany and a collection, but I figured it might be simpler to get it working with OwnsOne first. The key thing to note here is that if I comment out the line
TV = new TestQ();
in the Questionnaire class, the model persists correctly into CosmosDB. It's only when I actually instantiate an owned entity that I get the NullReferenceExceptions.
Any advice would be much appreciated! Thank you!
Well, I'm not sure why this is the case, but the issue turned out to be with how we were adding the document. Using this generic code:
public virtual async Task<TEntity> Add(TEntity entity)
{
_context.Entry(entity).State = EntityState.Added;
await _context.SaveChanges();
return entity;
}
was the issue. It works just fine if I use the actual QuestionnaireDbContext class like so:
context.Add(questionnaire);
await context.SaveChangesAsync();
Very new to DI and concepts so I'm struggling to find a solution to the following:
We have a Web Project (MVC, Core 3) and a Class Library (for all the business and data layers). We are trying to have a DBContext Class in the Class Library to handle all the DB connections (using connection string). We can DI into this DBContext the IConfiguration so we can extract the connection string from this and place into a local readonly string. We have a method to return the SQL Connection using this connection string.
The issue is when we are in a business class, we need to access the DBContext class to obtain a SQL Connection object. We cannot create a "new" DBContext as we do not have the IConfigration in the other business classes.
DBContext class library:
public class DBContext
{
private readonly string _connectionString;
private readonly IConfiguration _configuration;
public DBContext(IConfiguration configuration)
{
_configuration = configuration;
_connectionString = configuration.GetConnectionString("db");
}
public SqlConnection Connection()
{
return new System.Data.SqlClient.SqlConnection(_connectionString);
}
}
So the problem lies when we look at the Customer class in the Class Library:
Customer Class:
public class Customer
{
public int CustomerId { get; set; }
public string CustomerName { get; set; }
public Customer(int customerId)
{
//load customer from the db from the id
using (IDbConnection db = new DBContext().Connection())
{
//call SQL Stored Procedure here ....
}
}
}
We are unable to create a "new" DBContext to access the connection, as in the Customer Class we don't have the IConfiguration object to pass to the constructor.
How do we achieve this the "correct" way?
Is it as bad as having to DI the IConfiguration object into every single class library constructor e.g. Customer from all the web controllers, so we can pass it over to the DBContext? As this seems long winded.
Sorry if this is very basic DI stuff but just struggling to find a good example on how to do this stuff.
Thanks in advance,
Ro
public class Customer
{
private readonly ApplicationDbContext _context;
public int CustomerId { get; set; }
public string CustomerName { get; set; }
public Customer(ApplicationDbContext context, int customerId)
{
_context = context; //use _context to reference the db context.
//do stuff
}
}
And in Startup.cs -> ConfigureServices() method:
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DevDb")));
Edit for Dapper:
In appsettings.json:
"ConnectionStrings": {
"DefaultConnection": "<connection string here>"
}
Then add a class called ConnectionString in the solution. We will use this class to hold the connection string value from the configuration file and use it in our classes via dependency injection.
public sealed class ConnectionString
{
public ConnectionString(string value) => Value = value;
public string Value { get; }
}
Then Register the configuration in the ASP.NET Core dependency injection container in Startup.cs
var connectionString = new ConnectionString(Configuration.GetConnectionString("DefaultConnection"));
services.AddSingleton(connectionString);
Then add your repository class:
using Dapper;
public class MovieRepository
{
private readonly ConnectionString _connectionString;
public MovieRepository(ConnectionString connectionString)
{
_connectionString = connectionString;
}
public async Task<IEnumerable<MovieModel>> GetAllMovies()
{
const string query = #"SELECT m.Id, m.Name,d.Name AS DirectorName, m.ReleaseYear
FROM Movies m
INNER JOIN Directors d
ON m.DirectorId = d.Id";
using (var conn = new SqlConnection(_connectionString.Value))
{
var result = await conn.QueryAsync<MovieModel>(query);
return result;
}
}
}
Then Register the Repository class in you DI container. Add the following code to ConfigureServices method.
services.AddScoped<MovieRepository>();
Add the controller class MoviesController.cs and write an action method to fetch all movies using this MovieRepository.
[ApiController]
public class MoviesController : ControllerBase
{
private readonly MovieRepository _movieRepository;
public MoviesController(MovieRepository movieRepository)
{
_movieRepository = movieRepository;
}
[HttpGet("api/movies")]
public async Task<IActionResult> GetMovies()
{
return Ok(await _movieRepository.GetAllMovies());
}
}
In this example, your ApplicationDbContext is equivalent to the movie repository. Answer from: https://www.c-sharpcorner.com/article/using-dapper-for-data-access-in-asp-net-core-applications/
I'm new to ASP.NET Core and I'm trying to solve this problem for a week now.
I have a solution with two projects.
And when I start the porject the browser just says:
InvalidOperationException: Unable to resolve service for type 'TSM_Programm.Data.TSMContext' while attempting to activate 'TSM_Programm.Controllers.ResourcesController'.
The first part of the solution is my API-Layer that passes data to a user (currently via postman).
The second project is my Data Access Layer.
This Layer contains several Controllers, all of them using the same constructor, which is the following:
public TSMContext _context;
public ResourcesController(TSMContext context)
{
_context = context;
}
The TSMContext Class is the following:
namespace TSM_Programm.Data
{
public class TSMContext : DbContext
{
public TSMContext(DbContextOptions<TSMContext> options)
: base(options)
{
}
public DbSet<Resource> Resources { get; set; }
public DbSet<Parameter> Parameters { get; set; }
public DbSet<ResourceToParameter> ResourceToParameters { get; set; }
public DbSet<Reservation> Reservations { get; set; }
}
So far so god, but when I am trying to start the program the controllerof the API-Layer does not seem to be able to handle the constructor.
This is my API-Conrtoller:
namespace TSM_API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class APIController : ControllerBase //Base Class without View Support
{
//Troublemaker
public ResourcesController _resources;
public ParametersController _parameters;
public ReservationsController _reservations;
public APIController(ResourcesController resources, ParametersController parameters, ReservationsController reservations)
{
_resources = resources;
_parameters = parameters;
_reservations = reservations;
}
//Function to check if controller works
//GET: api/API
[HttpGet]
public IEnumerable<string> Get()
{
// ResourcesController controller = new ResourcesController();
return new string[] { "value1", "value2" };
}
The API-Controller was not able to use its own constructors, that's why I changed the Startup.cs.
namespace TSM_API
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddMvc().AddApplicationPart(typeof(ResourcesController).Assembly).AddControllersAsServices();
services.AddMvc().AddApplicationPart(typeof(ParametersController).Assembly).AddControllersAsServices();
services.AddMvc().AddApplicationPart(typeof(ReservationsController).Assembly).AddControllersAsServices();
services.AddMvc().AddApplicationPart(typeof(TSMContext).Assembly).AddControllersAsServices();
}
I'm simply out of ideas on how to solve the problem, since I can't add the TSMContext class a service.
Any idea how to solve it?
Thank you.
I see you have not registered your dbcontext as a dependency injection. Your issue might be due to ResourceController trying to access _context as a DI but it is not registered. To use the context as a dependency injection, register it in the startup.cs as following.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TSMContext>(options => options.UseSqlServer(Configuration.GetConnectionString("YOUR_CONNECTION_STRING")));
//If you have any services that should be used as DI, then they also must be registered as like this
services.AddScoped<Interface, Class>(); //Interface refer to the service interface while class is the actual service you will use.
}
I have tried to make an MVC news system. I started by the use a pluralsight tutorial which was used to create a department with employees. I changed the idea to fit my own purposes, changing the departments to "category" and employees to "newspost". This all works out fine, but now I want to remove the categories, since I don't need categories for my news system. But I can't add to the database using entityframework when I do this.
I have an interface for the datasource that looks like this:
INewMvcSiteDataSource
public interface INewMvcSiteDataSource
{
IQueryable<NewsPost> NewsPosts { get; }
void Save();
}
This is inherited by my DB Context class:
NewMvcSiteDb
public class NewsMvcSiteDb : DbContext, INewMvcSiteDataSource
{
public NewsMvcSiteDb() : base("DefaultConnection")
{
}
public DbSet<NewsPost> NewsPosts { get; set; }
IQueryable<NewsPost> INewMvcSiteDataSource.NewsPosts
{
get { return NewsPosts; }
}
public void Save()
{
SaveChanges();
}
}
I then want to use it in the controller to add a newspost to the database:
NewsController
var newsPost = new NewsPost()
{
Subject = newsModel.Subject,
Content = newsModel.Content,
ImagePath = newsModel.ImagePath
};
_db.NewsPosts.Add(newsPost);
_db.Save();
But this is where the ADD fails with the message: 'System.Linq.IQueryable' does not contain a definition for 'Add' and no extension method 'Add' accepting a first argument of type 'System.Linq.IQueryable' could be found (are you missing a using directive or an assembly reference?)
Now as the error says, its caused by using IQueryable, but I have no idea how else to do it.
Can you guys help?
Thanks.
If you don't mind exposing DbSet via your interface (some people don't like the ORM bleeding into the application),you should be able to do the following:
public interface INewMvcSiteDataSource
{
DbSet<NewsPost> NewsPosts { get; }
void Save();
}
public class NewsMvcSiteDb : DbContext, INewMvcSiteDataSource
{
public NewsMvcSiteDb() : base("DefaultConnection")
{
}
public DbSet<NewsPost> NewsPosts { get; set; }
public void Save()
{
SaveChanges();
}
}
We've been experimenting with StructureMap, and I'm having trouble grasping how to handle situations where a single interface has multiple implementations. The code below shows an example where we have two databases that are both accessible from a single service.
public class SomeController : Controller
{
private ISomeService _service;
private IClientRepository _repository;
protected IContext _masterContext;
protected IContext _clientContext;
public SomeController(ISomeService service, ISomeRepository repository
, IContext masterCon, IContext clientCon)
{
_service = service;
_repository = repository;
_masterContext = masterCon;
_clientContext = clientCon;
}
}
public class SomeService : ISomeService
{
private IContext _masterContext;
private IContext _clientContext;
public SomeService(IContext masterContext, IContext clientContext)
{
masterContext = _masterContext;
clientContext = _clientContext;
}
}
public class ClientRepository : IClientRepository
{
private IContext _clientContext;
public ClientRepository(IContext clientContext)
{
_clientContext = clientContext;
}
}
public class MasterContext : IContext
{
public MasterContext(String connString)
//<snip, snip> implement 3rd party data context
}
public class ClientContext : IContext
{
public ClientContext(String connString)
//<snip, snip> implement 3rd party data context
}
StructureMap worked GREAT when we had a single context (database), but how do I tell it how to resolve the 2nd? Note: in most situations we wouldn't have a service handling 2 databases (but may have a controller handling 2 connections, i.e. 2 repositories accessing 2 different databases), but it still doesn't seem to make it easier.
I'm half ready to just give up on using an IoC framework and go back to poor man's DI.
Is it not possible to have an IClientContext and an IMasterContext, possibly inheriting from IContext. My feeling is that the code would be doing one of two very different things depending on whether you were talking to the 'Master' or 'Client' database.
In Unity you can have named registrations, allowing you to effectively register more than a class for a given interface. So you could do (typing by heart, check the actual Unity documentation if interested):
container.RegisterType<IContext, MasterContext>("Master");
container.RegisterType<IContext, ClientContext>("Client");
and then the constructor for SomeService would be:
public SomeService(
[Dependency("Master")]IContext masterContext,
[Dependency("Client")]IContext clientContext)
{
//...
}
The drawback is that in this way your service class is no longer independent of the DI framework used, but depending on the project that may be ok.
This can be a little difficult if you're relying on StructureMap to resolve the dependencies automatically. The first solution (and what I'd err towards) is to make use of marker interfaces like Richard mentions in his answer then just register them. You can then explicitly specify whether you want your client or master context there.
The second way is to make use of named registrations, then specify the constructor params explicitly.
ForRequestedType<IContext>().AddInstances(
i => {
i.OfConcreteType<ClientContext>().WithName("Client");
i.OfConcreteType<MasterContext>().WithName("Master");
});
ForRequestedType<SomeController>().TheDefault.Is.ConstructedBy(
i => new SomeController(i.GetInstance<ISomeService>(),
i.GetInstance<IClientRepository>(),
i.GetInstance<IContext>("Master"),
i.GetInstance<IContext>("Client")));
Not particularly nice but it does the job and ultimately if it's only in one or two places it might be OK.
If you want to resolve differently on namespace / assembly you could try something like this:-
ForRequestedType<IContext>().AddInstances(
i => {
i.OfConcreteType<ClientContext>().WithName("Client");
i.OfConcreteType<MasterContext>().WithName("Master");
}).TheDefault.Is.Conditional(c => {
c.If(con => con.ParentType.Namespace.EndsWith("Client"))
.ThenIt.Is.TheInstanceNamed("Client");
c.If(con => con.ParentType.Namespace.EndsWith("Master"))
.ThenIt.Is.TheInstanceNamed("Master");
c.TheDefault.Is.OfConcreteType<ClientContext>();
});
Where the predicate on ParentType can refer to Assembly (or whatever you want really)
In case someone stumble in this problem, you can achieve it using factory pattern.
Service extension
public static class ServiceFactoryExtensions
{
public static void RegisterSqlFactory(this IServiceCollection serviceCollection)
{
serviceCollection.Configure<MsSqlOption>(option => option.ConnectionString = "Mssql connection string");
serviceCollection.Configure<MySqlOption>(option => option.ConnectionString = "Mysql connection string");
serviceCollection.Configure<PostgreOption>(option => option.ConnectionString = "Postgrel connection string");
serviceCollection.AddSingleton<ISqlDatabase, MsSql>();
serviceCollection.AddSingleton<ISqlDatabase, Postgre>();
serviceCollection.AddSingleton<ISqlDatabase, MySql>();
serviceCollection.AddSingleton<Func<IEnumerable<ISqlDatabase>>>(serviceProvider => () => serviceProvider.GetService<IEnumerable<ISqlDatabase>>());
serviceCollection.AddSingleton<ISqlDatabaseFactory, SqlDatabaseFactory>();
}
}
Factory class
public class SqlDatabaseFactory : ISqlDatabaseFactory
{
private readonly Func<IEnumerable<ISqlDatabase>> _factory;
public SqlDatabaseFactory(Func<IEnumerable<ISqlDatabase>> factory)
{
_factory = factory;
}
public ISqlDatabase CreateSql(SqlType sqlType)
{
var databases = _factory();
var sqlDatabase = databases.FirstOrDefault(x => x.DatabaseName == sqlType);
if (sqlDatabase == null)
throw new NotImplementedException($"Sql type {nameof(sqlType)} is not implemented");
return sqlDatabase;
}
}
Sql classes
public class MsSql : ISqlDatabase
{
public SqlType DatabaseName => SqlType.MsSql;
public string Connecionstring { get; private set; }
public MsSql(IOptions<MsSqlOption> option)
{
Connecionstring = option.Value.ConnectionString;
}
}
public class Postgre : ISqlDatabase
{
public SqlType DatabaseName => SqlType.Postgre;
public string Connecionstring { get; private set; }
public Postgre(IOptions<PostgreOption> option)
{
Connecionstring = option.Value.ConnectionString;
}
}
public class MySql : ISqlDatabase
{
public SqlType DatabaseName => SqlType.MySql;
public string Connecionstring { get; private set; }
public MySql(IOptions<MySqlOption> option)
{
Connecionstring = option.Value.ConnectionString;
}
}
public interface ISqlDatabase
{
string Connecionstring { get; }
SqlType DatabaseName { get; }
}
public enum SqlType
{
MsSql,
Postgre,
MySql
}
Usage
internal class Program
{
static void Main(string[] args)
{
var serviceCollection = new ServiceCollection();
serviceCollection.RegisterSqlFactory();
var provider = serviceCollection.BuildServiceProvider();
var sqlFactory = provider.GetService<ISqlDatabaseFactory>();
var mySql = sqlFactory.CreateSql(SqlType.MySql);
var msSql = sqlFactory.CreateSql(SqlType.MsSql);
var postgre = sqlFactory.CreateSql(SqlType.Postgre);
Console.WriteLine($"Database Type : {mySql.DatabaseName}, Connectionstring: {mySql.Connecionstring}");
Console.WriteLine($"Database Type : {msSql.DatabaseName}, Connectionstring: {msSql.Connecionstring}");
Console.WriteLine($"Database Type : {postgre.DatabaseName}, Connectionstring: {postgre.Connecionstring}");
Console.ReadKey();
}
}
Output
Dependencies:
.Net Core 3.1
Microsoft.Extensions.DependencyInjection;
Microsoft.Extensions.Options;
System
System.Collections.Generic
System.Linq;