0

I am working on an existing multi-user EF6 / SQL ASP.NET MVC 5 application (database first) that is having concurrency issues I need to address. The database contains a one-to-many relationship that is represented by a bridge table between the 'one' and 'many' tables.

[EDIT] It has been pointed out that a Bridge is not required in a 1-to-M structure. I'm aware of that. Please see the comment & response below.

One table

   OneTableId       (one relationship w BridgeTable)
   .....
   RowVersion       (not yet created)

Many table

   ManyTableId      (many relationship w BridgeTable)
   .....
   RowVersion       (not yet created)

Bridge table

   BridgeTableRowId
   OneTableId         (relationship w OneTable)
   ManyTableId        (relationship w ManyTable)

The bridging rows are established when a One entity and an arbitrary number of Many entities are passed to a long running process which is executed using a QueueBackgroundWorkItem on a SignalR Hub. The process creates a number of PDFs and Emails them.

The creation of the bridge records is currently the only database impact during the long Running Process, which is initiated like this:

   using (Entities db = new Entities())
   {
       var oneTableEntity = db.oneTable.SingleOrDefault(s => s.OneTableId == RequestedOneId);
       var manyTableEntities = db.manyTable.Where(s => RequestedIdArray.Contains(s.ManyTableId).ToList();
   }

   HostingEnvironment.QueueBackgroundWorkItem(ct => _DoLongRunningProcess(OneTableEntity, ManyTableEntities).ContinueWith(x =>
   {
       // cleanup and SignalR notifications
   }

The LRP (handled in a separate class) instantiates the separate Data Contexts and Data Streams(memory and file) it needs to complete its chores.

The system is currently not transacted but I'm adding a Transaction that is only Completed if the PDFs are successfully created and emailed.

I need to handle concurrency such that if a user selects and submits a OneTable or ManyTable entity that is already being handled in the Long Running Process, we'll get an exception to be handled.

Questions:

  • Could I simply place a unique constraint over the OneTableId-ManyTableId columns in the Bridge table and just catch that exception OR should I implement a Row Versioning system? I'm leery about just letting the DB complain based on a Unique Index Constraint, but not clear about the impact of the LRP with its separate Data Contexts.

  • If I transact the LRP so that Bridge Table row commitment is dependent upon the emails being sent, what level of isolation should I use, given the concurrency issue?

  • If I use the Row Versioning approach:

    1. Should the assignment of BridgeTable records trigger EF6 to increment RowVersions in OneTable and ManyTable or do I need to somehow manually check entity RowVersions on entry to (inside) the LRP?
    2. Do I need to wrap the entire LRP in the outer Data Context (using()) in order for EF6 to handle this correctly?

Please note that the overall design for this app is already in place and I need to minimize changes while addressing the Concurrency issue.

Hope I've been clear enough, happy to answer any questions that would help :-)

Thanks for any guidance.

Serexx
  • 1,232
  • 1
  • 15
  • 32
  • Why do you even have a "bridge" table for a 1-to-many relationship?? Those association tables are really **only** needed if you have a many-to-many relationship..... – marc_s Dec 28 '20 at 09:19
  • Yes, I know :-) but it is what it is. I'm doing pro-bono work for a small charity and I'm loath to start redesigning things unless forced to, lest I run out of time and leave them stuck in the middle. The story above is simplified but at one time it was going to be m-to-m but ideas changed & the structure didn't. There is some possibility they'll change their minds again at some point. – Serexx Dec 28 '20 at 20:08
  • It kind of makes sense to have this bridge table. It appears to function as a working queue so it looks to me like your unit of work should be the bridge record, not the one-to-many relation, the cross-multiplication of which appears to be the base of the bridge record. Depending on the business rules and expected behavior, one could look into the bridge table, reject the request and alert the user if a similar job is already queued or simply treat the unit of work as a repeat job (if the output is static) and ignore/discard it. Not enough details. – JuanR Dec 29 '20 at 05:26
  • @JuanR - thanks for that. focusing on the bridge table was what I had in mind when I was considering just letting SQL throw on a unique constraint violation, not worrying about row-version etc. Any thoughts as to whether that's a bad idea from an SQL perspective? I see what you mean about not enough info. I'll get more verbose if I dont solve it this week. BTW- saw your note re: Commodore... my first was a TRS80 with tape for storage :-) – Serexx Dec 30 '20 at 06:38
  • @Serexx: If I understand you well, the bridge table contains unique OneTableId/ManyTableId combinations, which appears to be a business rule, so it should be enforced in code, in my opinion. It doesn't hurt to have a DB constraint as well but what happens tomorrow when you switch databases for whatever reason? – JuanR Dec 30 '20 at 14:21
  • @Serexx: Also, the user may be selecting many relations through the interface but it looks like they should be processed individually (which would allow you to enforce the business rule in code). What you do next is up to your requirements. Do you fail the whole batch? Or can you just ignore the dup? Exclude the dup and inform the user? And yes, I also had a tape storage device! :-) – JuanR Dec 30 '20 at 14:22
  • JuanR - I doubt that would ever happen but nonetheless good point. Thanks for your thoughts :-) – Serexx Dec 30 '20 at 14:39

0 Answers0