So my first choice would be exactly what @benwilson512 said - try to load all the data before even entering the changeset. I really like to have much changeset functions pure (without db access or other things) - this makes it quite easy and very fast to test them.
If loading it before is not an option for some reason, than I think updating data is fine, I’d probably go for something like:
update_in(changeset.data, &Repo.preload(&1, :foo))
This should be safe in a way that the data
field of a changeset is considered public and fine to update, unless you don’t change the data type (since we cache some things like types inside the changeset struct).