currently I'm experimenting with WPF/EFCore and DI. I already set up the Application class to register the different/upcoming services like this:
public partial class App : Application
{
private IHost AppHost;
public App()
{
AppHost = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
ConfigureServices(context.Configuration, services);
})
.Build();
}
private void ConfigureServices(IConfiguration Configuration, IServiceCollection Services)
{
Services.AddDbContext<AppDataContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("ProdToolDb"));
});
Services.AddSingleton<MainWindow>();
}
protected override async void OnStartup(StartupEventArgs e)
{
await AppHost.StartAsync();
MainWindow MainWindow = AppHost.Services.GetRequiredService<MainWindow>();
MainWindow.Show();
base.OnStartup(e);
}
protected override async void OnExit(ExitEventArgs e)
{
using (AppHost)
{
await AppHost.StopAsync();
}
base.OnExit(e);
}
}
Of course, I have the appr. DataContext classes too:
public class AppDataContext : DbContext
{
public AppDataContext(DbContextOptions<AppDataContext> options) : base(options) { }
public DbSet<Capacity> Capacities { get; set; }
public DbSet<CapacityType> CapacityTypes { get; set; }
}
public class AppDataContextFactory : IDesignTimeDbContextFactory<AppDataContext>
{
public AppDataContext CreateDbContext(string[] args = null)
{
DbContextOptionsBuilder<AppDataContext> OptionsBuilder = new DbContextOptionsBuilder<AppDataContext>();
IConfigurationRoot Config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
OptionsBuilder.UseSqlServer(Config.GetConnectionString("ProdToolDb"));
return new AppDataContext(OptionsBuilder.Options);
}
}
I was told in an other question, that in order to get the migration working with EF Core, I have to use IDesignTimeDbContextFactory.
Now, going further, I set up the generic pattern implementation. I created an interface:
public interface IDataService<T>
{
Task<IEnumerable<T>> GetAll();
Task<T> Get(int Id);
Task<T> Create(T Entity);
Task<T> Update(int Id, T Entity);
Task<bool> Delete(int Id);
}
and it's implementation:
public class GenericDataService<T> : IDataService<T> where T : class
{
private readonly AppDataContextFactory ContextFactory;
public GenericDataService(AppDataContextFactory contextFactory)
{
ContextFactory = contextFactory;
}
public async Task<T> Create(T Entity)
{
using (AppDataContext Context = ContextFactory.CreateDbContext())
{
await Context.Set<T>().AddAsync(Entity);
await Context.SaveChangesAsync();
return Entity;
}
}
... other methods omitted for brevity
}
I have the following questions:
Since I use IDesignTimeDbContextFactory in order to have created and configured the DataContext, is this call in the startup redundant?
Services.AddDbContext<AppDataContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("ProdToolDb"));
});
Or should I instead of this create a IDesignTimeDbContextFactory service and register it? If yes, then how?
As you see in the IDataService implementation, I use AppDataContextFactory and AppDataContext directly, not with the DI technique. How should I modify the code in order to use DI?
And if I would like to handle everything above with DI, I have to register/add GenericDataService<T> in the startup class, right? How can I do that?
Please advise.
Thank you.
EDIT:
I read the linked blog entries and others too and now I see, why you say, that the generic pattern is an antipattern. I will throw it away and concentrate on the general WPF binding methods and DI of views/viewmodels.
Related
I'm trying to make a N-layer architecture for my Telegram Bot. I created DAL, BLL and PL. I would like to add entity News to my DB. But I have some issue with my context.
My DB Context:
public class ApplicationContext : DbContext
{
public DbSet<News> News { get; set; }
public DbSet<User> Users { get; set; }
public ApplicationContext(DbContextOptions<ApplicationContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<News>().Property(tn => tn.Id).ValueGeneratedOnAdd();
modelBuilder.Entity<User>().Property(tn => tn.Id).ValueGeneratedOnAdd();
modelBuilder.Entity<News>().Property(tn => tn.Title).IsRequired();
modelBuilder.Entity<News>().Property(tn => tn.Href).IsRequired();
modelBuilder.Entity<News>().Property(tn => tn.Image).IsRequired();
modelBuilder.Entity<News>().Property(tn => tn.Date).IsRequired();
modelBuilder.Entity<User>().Property(tn => tn.UserId).IsRequired();
modelBuilder.Entity<User>().Property(tn => tn.UserName).IsRequired();
modelBuilder.Entity<User>().Property(tn => tn.DateOfStartSubscription).IsRequired();
base.OnModelCreating(modelBuilder);
}
}
Interface UoW:
public interface IUnitOfWork : IDisposable
{
INewsRepository News { get; }
IUserRepository Users { get; }
int Complete();
}
Class UoW:
public class UnitOfWork : IUnitOfWork
{
public IUserRepository Users { get; }
public INewsRepository News { get; }
private readonly ApplicationContext _context;
public UnitOfWork(ApplicationContext context)
{
_context = context;
Users = new UserRepository.UserRepository(_context);
News = new NewsRepository.NewsRepository(_context);
}
public int Complete() => _context.SaveChanges();
public void Dispose() => _context.Dispose();
}
My DAL Generic Repository:
async Task IGenericRepository<T>.AddAsync(T entity) => await _context.Set<T>().AddAsync(entity);
DAL Injection:
public static class DALInjection
{
public static void Injection(IServiceCollection services)
{
services.AddTransient(typeof(IGenericRepository<>), typeof(GenericRepository<>));
services.AddTransient<IUserRepository, UserRepository.UserRepository>();
services.AddTransient<INewsRepository, NewsRepository.NewsRepository>();
services.AddTransient<IUnitOfWork, UnitOfWork.UnitOfWork>();
}
}
My BLL Service class:
public class ParserService : IParser
{
private IUnitOfWork _unitOfWork;
private readonly IMapper _mapper;
public ParserService(IUnitOfWork unitOfWork, IMapper mapper)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
}
private async Task SaveArticles(IEnumerable<NewsDTO> articlesDTO)
{
var articles = _mapper.Map<IEnumerable<NewsDTO>, IEnumerable<News>>(articlesDTO);
await _unitOfWork.News.AddAsync(articles.First());
_unitOfWork.Complete();
}
BLL Injection:
public static class BLLInjection
{
public static void Injection(IServiceCollection services)
{
DALInjection.Injection(services);
services.AddTransient<IParser, ParserService>();
services.AddTransient<IArticleService, ArticleService>();
services.AddAutoMapper(typeof(CommonMappingProfile));
}
}
My PL:
private static async Task SendArticleAsync(long chatId, int offset, int count)
{
var articles = await _parser.MakeHtmlRequest(offset, count);
foreach (var article in articles)
{
var linkButton = KeyboardGoOver("Перейти", article.Href);
await _client.SendPhotoAsync(chatId: chatId, photo: article.Image,
caption: $"*{article.Title}*", parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: linkButton);
}
await OnLoadMoreNewsAsync(chatId, offset + count, count);
}
PL Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<ApplicationContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly(typeof(ApplicationContext).Assembly.FullName)));
BLLInjection.Injection(services);
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "TelegramBot.WebApi", Version = "v1" });
});
}
When I tried to debug, I had this error but I could not resolve this issue.
_context = Database = {"Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may o...
Could someone help me with this issue?
There are few problems in your code.
Controllers are scoped entities, their instances created per http request and disposed after request is finished. It means controller is not good place to subscribe to events. When you call /start endpoint you create an instance of TelegramController and TelegramBotClient, but once the request is finished, the controller and all its non-singleton dependencies (IParser in your case) are disposed. But you subscribed for TelegramBotClient events that captured reference to IParser. It means all events that will arrive after request is finished will try to access disposed IParser instance and this is the reason for your exception.
For event based messages it's better to use IHostedService. You will need to use IServiceScopeFactory to create a scope for each message and resolve your dependencies from this scope.
public class TelegramHostedService : IHostedService
{
private IServiceScopeFactory _scopeFactory;
public TimedHostedService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public Task StartAsync(CancellationToken stoppingToken)
{
_client = new TelegramBotClient(_token);
_client.OnMessage += OnMessageHandlerAsync;
_client.OnCallbackQuery += OnLoadCallBackAsync;
_client.StartReceiving();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken stoppingToken)
{
// TODO: Unsubscribe from events
return Task.CompletedTask;
}
public static async void OnMessageHandlerAsync(object sender, MessageEventArgs e)
{
using var scope = _scopeFactory.CreateScope();
var handler = scope.ServiceProvider.GetRequiredService<MessageHandler>();
await handler.Handle(TODO: pass required args); // Move the logic to separate handler class to keep hosted service clean
}
...
}
I moved call to _client.StartReceiving(); after event subscription otherwise there is a chance for race condition when you receive event but you don't yet have subscribers and this event will be lost.
The second issue is as #PanagiotisKanavos said: async void can't be awaited, hence once your code hit first true async method (like DB access, http request, file read or any other I/O operation) the control is returned to the point where async void method was called and continues execution without waiting for operation completion. The whole app can even crash if you throw unhandled exception from such method, hence async void should be avoided. To prevent these problems wrap your async event handlers with sync methods that will block the execution with Wait() method:
public class TelegramHostedService : IHostedService
{
private IServiceScopeFactory _scopeFactory;
public TimedHostedService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public Task StartAsync(CancellationToken stoppingToken)
{
_client = new TelegramBotClient(_token);
_client.OnMessage += OnMessageHandler;
_client.OnCallbackQuery += OnLoadCallBack;
_client.StartReceiving();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken stoppingToken)
{
// TODO: Unsubscribe from events
return Task.CompletedTask;
}
public static void OnMessageHandler(object sender, MessageEventArgs e)
{
OnMessageHandlerAsync(sender, e).Wait();
}
public static async Task OnMessageHandlerAsync(object sender, MessageEventArgs e)
{
using var scope = _scopeFactory.CreateScope();
var handler = scope.ServiceProvider.GetRequiredService<MessageHandler>();
await handler.Handle(TODO: pass required args); // Move the logic to separate handler class to keep hosted service clean
}
...
}
I'm developing a Blazor app and somewhere I'm implementing an interface:
public class UserData : IUserData
{
private readonly ISqlDataAccess _db;
public UserData(ISqlDataAccess db)
{
_db = db;
}
public void SomeFunction ()
{
...
}
}
public interface IUserData
{
void SomeFunction();
}
While on .razor I can do: #inject IUserData UserData; and ((UserData)UserData).SomeFunction();; I'm failing to discover how to do it on .cs file.
//There is no such constructor and If I create a new one, then I won't get the _db dependecy injection
IUserData userDate = new UserData();
userDate.SomeFunction();
Edit
So now, when I'm calling the method from the .cs file, the app freezes; it doesn't throw an error and I am able to refresh the page, so it seems it's stuck on the call to the db; but if I call it from the .razor it works flawlessy.
.cs
public AccountService(IUserData userData)
{
_userData = userData;
}
...
public async Task<bool> Validate(string userId, string password)
{
...
try
{
List<UserModel> users = new List<UserModel<();
users = await _userData.GetUsers();
//NEVER GETS HERE
return true;
}
catch (Exception ex)
{
return false;
}
...
}
.razor
#inject IUserData _db;
#code {
private List<UserModel> users;
...
protected override async Task OnInitializedAsync()
{
users = await _db.GetUsers();
}
...
UserData
public class UserData : IUserData
{
private readonly ISqlDataAccess _db;
public UserData(ISqlDataAccess db)
{
_db = db;
}
public Task<List<UserModel>> GetUsers()
{
string sql = "Select *from dbo.Users";
return _db.LoadData<UserModel, dynamic>(sql, new { });
}
...
}
IUserData
public interface IUserData
{
Task<List<UserModel>> GetUsers();
...
}
Edit2
It turns out I was missing an await when calling Validate() service, and thus not running it asynchronous.
At some point in the program, you need to setup dependency injection. This is most common to do in the ConfigureServices method in Startup.cs by convention.
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonConfigurationProvider("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient <ISqlDataAccess, SqlDataAccess>(); //Second argument is the implementation of the interface
services.AddTransient <IUserData, UserData>();
}
}
You need to pass the ISqlDataAccess to the constructor of the UserData but you had it covered already.
public class UserData : IUserData
{
private readonly ISqlDataAccess _db;
public UserData(ISqlDataAccess db)
{
_db = db;
}
//...
}
Then you need to pass your IUserData to your objects via constructors:
public class ClassWithIUserDataDependency {
private IUserData _userData;
public ClassWithIUserDataDependency (IUserData userData) {
_userData = userData;
}
//rest of the class
}
One note: You would need to pass IUserData to all dependency classes. Based on the name, this looks like a POCO object (If it is not, don't mind this comment) If this is a POCO class, or anything representing a DTO or Data, then it is better to separate db from it and allow users to just new it. If it is not, you may want to change its name.
In Startup.cs you can register your interface and implementation;
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IUserData, Userdata>();
}
Then you can use the interface in a class:
public class TestClass{
private IUserData _userData;
public TestClass(IUserData userdata){
_userData = userdata;
}
}
I am having trouble trying to apply dependency injection. After a lot of research and looking at various videos on YouTube and answers on Stack overflow, my ITaskRepository keeps returning a null instead of it being an instance of my repository. Looking at my code it seems I have added all the right things to make dependency injection work.
My Base Repository interface
using portfolio_backend.Data.Base;
using System.Collections.Generic;
namespace portfolio_backend.Business.Repositories.Base
{
public interface IBaseRepository<TEntity> where TEntity : BaseModel
{
void Add(TEntity model);
void Delete(TEntity model);
bool Exists(int Id);
TEntity Get(int Id);
IEnumerable<TEntity> GetAll();
void Update(int Id, TEntity model);
}
}
My BaseRepository class
using Microsoft.EntityFrameworkCore.Internal;
using portfolio_backend.Data;
using portfolio_backend.Data.Base;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace portfolio_backend.Business.Repositories.Base
{
public class BaseRepository<TEntity> : IBaseRepository<TEntity> where TEntity : BaseModel
{
protected PortfolioContext _context;
public BaseRepository(PortfolioContext context)
{
_context = context;
}
public void Add(TEntity model)
{
if (!Exists(model.Id))
{
_context.Set<TEntity>().Add(model);
_context.SaveChanges();
}
}
public void Delete(TEntity model)
{
if (Exists(model.Id))
{
_context.Set<TEntity>().Remove(model);
_context.SaveChanges();
}
}
public bool Exists(int Id)
{
return _context.Set<TEntity>().Any(model => model.Id == Id);
}
public TEntity Get(int Id)
{
return _context.Set<TEntity>().FirstOrDefault(model => model.Id == Id);
}
public IEnumerable<TEntity> GetAll()
{
return _context.Set<TEntity>().ToList();
}
public void Update(int Id, TEntity model)
{
var modelToFind = Get(Id);
_context.Set<TEntity>().Update(modelToFind);
_context.SaveChanges();
}
}
}
My ITaskRepository interface
using portfolio_backend.Business.Repositories.Base;
using portfolio_backend.Data;
using System.Collections.Generic;
namespace portfolio_backend.Business.Repositories
{
public interface ITaskRepository : IBaseRepository<Task>
{
IEnumerable<Task> GetTaskByProjects(int ProjectId);
}
}
TaskRepository Implementation
using portfolio_backend.Business.Repositories.Base;
using portfolio_backend.Data;
using System.Collections.Generic;
using System.Linq;
namespace portfolio_backend.Business.Repositories
{
public class TaskRepository : BaseRepository<Task>, ITaskRepository
{
public TaskRepository(PortfolioContext context) : base(context)
{
}
public IEnumerable<Task> GetTaskByProjects(int ProjectId)
{
return _context.Tasks.OrderByDescending(task => task.Project.Id == ProjectId).ToList();
}
}
}
My Startup class:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using portfolio_backend.Business.Repositories.Base;
using portfolio_backend.Business.Repositories;
using portfolio_backend.Data;
using Blazorise;
using Blazorise.Bootstrap;
using Blazorise.Icons.FontAwesome;
namespace portfolio_backend.Presentation
{
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.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<PortfolioContext>(options =>
options.UseMySQL(Configuration.GetConnectionString("portfolio")));
services.AddScoped(typeof(IBaseRepository<>), typeof(BaseRepository<>));
services.AddScoped<ITaskRepository, TaskRepository>();
services.AddBlazorise(options =>{
options.ChangeTextOnKeyPress = true;})
.AddBootstrapProviders()
.AddFontAwesomeIcons();
services.AddRazorPages();
services.AddServerSideBlazor();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.ApplicationServices
.UseBootstrapProviders()
.UseFontAwesomeIcons();
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
}
I am trying to apply dependency injection for the following two classes:
Tasks.razor.cs ( a code-behind for a Blazor component)
using portfolio_backend.Business;
using portfolio_backend.Business.Repositories;
namespace portfolio_backend.Presentation.Pages
{
public partial class Tasks
{
private ITaskRepository _taskRepository;
private TaskViewModel _taskViewModel => new TaskViewModel(_taskRepository);
protected override void OnInitialized()
{
_taskViewModel.SeedTasks();
}
}
}
and the view model for this component
using portfolio_backend.Business.Repositories;
using portfolio_backend.Data;
using System.Collections.Generic;
namespace portfolio_backend.Business
{
public class TaskViewModel
{
private ITaskRepository _taskRepository {get; set;}
private List<Task> _allTasks;
public TaskViewModel(ITaskRepository repository)
{
_taskRepository = repository;
}
public List<Task> AllTasks
{
get => _allTasks;
set => _taskRepository.GetAll();
}
public void SeedTasks()
{
_taskRepository.Add( new Task { Description = "Task 1"} );
_taskRepository.Add(new Task { Description = "Task 2" });
_taskRepository.Add(new Task { Description = "Task 3" });
}
}
}
_taskRepository always returns null, and this is the error message that appears:
System.NullReferenceException: 'Object reference not set to an instance of an object.'
what can I do to solve this? or how can I apply DI in a better way under these circumstances?
UPDATE:
I made the following changes based on one of the suggested solutions in the comments:
using portfolio_backend.Business;
using portfolio_backend.Business.Repositories;
namespace portfolio_backend.Presentation.Pages
{
public partial class Tasks
{
private ITaskRepository _taskRepository;
private TaskViewModel _taskViewModel;
public Tasks(ITaskRepository repository)
{
_taskRepository = repository;
_taskViewModel = new TaskViewModel(_taskRepository);
}
protected override void OnInitialized()
{
_taskViewModel.SeedTasks();
}
}
}
This will trigger the following error:
MissingMethodException: No parameterless constructor defined for type 'portfolio_backend.Presentation.Pages.Tasks'.
As the error suggested I added an additional parameterless constructor
using portfolio_backend.Business;
using portfolio_backend.Business.Repositories;
namespace portfolio_backend.Presentation.Pages
{
public partial class Tasks
{
private ITaskRepository _taskRepository;
private TaskViewModel _taskViewModel;
public Tasks()
{
}
public Tasks(ITaskRepository repository)
{
_taskRepository = repository;
_taskViewModel = new TaskViewModel(_taskRepository);
}
protected override void OnInitialized()
{
_taskViewModel.SeedTasks();
}
}
}
The change above created the same issue with the taskRepository being null.
_taskRepository must be either a property or a constructor parameter. You have it as a class member. It can't be injected like that.
You have to register your dependency in ConfigureServices(IServiceCollection services) of your startup file :
services.AddScoped<ITaskRepository, TaskRepository>();
or
services.AddTransient<ITaskRepository, TaskRepository>();
You have to decide what fits you app better.
Following the edits you've made to your initial question, it seems that your are using Dependency Injection mechanisms on a class which is not registered : Tasks. How is this class implemented?
If you want to use DI on a specific class, you should register it as you did with ITaskRepository for example.
Add the following line to your ConfigureServices() method :
services.AddScoped<Tasks>();
There were two main challenges in this scenario.
The first one was that I didn't design my app properly. What I mean by that is that I initially intended to use dependency injection on an instance of my repository to be able to create an instance of my TaskViewModel in my code behind like this.
public Tasks(ITaskRepository repository)
{
_taskRepository = repository;
_taskViewModel = new TaskViewModel(_taskRepository);
}
A better way to do it which was part of my solution was to also create an interface for my TaskViewModel so I can use dependency injection in my Blazor component code-behind. The TaskViewModel itself should have had an instance of my repository through dependency injection.
ITaskViewModel:
public interface ITaskViewModel : IBaseViewModel<Task>
{
List<Task> AllTasks { get; set; }
void SeedTasks();
}
My Implementation for the TaskViewModel
public class TaskViewModel : BaseViewModel<Task>, ITaskViewModel
{
private ITaskRepository _taskRepository;
private List<Task> _allTasks;
public TaskViewModel(ITaskRepository repository) : base(repository)
{
_taskRepository = repository;
}
public List<Task> AllTasks
{
get => _allTasks;
set
{
_allTasks = value;
}
}
public void SeedTasks()
{
var task1 = new Task { Description = "Task 1" };
var task2 = new Task { Description = "Task 2" };
var task3 = new Task { Description = "Task 3" };
_taskRepository.Add(task1);
_taskRepository.Add(task2);
_taskRepository.Add(task3);
}
}
Component registration on the ConfigureServices method of the Startup.cs file
services.AddScoped<ITaskViewModel, TaskViewModel>();
The 2nd problem was that I could not use the constructor or the member property approach to use dependency injection in a Blazor component code-behind. Not sure if the same applies to razor page code behind, but the way you use dependency inject for a Blazor component code-behind is by using the Inject attribute
Tasks.razor.cs
[Inject]
private ITaskViewModel _viewModel { get; set; }
Make sure you also have the following Nuget package installed for the Inject attribute to work.
using Microsoft.AspNetCore.Components;
My Repository like that.
public class DonorRepository : BaseRepository<Donor>, IDonorRepository
{
public DonorRepository(ApplicationContext dbContext) : base(dbContext)
{
}
}
public interface IDonorRepository : IBaseRepository<Donor>
{
}
public interface IBaseRepository<T> where T : class
{
T GetById(object EntityId);
List<T> GetAll(Expression<Func<T, bool>> Filter = null);
void Add(T Entity);
void Delete(T Entity);
}
public abstract class BaseRepository<T> where T : class
{
private ApplicationContext _context;
protected ApplicationContext context { get { return _context; } }
protected BaseRepository(ApplicationContext dbContext)
{
_context = dbContext;
}
public virtual void Add(T Entity)
{
_context.Add<T>(Entity);
}
public virtual void Delete(T Entity)
{
_context.Set<T>().Remove(Entity);
}
public virtual List<T> GetAll(Expression<Func<T, bool>> Filter = null)
{
return _context.Set<T>().ToList();
}
public virtual T GetById(object EntityId)
{
var propInfo = GetIdPropInfo();
return _context.Set<T>().SingleOrDefault(x => (int)propInfo.GetValue(x) == (int)EntityId);
}
private PropertyInfo GetIdPropInfo()
{
var type = typeof(T);
var result = "Id";
return type.GetProperty(result);
}
}
And my ApplicationContext
public class ApplicationContext : DbContext
{
public ApplicationContext(DbContextOptions<ApplicationContext> options) : base(options)
{
}
public DbSet<Donor> Donors { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
I use repository like that:
public class DonorService : IDonorService
{
private IDonorRepository donorRepository;
public DonorService(IDonorRepository donorRepository)
{
this.donorRepository = donorRepository;
}
}
And my program.cs
static class Program
{
public static IServiceProvider ServiceProvider { get; set; }
[STAThread]
static void Main()
{
IServiceCollection serviceCollection = new ServiceCollection();
Application.SetHighDpiMode(HighDpiMode.SystemAware);
ConfigureServices(serviceCollection);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
using (ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider())
{
var MainForm = serviceProvider.GetRequiredService<MainForm>();
Application.Run(MainForm);
}
}
static void ConfigureServices(IServiceCollection serviceCollection)
{
serviceCollection.AddScoped<IDonorService, DonorService>();
serviceCollection.AddScoped<IUnitOfWork, UnitOfWork>();
serviceCollection.AddTransient(typeof(DbContext), typeof(ApplicationContext));
serviceCollection.AddTransient(typeof(IDonorRepository), typeof(DonorRepository));
}
}
When i start this WinForm project i get this error:
"Unable to resolve service for type 'BloodBank.DAL.ApplicationContext' while attempting to activate 'BloodBank.DAL.Repositories.DonorRepository'."
What and where did I make a mistake.
In the following line you register your ApplicationContext type to be resolved whenever something requests a DbContext:
serviceCollection.AddTransient(typeof(DbContext), typeof(ApplicationContext));
However, your DonorRepository and your BaseRepository<T> classes don't request a DbContext but an ApplicationContext.
So you either need to register your ApplicationContext to be resolved for a requested ApplicationContext, or you need to replace all occurrences of ApplicationContext with DbContext in your DonorRepository and BaseRepository<T> classes.
Unrelated to the actual question, you are likely to need to use serviceCollection.AddEntityFrameworkSqlServer() and optionsBuilder.UseInternalServiceProvider(serviceProvider).UseSqlServer() when using a service provider with EF Core in a console project.
You can try this
serviceCollection.AddDbContext<ApplicationContext>(
opt => opt.UseSqlServer("DefaultConnection"));
I am new to Repository concept and get some questions. I have created simple repository class in my MVC app.
public interface IRepository<TEntity> where TEntity : class
{
List<TEntity> FetchAll();
IQueryable<TEntity> Query { get; }
void Add(TEntity entity);
void Delete(TEntity entity);
void Save();
}
public class SqlRepository<T> : IRepository<T> where T : class
{
readonly DataContext _db;
public SqlRepository(DataContext db)
{
_db = db;
}
#region IRepository<T> Members
public IQueryable<T> Query
{
get { return _db.GetTable<T>(); }
}
public List<T> FetchAll()
{
return Query.ToList();
}
public void Add(T entity)
{
_db.GetTable<T>().InsertOnSubmit(entity);
}
public void Delete(T entity)
{
_db.GetTable<T>().DeleteOnSubmit(entity);
}
public void Save()
{
_db.SubmitChanges();
}
#endregion
}
In my Controller I initialize repository classes for specified table class like this
public class AdminController : Controller
{
private readonly SqlRepository<User> _userRepository = new SqlRepository<User>(new DataContext(ConfigurationManager.ConnectionStrings["ConnectionString"].ToString()));
private readonly SqlRepository<Order> _orderRepository = new SqlRepository<Order>(new DataContext(ConfigurationManager.ConnectionStrings["ConnectionString"].ToString()));
//Skip code
}
But I duplicate this code again and again in many places in my app. What is the best place to instanceate these repository classes?
I think you should refer to repository via it's interface:
public class AdminController : Controller
{
private readonly IRepository<User> _userRepository;
private readonly IRepository<Order> _orderRepository;
public AdminController(IRepository<User> userRepository,
IRepository<Order> orderRepository)
{
_userRepository = userRepository;
_orderRepository = orderRepository;
}
//Skip code
}
And inject implementations via some dependency injection framework.
UPDATE
You can use Ninject for dependency injection to your controllers. Here is Example how to add DependencyResolver to your application. In your case you can configure kernel this way:
IKernel kernel = new StandardKernel();
var connectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
kernel.Bind(typeof(DataContext)).ToMethod(context => new DataContext(connectionString));
kernel.Bind(typeof(IRepository<>)).To(typeof(SqlRepository<>));
And that's it. No duplication. Dependencies are resolved. Your class does not depend on concrete repositories. You can easy mock dependencies for testing.