1
\$\begingroup\$

Let's suppose I have 2 repositories:

  • first is responsible for creating job and return jobId
  • second is responsible for creating log and take jobId as argument

My goal is to:

  • save Job and Log simultaneously
  • prevent situation when in case of error only Job would be saved without Log

What is the most recommended way to get desired result?

I prepared 3 cases which came to my mind but if you see better alternative please share it.

  • option 1 (getting result and save changes in controller)
public class JobRepository : IJobRepository
{
    private readonly Context _context;
    
    public JobRepository(Context context)
    {
        _context = context;
    }

    public Guid CreateJob()
    {
        var job = new Job { Id = Guid.NewGuid() };
        _context.Jobs.Add(job);
        return job.Id;
    }
}
// ...
public class LogRepository : ILogRepository
{
    private readonly Context _context;
    
    public LogRepository(Context context)
    {
        _context = context;
    }

    public void CreateLog(Guid id)
    {
        var log = new Log { Jobid = id };
        _context.Logs.Add(log);
    }
}
// ...
public class JobsController : Controller
{
    private readonly Context _context;
    private readonly IJobRepository _jobRepository;
    private readonly ILogRepository _logRepository;

    public JobsController(Context context, JobRepository jobRepository, ILogRepository logRepository)
    {
        _context = context;
        _jobRepository = jobRepository;
        _logRepository = logRepository
    }

    [HttpGet]
    public IActionResult Create()
    {
        return View();
    }

    [HttpPost]
    public IActionResult Create()
    {
        var id = _jobRepository.CreateJob();
        _logRepository.CreateLog(id);
        _context.SaveChanges();
        return RedirectToAction("Index");
    }
}
  • option 2 (inject one repository into another)
public class JobRepository : IJobRepository
{
    private readonly Context _context;
    private readonly ILogRepository _logRepository;
    
    public JobRepository(Context context, ILogRepository logRepository)
    {
        _context = context;
    }

    public void CreateJob()
    {
        var job = new Job { Id = Guid.NewGuid() };
        _context.Jobs.Add(job);
        _logRepository.CreateLog(job.Id);
        _context.SaveChanges();
    }
}
// ...
public class LogRepository : ILogRepository
{
    private readonly Context _context;
    
    public LogRepository(Context context)
    {
        _context = context;
    }

    public void CreateLog(Guid id)
    {
        var log = new Log { Jobid = id };
        _context.Logs.Add(log);
    }
}
// ...
public class JobsController : Controller
{
    private readonly IJobRepository _jobRepository;

    public JobsController(JobRepository jobRepository)
    {
        _jobRepository = jobRepository;
    }

    [HttpGet]
    public IActionResult Create()
    {
        return View();
    }

    [HttpPost]
    public IActionResult Create()
    {
         _jobRepository.CreateJob();
        return RedirectToAction("Index");
    }
}
  • option 3 (do not use context in controller but declare Save method in each repo)
public class JobRepository : IJobRepository
{
    private readonly Context _context;
    
    public JobRepository(Context context)
    {
        _context = context;
    }

    public Guid CreateJob()
    {
        var job = new Job { Id = Guid.NewGuid() };
        _context.Jobs.Add(job);
        return job.Id;
    }

    public void Save()
    {
        _context.SaveChanges();
    }
}
// ...
public class LogRepository : ILogRepository
{
    private readonly Context _context;
    
    public LogRepository(Context context)
    {
        _context = context;
    }

    public void CreateLog(Guid id)
    {
        var log = new Log { Jobid = id };
        _context.Logs.Add(log);
    }

    public void Save()
    {
        _context.SaveChanges();
    }
}
// ...
public class JobsController : Controller
{
    private readonly IJobRepository _jobRepository;
    private readonly ILogRepository _logRepository;

    public JobsController(JobRepository jobRepository, ILogRepository logRepository)
    {
        _jobRepository = jobRepository;
        _logRepository = logRepository
    }

    [HttpGet]
    public IActionResult Create()
    {
        return View();
    }

    [HttpPost]
    public IActionResult Create()
    {
        var id = _jobRepository.CreateJob();
        _logRepository.CreateLog(id);
        return RedirectToAction("Index");
    }
}
```
\$\endgroup\$
3
  • 2
    \$\begingroup\$ Unfortunately none of them is correct. If one of the save command fails then the other is not rolled back. So both changes should be tackled under the same DbContext to be able to participate in a same transaction. \$\endgroup\$ Nov 23, 2021 at 15:55
  • \$\begingroup\$ plus, it seems you're using EF as SaveChanges exists in DbContext, if that's true, then the best approach would be to override SaveChanges to log entities changes. \$\endgroup\$
    – iSR5
    Nov 23, 2021 at 16:25
  • 2
    \$\begingroup\$ This question seems better suited to StackOverflow or SoftwareEngineering.SE. This isn't a request for a code review. \$\endgroup\$
    – Flater
    Nov 24, 2021 at 13:01

1 Answer 1

2
\$\begingroup\$

This looks like you have organised your database code in such a way that you have one repository per table. With this pattern you'll eventually always reach the point you are at right now. Just create a single repository by database with all the queries you need to execute. You'll then be able to create proper transactions and handle inserts without scratching your head how to keep the data consistent.

Yet another option would be to add a trigger to the job-table at the database level and handle writing the log there but I'm not a fan of this approach.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.