0

ASP.NET Core 5 MVC application uses EF Core with Npgsql data provider with different child databases.

There is a base database. Child databases contain additional columns in tables. Child databases contain all base table tables and columns.

How to use dynamic columns? EF Core and ER Npgsql provider do not support dynamic columns.

I tried to generate interface from base database which and used all of those databases. Each database DbContext is compiled into separate assembly and ASP.NET MVC dependency injection is used to provide it to application at runtime. For database specific columns Poco GetProperties() method can be used to access them.

EF Core scaffold tool does not have any option on create interface instead of classes. Also it looks like Visual Studio does no allow to create interface from DbContext class.

How to create interface from DbContext class? Or is there some other solution?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Andrus
  • 26,339
  • 60
  • 204
  • 378

1 Answers1

0

If I understood you correctly, what you might want to do is to create an Interface which will define common tasks for database contexts and will be implemented by SqlDbLayer which will actually work with database context. So by this approach, you can switch between databases easily or use several ones.

For example, your DAL may look like this:

Database layer interface

public interface IDbLayer
{
    public Task<IEnumerable<Student>> GetStudents();
    public Task<IEnumerable<Subject>> GetSubjects();
    public Task<IEnumerable<Enrollment>> GetEnrollments();

    // Student
    public Task<Student> GetStudent(int? id);
    public Task<Student> AddStudent(Student newStudent);
    public Task<Student> EditStudent(Student studentToEdit);
    public Task<Student> DeleteStudent(int? id);

    // Course
    public Task<Subject> GetSubject(int? id);
    public Task<Subject> AddSubject(Subject newSubject);
    public Task<Subject> EditSubject(Subject subjectToEdit);
    public Task<Subject> DeleteSubject(int? id);

    // Enrollment
    public Task<Enrollment> GetEnrollment(int? id);
    public Task<Enrollment> AddEnrollment(Enrollment newEnrollment);
    public Task<Enrollment> EditEnrollment(Enrollment enrollmentToEdit);
    public Task<Enrollment> DeleteEnrollment(int? grade);
}

SQL Database layer

public class SqlDbLayer : IDbLayer
{
    public readonly SchoolDbContext _context;

    public SqlDbLayer(SchoolDbContext context)
    {
        _context = context;
    }

    public async Task<IEnumerable<Student>> GetStudents()
    {
        return await _context.Students
            .Include(s => s.Study)
            .OrderBy(s => s.FirstName)
            .ThenBy(s => s.LastName)
            .ThenBy(s => s.IndexNumber)
            .ThenBy(s => s.Study.Name)
            .ToListAsync();
    }

    public async Task<IEnumerable<Subject>> GetSubjects()
    {
        return await _context.Subjects
            .OrderBy(s => s.Name)
            .ToListAsync();
    }

    public async Task<IEnumerable<Enrollment>> GetEnrollments(int? id)
    {
        if(id != null)
        {
            return await _context.Enrollments
                .Include(e => e.Student)
                .ThenInclude(e => e.Study)
                .Include(e => e.Subject)
                .Include(e => e.Instructor)
                .OrderBy(e => e.IdEnrollment)
                .Where(e => e.IdStudent == id)
                .ToListAsync();
        } 
        else
        {
            return await _context.Enrollments
            .Include(e => e.Student)
            .ThenInclude(e => e.Study)
            .Include(e => e.Subject)
            .Include(e => e.Instructor)
            .OrderBy(e => e.IdEnrollment)
            .ToListAsync();
        }
    }

    public async Task<Student> GetStudent(int? id)
    {
        var students = await _context.Students.Include(s => s.Study).ToListAsync();

        return students.Find(s => s.IdStudent == id);
    }

    public async Task<Subject> GetSubject(int? id)
    {
        var subjects = await _context.Subjects.ToListAsync();

        return subjects.Find(s => s.IdSubject == id);
    }

    public async Task<Enrollment> GetEnrollment(int? id)
    {
        var enrollemnts = await _context.Enrollments
            .Include(e => e.Student)
            .Include(e => e.Subject)
            .ToListAsync();

        return enrollemnts.Find(s => s.IdEnrollment == id);
    }

    public async Task<Student> AddStudent(Student newStudent)
    {
        _context.Students.Add(newStudent);
        await _context.SaveChangesAsync();

        return newStudent;
    }

    public async Task<Subject> AddSubject(Subject newSubject)
    {
        _context.Subjects.Add(newSubject);
        await _context.SaveChangesAsync();

        return newSubject;
    }

    public async Task<Enrollment> AddEnrollment(Enrollment newEnrollment)
    {
        _context.Enrollments.Add(newEnrollment);
        await _context.SaveChangesAsync();

        return newEnrollment;
    }

