如何用Elixir Ecto查询排除软删除关联表的预加载数据?
解决Ecto预加载软删除关联Location的问题
这个问题我之前也遇到过,核心是预加载关联的时候没有把软删除的UserLocation记录过滤掉,导致对应的Location依然被加载出来。下面给你两种解决方案,都能保证始终返回User(哪怕没有关联的Location),同时只加载未被软删除的关联数据:
方法一:在查询时显式过滤预加载的关联表
如果你只是在特定查询中需要这个逻辑,可以直接在预加载的时候给UserLocation添加过滤条件,确保只取deleted_at为nil的记录:
import Ecto.Query # 方式1:直接预加载关联表并过滤 query = from u in User, preload: [ user_locations: ul from ul in UserLocation, where: is_nil(ul.deleted_at), preload: :location ] Repo.all(query)
如果你的User通过through关联直接指向Location(比如定义了has_one :location, through: [:user_locations, :location]),可以简化成:
query = from u in User, preload: [ location: ul from ul in UserLocation, where: is_nil(ul.deleted_at) ] Repo.all(query)
这种方式的好处是灵活,只在当前查询生效,不会影响其他地方的关联使用。
方法二:在Schema中定义默认过滤的关联
如果希望所有查询默认都排除软删除的UserLocation,可以在User的Schema里定义一个带过滤条件的关联,替代原来的默认关联:
defmodule YourApp.User do use Ecto.Schema import Ecto.Changeset schema "users" do # 你的用户字段,比如name、email等 field :name, :string # 定义带软删除过滤的关联 has_many :active_user_locations, YourApp.UserLocation, where: [deleted_at: nil], on_delete: :nilify_all # 通过过滤后的关联指向Location has_one :active_location, through: [:active_user_locations, :location] timestamps() end # ... 其他代码(changeset等) end
之后查询的时候,直接预加载这个新定义的关联即可:
User |> preload(:active_location) |> Repo.all()
这种方式的优势是一劳永逸,所有使用active_location关联的地方都会自动排除软删除的记录。如果偶尔需要查询包含已删除关联的场景,再单独写查询逻辑即可。
关键注意点
- 确保软删除
UserLocation时,正确设置deleted_at字段为当前时间(比如用DateTime.utc_now()),而不是执行物理删除。 - 不管用哪种方法,主查询都是针对
User表,所以即使没有关联的Location,User记录依然会被完整返回,符合你的需求。
内容的提问来源于stack exchange,提问作者larskris
相关产品推荐
相关产品推荐

