I try to write an MVC n tier application. I have used repository pattern during connecting to the database. In my repository class, I have a Context variable inside repository class. I am not sure that this approach is true. Here are my codes:
public class TTPDbContext : DbContext
{
public TTPDbContext() : base("TTPContext")
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<TTPDbContext>());
Database.Log = s => Debug.WriteLine(s);
Configuration.AutoDetectChangesEnabled = true;
}
public DbSet<Kisi> Kisiler { get; set; }
public DbSet<BakanlikBirim> BakanlikBirimleri { get; set; }
public DbSet<DisBirim> DisBirimler { get; set; }
public DbSet<Kullanici> Kullanicilar { get; set; }
public DbSet<Talep> Talepler { get; set; }
public DbSet<UnvanPozisyon> UnvanPozisyonlar { get; set; }
public DbSet<TalepDurum> TalepDurumlar { get; set; }
public DbSet<TalepKagidi> TalepKagidi { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("OsiTtp");
}
}
Here is the repository class:
public class TTPRepository : ITTPRepository,IDisposable
{
private IErrorHandling _errorHandling;
private TTPDbContext _context;
public TTPRepository()
{
this._errorHandling = new ErrorHandling();
this._context = new TTPDbContext();
}
// other sections has been dismissed for brevity.
public List<DisBirim> GetAllExternalInstitutions()
{
List<DisBirim> result = null;
DbSet<DisBirim> intermediaryresult = null;
try
{
result = new List<DisBirim>();
intermediaryresult = this._context.DisBirimler;
if (intermediaryresult != null)
{
foreach (DisBirim institution in intermediaryresult)
{
result.Add(institution);
}
}
}
catch (Exception Hata)
{
this.yazHata(Hata);
}
return result;
}
public void Dispose()
{
this._context.Dispose();
}
}
I am not sure that is a optimal approach. Do you have any recommandations? Thanks in advance.
I would recommend to read the msdn documentation
Instead of this
public TTPRepository()
{
this._errorHandling = new ErrorHandling();
this._context = new TTPDbContext();
}
try this
public TTPRepository(TTPDbContext context,ErrorHandling errorHandler)
{
this._errorHandling = errorHandler;
this._context = context;
}
By this your repository is ready to work with a context and Error Handler. I always recommend to use something like IErrorHandler and IDbContext rather than concrete classes.
So you have freedom to initialize like this. Even if you use IoC containers you can control the lifetime of your Context.
var yourRepo = new TTPRepository(new TTPDbContext());
When use repository pattern and interface best practice is that use IoC Container for inject DbContext to repository constructor.
If you are using an IoC Container, you can control the lifetime of the DbContext to ensure that all instances of Repository get the same Context.
you must one of IoC containers likes Unity, Ninject, Autofac, ...
Documentation of unity usage Dependency Injection in ASP.NET MVC - An Introduction
Related
I have a CatalogDbContext class.
I want to use Bogus library to seed fake data into the database that my unit tests will use.
The example provided in bogus's github repo makes use of the HasData method of the CatalogDbContext class to seed data into the tables.
However, I will not want this HasData method to be executed from the API - meaning, the HasData method should only be run if the DBContext is created from the Unit Tests.
Kindly advise how to achieve this?.
using Bogus;
using Catalog.Api.Database.Entities;
using Microsoft.EntityFrameworkCore;
namespace Catalog.Api.Database
{
public class CatalogDbContext : DbContext
{
public CatalogDbContext(DbContextOptions<CatalogDbContext> options) : base(options)
{
}
public DbSet<CatalogItem> CatalogItems { get; set; }
public DbSet<CatalogBrand> CatalogBrands { get; set; }
public DbSet<CatalogType> CatalogTypes { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.ApplyConfiguration(new CatalogBrandEntityTypeConfiguration());
builder.ApplyConfiguration(new CatalogTypeEntityTypeConfiguration());
builder.ApplyConfiguration(new CatalogItemEntityTypeConfiguration());
FakeData.Init(10);
builder.Entity<CatalogItem>().HasData(FakeData.CatalogItems);
}
}
internal class FakeData
{
public static List<CatalogItem> CatalogItems = new List<CatalogItem>();
public static void Init(int count)
{
var id = 1;
var catalogItemFaker = new Faker<CatalogItem>()
.RuleFor(ci => ci.Id, _ => id++)
.RuleFor(ci => ci.Name, f => f.Commerce.ProductName());
}
}
}
We have an API with about a dozen integration tests. All the tests passed until I added some DTOs and used AutoMapper. Now, all the tests that test methods that use AutoMapper and the DTOs are failing. I have provided all the code needed to understand one of the failing tests. Also, I read a lot about AutoMapper and the following StackOverflow posts:
Integration Testing with AutoMapper fails to initialise configuration
A kind of integration testing in ASP.NET Core, with EF and AutoMapper
Startup.cs
This is our Startup.ConfigureServices(). I have tried every code block commented out and/or marked "ATTEMPTED".
public void ConfigureServices(IServiceCollection services)
{
services
.AddDbContext<OurContext>(options =>
options.UseSqlServer(Configuration["ConnectionString"]))
.AddDbContext<OurContext>()
.AddRazorPages()
.AddMvcOptions(options => options.EnableEndpointRouting = false)
.AddNewtonsoftJson(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());
services
.AddControllersWithViews();
//ATTEMPTED
//services
// .AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
//ATTEMPTED
//MapperConfiguration mapperConfiguration = new MapperConfiguration(mc =>
//{
// mc.AddProfile(new OurProfile());
//});
//IMapper mapper = mapperConfiguration.CreateMapper();
//services
// .AddSingleton(mapper);
//ATTEMPTED
//services
// .AddAutoMapper(typeof(Startup));
//ATTEMPTED
//var assembly = typeof(Program).GetTypeInfo().Assembly;
//services
// .AddAutoMapper(assembly);
//ATTEMPTED
var assembly = typeof(Program).GetTypeInfo().Assembly;
services.AddAutoMapper(cfg =>
{
cfg.AllowNullDestinationValues = true;
cfg.CreateMap<OurModel, OurDto>()
.IgnoreAllPropertiesWithAnInaccessibleSetter();
}, assembly);
}
Controller
This is our controller.
[Route("api/[controller]")]
[ApiController]
public class OurController : ControllerBase
{
private readonly OurContext _context;
protected readonly ILogger<OurController> Logger;
private readonly IMapper _mapper;
public OurController(OurContext context, ILogger<OurController> logger,
IMapper mapper)
{
_context = context ??
throw new ArgumentNullException(nameof(context));
Logger = logger ??
throw new ArgumentNullException(nameof(logger));
_mapper = mapper ??
throw new ArgumentNullException(nameof(mapper));
}
[HttpGet]
public async Task<ActionResult<IEnumerable<OurDto>>> GetAll()
{
IQueryable<OurModel> models = _context.OurModel;
IQueryable<OurDto> dtos =
_mapper.Map<IQueryable<OurDto>>(models);
return await dtos.ToListAsync();
}
}
Profile, Model, and DTO
Profile
public class OurProfile : Profile
{
public OurProfile()
{
CreateMap<OurModel, OurDto>();
}
}
Model
public partial class OurModel
{
public string Number { get; set; }
public string Name1 { get; set; }
public string Name2 { get; set; }
public string Status { get; set; }
public DateTime? Date { get; set; }
public string Description { get; set; }
public string Comment { get; set; }
public string District { get; set; }
}
DTO
public class OurDto
{
public string Number { get; set; }
public string Name1 { get; set; }
public string Name2 { get; set; }
public string Status { get; set; }
public DateTime? Date { get; set; }
public string Description { get; set; }
public string Comment { get; set; }
public string District { get; set; }
}
Test Fixture
This is our test fixture.
public abstract class ApiClientFixture : IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> _factory;
protected abstract string RelativeUrl { get; }
protected ApiClientFixture(WebApplicationFactory<Startup> factory)
{
_factory = factory;
}
protected HttpClient CreateClient()
{
HttpClient client;
var builder = new UriBuilder();
client = _factory.CreateClient();
builder.Host = client.BaseAddress.Host;
builder.Path = $"{RelativeUrl}";
client.BaseAddress = builder.Uri;
return client;
}
}
Test
This is our test class. The single test in this test class fails.
public class Tests : ApiClientFixture
{
public Tests(WebApplicationFactory<Startup> factory) : base(factory)
{
}
protected override string RelativeUrl => "api/OurController/";
[Fact]
public async void GetAllReturnsSomething()
{
var response = await CreateClient().GetAsync("");
Assert.True(response.IsSuccessStatusCode);
}
}
When I debug the test I see that a 500 status code is returned from the URL provided to the in-memory API.
Does anybody have some suggestions? More than half of our tests currently fail, and I suspect that AutoMapper is not configured properly for integration testing.
Creating a map for IQueryable<T> is not really a good solution. In your answer you are losing proper flow of asynchronous database querying. I wrote about IQueryable<T> in a comment because you were looking for a 500 error cause. Making it work it's a one thing, making it a good solution it's another thing, however.
I'd strongly suggest to use AutoMapper ProjectTo() extension which you can use directly on a IQueryable<T> sequence. It let's you combine mapping and querying in one go. More or less it does a Select() based on your mappings, so it not only gives you proper model right away with the query result, but it also reduces the amount of columns obtained from database, which can make the query run faster. But, there are of course limitations to it, e.g. you can't use custom type converters or conditional mapping. You can read more about Project() in the documentation.
Usage:
public async Task<ActionResult<List<OurDto>>> GetAll()
{
return await _context
.OurModel
.ProjectTo<OutDto>(_mapper.ConfigurationProvider)
.ToListAsync();
}
Thanks to #Prolog for his comment. I realized that I need to map each element of the IQueryable individually, so I rewrote my Controller method.
Also, side note: IList.AsQueryable().ToListAsync() does not work, so I wrote:
IQueryable<OurDto> dtosQueryable = dtos.AsQueryable();
return await Task.FromResult(dtosQueryable.ToList());
Old Controller Method
[HttpGet]
public async Task<ActionResult<IEnumerable<OurDto>>> GetAll()
{
IQueryable<OurModel> models = _context.OurModel;
IQueryable<OurDto> dtos =
_mapper.Map<IQueryable<OurDto>>(models);
return await dtos.ToListAsync();
}
New Controller Method
public async Task<ActionResult<IEnumerable<OurDto>>> GetAll()
{
IQueryable<OurModel> models = _context.OurModel;
IList<OurDto> dtos = new List<OurDto>();
foreach (OurModel model in models)
{
OurDto dto = _mapper.Map<OurDto>(model);
dtos.Add(dto);
}
IQueryable<OurDto> dtosQueryable = dtos.AsQueryable();
return await Task.FromResult(dtosQueryable.ToList());
}
I want to implement something similar to lazy loading, but don't understand how to implement that. I want to force entity framework core include navigation property for all queries for type which implements my interface
public interface IMustHaveOrganisation
{
Guid OrganisationId { get; set; }
Organisation Organisation { get; set; }
}
public class MyEntity : IMustHaveOrganisation {
public Guid OrganisationId { get; set; }
public virtual Organisation Organisation { get; set; }
}
Without lazy loading I need to add .Include(x=>x.Organisation) to each query literally , and I can't use implementation of lazy loading provided by Microsoft. I need kind of custom implementation of that with loading just one property.
Or even force DbContext somehow to Include that property, it also fine for me.
How can I achieve that?
You can make this work by rewriting the expression tree, before it gets translated by EF Core.
To make this work in a way, where you don't have to specify anything additional in the query, you can hook into the very beginning of the query pipeline and inject the Include() call as needed.
This can be done, by specifying a custom IQueryTranslationPreprocessorFactory implementation.
The following fully working console project demonstrates this approach:
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace IssueConsoleTemplate
{
public class Organisation
{
public int OrganisationId { get; set; }
public string Name { get; set; }
}
public interface IMustHaveOrganisation
{
int OrganisationId { get; set; }
Organisation Organisation { get; set; }
}
public class MyEntity : IMustHaveOrganisation
{
public int MyEntityId { get; set; }
public string Name { get; set; }
public int OrganisationId { get; set; }
public virtual Organisation Organisation { get; set; }
}
public class CustomQueryTranslationPreprocessorFactory : IQueryTranslationPreprocessorFactory
{
private readonly QueryTranslationPreprocessorDependencies _dependencies;
private readonly RelationalQueryTranslationPreprocessorDependencies _relationalDependencies;
public CustomQueryTranslationPreprocessorFactory(
QueryTranslationPreprocessorDependencies dependencies,
RelationalQueryTranslationPreprocessorDependencies relationalDependencies)
{
_dependencies = dependencies;
_relationalDependencies = relationalDependencies;
}
public virtual QueryTranslationPreprocessor Create(QueryCompilationContext queryCompilationContext)
=> new CustomQueryTranslationPreprocessor(_dependencies, _relationalDependencies, queryCompilationContext);
}
public class CustomQueryTranslationPreprocessor : RelationalQueryTranslationPreprocessor
{
public CustomQueryTranslationPreprocessor(
QueryTranslationPreprocessorDependencies dependencies,
RelationalQueryTranslationPreprocessorDependencies relationalDependencies,
QueryCompilationContext queryCompilationContext)
: base(dependencies, relationalDependencies, queryCompilationContext)
{
}
public override Expression Process(Expression query)
{
query = new DependenciesIncludingExpressionVisitor().Visit(query);
return base.Process(query);
}
}
public class DependenciesIncludingExpressionVisitor : ExpressionVisitor
{
protected override Expression VisitConstant(ConstantExpression node)
{
// Call Include("Organisation"), if SomeEntity in a
// DbSet<SomeEntity> implements IMustHaveOrganisation.
if (node.Type.IsGenericType &&
node.Type.GetGenericTypeDefinition() == typeof(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable<>) &&
node.Type.GenericTypeArguments.Length == 1 &&
typeof(IMustHaveOrganisation).IsAssignableFrom(node.Type.GenericTypeArguments[0]))
{
return Expression.Call(
typeof(EntityFrameworkQueryableExtensions),
nameof(EntityFrameworkQueryableExtensions.Include),
new[] {node.Type.GenericTypeArguments[0]},
base.VisitConstant(node),
Expression.Constant(nameof(IMustHaveOrganisation.Organisation)));
}
return base.VisitConstant(node);
}
}
public class Context : DbContext
{
public DbSet<MyEntity> MyEntities { get; set; }
public DbSet<Organisation> Organisations { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// Register the custom IQueryTranslationPreprocessorFactory implementation.
// Since this is a console program, we need to create our own
// ServiceCollection for this.
// In an ASP.NET Core application, the AddSingleton call can just be added to
// the general service configuration method.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkSqlServer()
.AddSingleton<IQueryTranslationPreprocessorFactory, CustomQueryTranslationPreprocessorFactory>()
.AddScoped(
s => LoggerFactory.Create(
b => b
.AddConsole()
.AddFilter(level => level >= LogLevel.Information)))
.BuildServiceProvider();
optionsBuilder
.UseInternalServiceProvider(serviceProvider) // <-- use our ServiceProvider
.UseSqlServer(#"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=62849896")
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyEntity>(
entity =>
{
entity.HasData(
new MyEntity {MyEntityId = 1, Name = "First Entity", OrganisationId = 1 },
new MyEntity {MyEntityId = 2, Name = "Second Entity", OrganisationId = 1 },
new MyEntity {MyEntityId = 3, Name = "Third Entity", OrganisationId = 2 });
});
modelBuilder.Entity<Organisation>(
entity =>
{
entity.HasData(
new Organisation {OrganisationId = 1, Name = "First Organisation"},
new Organisation {OrganisationId = 2, Name = "Second Organisation"});
});
}
}
internal static class Program
{
private static void Main()
{
using var context = new Context();
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var myEntitiesWithOrganisations = context.MyEntities
.OrderBy(i => i.MyEntityId)
.ToList();
Debug.Assert(myEntitiesWithOrganisations.Count == 3);
Debug.Assert(myEntitiesWithOrganisations[0].Name == "First Entity");
Debug.Assert(myEntitiesWithOrganisations[0].Organisation.Name == "First Organisation");
}
}
}
Even though no explicit Include() is being made in the query in Main(), the following SQL is being generated, that does join and retrieve the Organisation entities:
SELECT [m].[MyEntityId], [m].[Name], [m].[OrganisationId], [o].[OrganisationId], [o].[Name]
FROM [MyEntities] AS [m]
INNER JOIN [Organisations] AS [o] ON [m].[OrganisationId] = [o].[OrganisationId]
ORDER BY [m].[MyEntityId]
I m new in .net core 2.1
I m working with .net core 2.1 with code first approach
issue is when I create a new object dbcontext class then give error see below line
dbcontextstudent db=new dbcontextstudent(); //here give an red line
appsettings.json
},
"ConnectionStrings": {
"sqlserverconn": "Server=DEVISSHAHID; Database=studdbs; User id=xxxx;Password=xxxxx;"
},
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//connection string
services.AddDbContext<DbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("sqlserverconn")));
student.cs
namespace WebApplication1.Models
{
public class student
{
[Key]
public int studid { get; set; }
public string studname { get; set; }
public string studsalary { get; set; }
public int studage { get; set; }
}
}
dbcontextstudent.cs
namespace WebApplication1.Models
{
public class dbcontextstudent : DbContext
{
public dbcontextstudent(DbContextOptions<dbcontextstudent> options) : base(options)
{
}
public DbSet<student> stud { get; set; }
}
}
HomeController.cs
I m not understood the above intellisense
I write the code as per intellisense but still give an error I know error is clear but not solved
which place doing I m wrong?
You will have to pass your DbContext type to the AddDbContext method in ConfigureServices method like this:
services.AddDbContext<dbcontextstudent>(options => options.UseSqlServer(Configuration.GetConnectionString("sqlserverconn")));
After that, you have registered the dbcontextstudent class in dependency injection.
You shouldn't create the instance of dbcontextstudent on your own like you did:
dbcontextstudent db=new dbcontextstudent();
Instead you can inject it though the constructor of your controller like this:
public HomeController : Controller
{
private readonly dbcontextstudent _db;
public HomeController(dbcontextstudent db)
{
_db = db;
}
... and then you can use the _db variable in your post action
}
i have this Dependency resolver
public class NinjectDependencyResolvercs : IDependencyResolver
{
private readonly IResolutionRoot resolutionRoot;
public NinjectDependencyResolvercs(IResolutionRoot kernel)
{
resolutionRoot = kernel;
}
public object GetService(Type serviceType)
{
return resolutionRoot.TryGet(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return resolutionRoot.GetAll(serviceType);
}
}
in global.asax.cs
// Ninject DI container ----------------------------------------------------------- |
public void SetupDependencyInjection()
{
// Create Ninject DI kernel
IKernel kernel = new StandardKernel();
#region Register services with Ninject DI Container
// DbContext to SqlDataContext
kernel.Bind<DbContext>()
.To<SqlDataContext>();
// IRepository to SqlRepository
kernel.Bind<IRepository>()
.To<SqlRepository>();
// IUsersServices to UsersServices
kernel.Bind<IUsersServices>()
.To<UsersServices>();
// IMessagesServices to MessagesServices
kernel.Bind<IMessagesServices>()
.To<MessagesServices>();
// IJobAdvertsServices to JobAdvertsServices
kernel.Bind<IJobAdvertsServices>()
.To<JobAdvertsServices>();
#endregion
// Tell ASP.NET MVC 3 to use Ninject DI Container
DependencyResolver.SetResolver(new NinjectDependencyResolvercs(kernel));
}
// --------------------------------------------------------------------------------- |
and class
public class SqlDataContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Profile> Profiles { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<JobAdvert> JobAdverts { get; set; }
public DbSet<Message> Messages { get; set; }
protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().HasMany(x => x.Roles).WithMany(x => x.Users).Map(x =>
{
x.MapLeftKey(y => y.UserId, "UserId");
x.MapRightKey(y => y.RoleId, "RoleId");
x.ToTable("UsersInRoles");
});
base.OnModelCreating(modelBuilder);
}
}
all dependecies work fine but for DbContext to SqlDataContext is problem. If use this:
public class SqlRepository
{
private DbContext dataContext;
public SqlRepository(DbContext dataContext) {
this.dataContext = dataContext;
}
public DbSet<User> Users {
get {
return dataContext.Users;
}
}
}
then
dataContext.Users
and all others properties alert this error:
'System.Data.Entity.DbContext' does not contain a definition for 'JobAdverts' and no extension method 'JobAdverts' accepting a first argument of type 'System.Data.Entity.DbContext' could be found (are you missing a using directive or an assembly reference?)
Have anyone any idea why DI doent work for Class DbContext ?
If I understand correctly, You're injecting DbContext which doesn't have those methods/properties, as they're declared in the derived type SqlDataContext.
You need to inject the SqlDataContext. If you want to use an interface, you'll need to extract an interface from SqlDataContext.
EDIT:
Ninject binds at runtime while the errors you're getting (I presume) are at compile time. You could get around this by using the dynamic key word, but that's just working AROUND the problem.
public class SqlRepository
{
private dynamic dataContext;
public SqlRepository(DbContext dataContext) {
this.dataContext = dataContext;
}
...
}
What you need to do is change the signature to use your SqlDataContext:
public class SqlRepository
{
private SqlDataContextdata Context;
public SqlRepository(SqlDataContextdata Context) {
this.dataContext = dataContext;
}
...
}
because DbContext does not contain those methods, only your SqlContext does. and your sqlcontext is bound to DbContext at runtime.