    public async Task<Student> DeleteStudent(int? id)
    {
        var studentToDelete = await _context.Students.FindAsync(id);
        _context.Students.Remove(studentToDelete);
        await _context.SaveChangesAsync();

        // int g = 0;

        _context.Remove(studentToDelete);

        return null;
    }

    public async Task<Subject> DeleteSubject(int? id)
    {
        var subjectToDelete = _context.Subjects.Find(id);
        _context.Subjects.Remove(subjectToDelete);
        await _context.SaveChangesAsync();

        _context.Remove(subjectToDelete);

        return null;

    }

    public async Task<Enrollment> DeleteEnrollment(int? id)
    {
        var enrollmentToDelete = _context.Enrollments.Find(id);
        _context.Enrollments.Remove(enrollmentToDelete);
        await _context.SaveChangesAsync();

        _context.Remove(enrollmentToDelete);

        return null;
    }

    public async Task<Student> EditStudent(Student studentToEdit)
    {
        _context.Update(studentToEdit);
        await _context.SaveChangesAsync();

        return null;
    }

    public async Task<Subject> EditSubject(Subject subjectToEdit)
    {
        _context.Update(subjectToEdit);
        await _context.SaveChangesAsync();

        return null;
    }

    public async Task<Enrollment> EditEnrollment(Enrollment enrollmentToEdit)
    {
        _context.Update(enrollmentToEdit);
        await _context.SaveChangesAsync();

        return null;
    }
}

Database context

public class SchoolDbContext : DbContext
{
    public SchoolDbContext(DbContextOptions options) : base(options)
    {
    }

    public DbSet<Student> Students { get; set; }
    public DbSet<Subject> Subjects { get; set; }
    public DbSet<Enrollment> Enrollments { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Student>().ToTable("Student", schema: "School");
        modelBuilder.Entity<Subject>().ToTable("Course", schema: "School");
        modelBuilder.Entity<Enrollment>().ToTable("Enrollment", schema: "School");

        base.OnModelCreating(modelBuilder);
    }
}

So here your DbContext which defines database is injected by SqlDbLayer and implements common tasks for all databases defined in IDbLayer, and you could define different database contexts and use SqlDbLayer to implement them. You might also want to inject the IDbLayer and SqlDbLayer in your Startup, for example:

public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<SchoolDbContext>
            (options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        services.AddScoped<IDbLayer, SqlDbLayer>();

        services.AddControllersWithViews();
    }

And you would consume your database contexts through IDbLayer interface you defined. For example:

public class StudentsController : Controller
{
    public readonly IDbLayer _context;
    public StudentsController(IDbLayer context)
    {
        _context = context;
    }

    public async Task<IActionResult> Index(string studyName, string searchString)
    {
        var students = await _context.GetStudents();

        var studies = await _context.GetStudies();

        IQueryable<string> studyQuery = (from s in students
                                         orderby s.Study.Name
                                         select s.Study.Name).AsQueryable();

        if (!string.IsNullOrEmpty(searchString))
        {
            students = students.Where(s => s.IndexNumber == searchString).ToList();
        }

        if (!string.IsNullOrEmpty(studyName))
        {
            students = students.Where(s => s.Study.Name == studyName).ToList();
        }


        ViewBag.Students = students;

        ViewBag.Studies = studies;

        var studentStudyView = new StudentStudyViewModel
        {
            StudentsList = students.ToList(),
            Studies = new SelectList(studyQuery.Distinct().ToList()),
            StudentsCount = students.Count()
        };
        
        // int g = 0;
        
        return View(studentStudyView);
    }

    public async Task<IActionResult> Details(int? id)
    {
        if(id == null)
        {
            return NotFound();
        }
        var student = await _context.GetStudent(id);

        if(student == null)
        {
            return NotFound();
        }

        return View(student);
    }

    public async Task<IActionResult> Create()
    {
        ViewBag.Studies = await _context.GetStudies();

        return View();
    }

    [HttpPost]
    public async Task<IActionResult> Create(Student studentToAdd)
    {
        if (!ModelState.IsValid)
        {
            return View("Create", studentToAdd);
        }

        await _context.AddStudent(studentToAdd);

        //int g = 0;

        return RedirectToAction("Create");  // 302
    }

I hope my point was actually related to your question.

Miraziz
  • 509
  • 7
  • 15
  • This requires Students, Subject and Enrollment classes to be known at compile time and hard coded to application. I want to use ˇIStudentsˇ, ˇISubjectˇ and ˇIEnrollmentˇ **interfaces** instead of classes so that their implementations can have additional properties. How to scaffold Poco classes as interfaces? – Andrus Jan 23 '21 at 19:18