How am I "supposed" to keep objects synced in WinForms when tracked by different dbContexts?

Viewed 90

I have a VB WinForms project, and we use Entity Framework 6 code-first. I know that in web apps it's ideal to limit the lifetime of dbContexts (which we use indirectly through repository classes), but I learned recently that you're supposed to keep a dbContext around for the lifetime of a form, generally, for WinForms projects.

So let's say I have class ClassA and class ClassB, and all ClassA objects have an optional ClassB object (and related "ClassBId" properties for EF6 to use as a foreign key). We have a form, ClassAForm, for users to edit and view ClassA objects, and this naturally displays a little info about its ClassB object; this ClassB should be exclusive to the ClassA, but might also be viewed on other forms. There is a button on this form to open another form, ClassBForm, that allows users to edit and view ClassB objects. ClassBForm isn't modal, and doesn't currently pass anything to the ClassAForm that spawned it, although ClassAForm does have listeners for events in ClassBForm.

So from my understanding, ClassAForm would get its own long-lived dbContext object that it retains for the form lifetime; ClassBForm would get its own dbContext object. So if I make changes to the ClassB object in ClassBForm's dbContext, what is the ideal way to see those changes in ClassAForm's dbContext's version of that ClassB object, where the ClassB is likely a navigation property of a ClassA?

I can think of a few solutions to this problem but I'm not sure what is the best practice; it's also possible (and I hope someone will mention it) that my entire understanding or framing is incorrect.

(Possible) Solutions I've thought of:

  1. Ignore the MS advice, and only keep dbContexts alive for specific DB interactions; refresh the dbContext(s) on every update event and form load, like it's a web app
  2. Pass the same dbContext(s) from form to form (in the constructor of my repository classes, in my case) and only dispose it rarely (not ideal- database gets updated by a couple of services)
  3. Manually attach/detach and mark updates as updated in my data layer
  4. Pass the updated objects by event to other forms, and update the objects manually in each context (I assume this would run into trouble when I need to save changes?)
1 Answers

I think this scenario is one of the worst for ORMs in general, as they tend to interfere with pretty much every operation you do on your domain entity objects and produce either errors or unwanted changes.

When faced a similar problem, I can think of two different possible solutions:

Make each form have its own context and never exchange objects between them

This way, when you open ClassBForm you don't pass a ClassB, but an Id only, and the new form uses its own context to load the object. Then when saving, again using its own private copy, when you send a notification event you notify of the Id only and ClassAForm uses its context to reload again the modified ClassB. Both forms never exchange data other than identifiers and remain as separate as possible.

Don't use entities in the forms at all

Implement DTOs/viewmodels/whatever to display and modify data**. In the data layer you load using your ORM, then return another class (say ClassADTO and ClassBDTO) which will contain all the needed information for the operation of its respective form. While you add an extra step and will need to define classes tailored for each form, you ensure that the ORM is left out of the way, working only in the DAOs where it belongs. DTOs can be passed along safely without anything giving unwanted results, as they're POCOs only. This way the contexts only live for the duration of one query, much like in web pages.

About your proposed solutions in the questions, I think none of them would fully work or at least will need careful attention to edge cases bugs:

Ignore the MS advice, and only keep dbContexts alive for specific DB interactions; refresh the dbContext(s) on every update event and form load, like it's a web app

This works fine as long as you don't want to use lazy load or save changes (where it gives an exception because a disposed context). You may need to manually attach objects to a context whenever you want to use them, and be aware of every lazy loaded property that may blow up.

Pass the same dbContext(s) from form to form (in the constructor of my repository classes, in my case) and only dispose it rarely (not ideal- database gets updated by a couple of services)

This is pretty much the opposite of the ideal situation. By tying up both form together changes in one form affect the other, you can't edit both objects independently, and most important, you end up saving changes in both places when you want to save in only one. All the ORM features that are supposed to help end up biting you.

Manually attach/detach and mark updates as updated in my data layer

This is doable, but it's a loot of work to keep track of the state of each object and when to attach/detach. Detached objects are subject to the same lazy load problems as with short-lived contexts. It's just too easy to forget to attach an object and silently end up with an inconsistent update. Seems like a recipe for hard to reproduce bugs to me.

Pass the updated objects by event to other forms, and update the objects manually in each context (I assume this would run into trouble when I need to save changes?)

It'll work differently depending on how the context are arranged. For shared contexts, you end up with the very same objects in both forms, and with separate ones you end up with unwanted updates at the next save, potentially overwriting changes. That's why I suggest just reloading with the proper context and ignore the data coming from the other form.

Related