ecto 简介

ecto 相当于 elixir 的 ORM,但是得益于 elixir 语言,和传统的 ORM 相比,更加简洁和强大。

ecto 主要分为 4 部分:

  1. Repo: 这是和真正数据库交互的部分
  2. Schema: 相当于是数据库中表的定义,但不仅仅是定义
  3. Changeset:Schema 到真正数据库之间的转换层
  4. Query:elixir 风格的数据查询方式

ecto 连接数据库(以 sqlite 为例)

连接到真实的数据库,需要相应的驱动,下面以 sqlite 为例,配置数据库的连接

创建工程

也就是一般的 elixir 工程

mix new ecto_sample

引入 ecto 和数据库驱动

引入 ecto 和 sqlite 相关的 packages (mix.exs)

defp deps do
[
{:sqlite_ecto2, "~> 2.2"},
{:ecto, "~> 2.1"}
]
end

配置好后通过命令行安装:

mix deps.get

创建数据库

在 lib/ecto_sample 下创建文件 repo.ex,文件内容如下:

defmodule EctoSample.Repo do
use Ecto.Repo,
otp_app: :ecto_sample,
adapter: Sqlite.Ecto2
end

配置数据库连接,config/config.esx

config :ecto_sample, ecto_repos: [EctoSample.Repo]  # 配置 repo

config :ecto_sample, EctoSample.Repo,   # 配置驱动和数据库位置,这里用的 sqlite 数据,比较简单
adapter: Sqlite.Ecto2,
database: "ecto_sample.db"

sqlite 数据库只要配置数据库文件的位置即可,如果是 postgres 之类的关系数据库,需要配置主机,用户名/密码 等

config :ecto_sample, EctoSample.Repo,
adapter: Ecto.Adapters.Postgres,
database: "ecto_sample_repo",
username: "user",
password: "pass",
hostname: "localhost"

配置完成后,创建数据库

mix ecto.create

成功执行的话,能看到在工程根目录下多了个 ecto_sample.db 文件

创建表

创建 schema users, lib/repo.ex 文件中追加

其中 changeset 是在更新数据时用来验证数据有效性或者转换数据用的,不是必须的

defmodule EctoSample.User do
use Ecto.Schema schema "users" do
field :username, :string
field :password, :string
field :email, :string
field :age, :integer
end def changeset(user, params \\ %{}) do
user
|> cast(params, [:username, :password, :email, :age])
|> validate_required([:username, :password])
end
end

创建建表的 migration

$ mix ecto.gen.migration create_user
Compiling 2 files (.ex)
Generated ecto_sample app
* creating priv/repo/migrations
* creating priv/repo/migrations/20171123012930_create_user.exs

参照 users 的 schema 编辑 priv/repo/migrations/20171123012930_create_user.exs

defmodule EctoSample.Repo.Migrations.CreateUser do
use Ecto.Migration def change do
create table(:users) do
add :username, :string
add :password, :string
add :email, :string
add :age, :integer
end
end
end

创建表

$ mix ecto.migrate

09:33:40.257 [info]  == Running EctoSample.Repo.Migrations.CreateUser.change/0 forward

09:33:40.257 [info]  create table users

09:33:40.259 [info]  == Migrated in 0.0s

登入数据库验证

用 sqlite3 的客户端登入数据库查看情况,下面使用的是命令行方式

$ sqlite3 ecto_sample.db
SQLite version 3.16.2 2017-01-06 16:32:41
Enter ".help" for usage hints.
sqlite> .fullschema
CREATE TABLE IF NOT EXISTS "schema_migrations" ("version" BIGINT PRIMARY KEY, "inserted_at" NAIVE_DATETIME);
CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER PRIMARY KEY, "username" TEXT, "password" TEXT, "email" TEXT, "age" INTEGER);
/* No STAT tables available */
sqlite> .exit

可以看出:

  1. 除了创建了 users 表,ecto 还创建了 schema_migrations 用来管理每次的 migration
  2. 默认创建了 id 主键,类型是 INTEGER,如果要改成 uuid 创建时要明确指定

ecto 单表操作

演示示例

通过一个例子来演示对 users 表的 CURD

示例流程: 增加一个记录 -> 查询这条记录 -> 修改这条记录 -> 查询新的记录 -> 删除这条记录 -> 再次查询为空

  1. 新增记录

    def add_user(username, password, email \\ "", age \\ 0) do
    user = EctoSample.User.changeset(%EctoSample.User{}, %{:username => username, :password => password,
    :email => email, :age => age}) case EctoSample.Repo.insert(user) do
    {:ok, _} -> Logger.info "insert successfully"
    {:error, _} -> Logger.error "insert failed"
    end
    end
  2. 查询记录

    import Ecto.Query, only: [from: 2]
    
    q = from u in EctoSample.User,
    where: u.username == ^username EctoSample.Repo.all(q) |> Enum.map(fn (u) ->
    Logger.info "==============================="
    Logger.info "username: " <> u.username
    Logger.info "password: " <> u.password
    if u.email do
    Logger.info "email: " <> u.email
    end
    Logger.info "age: " <> Integer.to_string u.age
    Logger.info "==============================="
    end)
  3. 修改记录

    def change_user(id, params \\ %{}) do
    u = EctoSample.Repo.get!(EctoSample.User, id)
    changeset = EctoSample.User.changeset(u, params) EctoSample.Repo.update(changeset)
    end
  4. 删除记录

    def delete_user(id) do
    u = EctoSample.Repo.get!(EctoSample.User, id)
    EctoSample.Repo.delete(u)
    |> case do
    {:ok, _} -> Logger.info "delete successfully"
    {:error, _} -> Logger.error "delete failed"
    end
    end

测试步骤

$ iex -S mix
Erlang/OTP 20 [erts-9.1] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:10] [hipe] [kernel-poll:false] Interactive Elixir (1.5.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> EctoSample.start
{:ok, #PID<0.182.0>}
iex(2)> EctoSample.add_user("a", "b", "c", 10) 22:45:22.570 [info] insert successfully
iex(3)> EctoSample.query_user("a") 22:45:29.370 [info] ===============================
[:ok] 22:45:29.370 [info] username: a 22:45:29.370 [info] password: b 22:45:29.370 [info] email: c 22:45:29.370 [info] age: 10 22:45:29.370 [info] =============================== iex(4)> EctoSample.change_user(1, %{:username => "change", :age => 20})
{:ok,
%EctoSample.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, age: 20,
email: "c", id: 1, password: "b", username: "change"}}
iex(5)> EctoSample.query_user("change") 22:48:47.644 [info] =============================== 22:48:47.644 [info] username: change 22:48:47.644 [info] password: b 22:48:47.644 [info] email: c 22:48:47.644 [info] age: 20 22:48:47.644 [info] ===============================
iex(6)> EctoSample.delete_user(1) 22:50:17.848 [info] delete successfully
iex(7)> EctoSample.query_user("change") []

ecto 表关系操作

表关系只有 3 种,1:1,1:N,M:N。 先在现有的表基础上增加 3 张表

增加和 User 关联的表

  1. table 定义

    defmodule EctoSample.Schema.User do
    use Ecto.Schema
    import Ecto.Changeset schema "users" do
    field :username, :string
    field :password, :string
    field :email, :string
    field :age, :integer has_one :computer, EctoSample.Schema.Computer
    belongs_to :company, EctoSample.Schema.Company
    many_to_many :friends, EctoSample.Schema.Friend, join_through: "users_friends"
    end def changeset(user, params \\ %{}) do
    user
    |> cast(params, [:username, :password, :email, :age])
    |> validate_required([:username, :password])
    end
    end # 1 : 1 computer - user
    defmodule EctoSample.Schema.Computer do
    use Ecto.Schema
    import Ecto.Changeset schema "computers" do
    field :hostname, :string
    field :ip, :string belongs_to :user, EctoSample.Schema.User
    end def changeset(computer, params \\ %{}) do
    computer
    |> cast(params, [:hostname, :ip])
    |> validate_required([:hostname, :ip])
    end
    end # 1 : N company - user
    defmodule EctoSample.Schema.Company do
    use Ecto.Schema
    import Ecto.Changeset schema "companys" do
    field :comp_name, :string
    field :addr, :string has_many :users, EctoSample.Schema.User
    end def changeset(company, params \\ %{}) do
    company
    |> cast(params, [:comp_name, :addr])
    |> validate_required([:comp_name, :addr])
    end
    end # M : N friend - user
    defmodule EctoSample.Schema.Friend do
    use Ecto.Schema
    import Ecto.Changeset schema "friends" do
    field :frient_name, :string
    field :phone, :string many_to_many :users, EctoSample.Schema.User, join_through: "users_friends"
    end def changeset(friend, params \\ %{}) do
    friend
    |> cast(params, [:friend_name, :phone])
    |> validate_required([:friend_name, :phone])
    end
    end

    原先的 user 表做了一些修改,增加了一些关联属性,另外增加了 3 张表,和 user 表的关系分别是:

    • 1:1 user : computer
    • 1:N company : user
    • M:N friend : user
  2. table migration

    创建各个表的 migration

    mix ecto.gen.migration create_company
    mix ecto.gen.migration create_computer
    mix ecto.gen.migration create_friend
    mix ecto.gen.migration create_users_friends

    migration 的代码参见:https://gitee.com/wangyubin/ecto_sample.git 整个示例工程的代码都在其中

1:1 示例

def one_to_one() do
import Ecto.Changeset
alias EctoSample.Schema.User
alias EctoSample.Schema.Computer # insert
computer = %Computer{}
|> Computer.changeset(%{:hostname => "debian", :ip => "192.168.0.100"})
|> EctoSample.Repo.insert! user = %User{}
|> User.changeset(%{:username => "wyb", :password => "123"})
|> put_assoc(:computer, computer)
|> EctoSample.Repo.insert! # query
u = EctoSample.Repo.get!(User, user.id) |> EctoSample.Repo.preload(:computer)
Logger.info "==============================="
Logger.info "id: " <> Integer.to_string(u.id)
Logger.info "username: " <> u.username
Logger.info "password: " <> u.password
Logger.info "computer: *********"
Logger.info "hostname: " <> u.computer.hostname
Logger.info "ip: " <> u.computer.ip
Logger.info "==============================="
end

1:N 示例

def one_to_many() do
alias EctoSample.Schema.User
alias EctoSample.Schema.Company # insert
user1 = EctoSample.Repo.insert!(%User{:username => "wyb001", :password => "123"})
user2 = EctoSample.Repo.insert!(%User{:username => "wyb002", :password => "321"})
company = EctoSample.Repo.insert!(%Company{:comp_name => "yunbim", :addr => "D216", :users => [user1, user2]}) # TODO 这里是根据 user 来新建 company,也可以 根据已有的 company 来创建 user # query
c = EctoSample.Repo.get!(Company, company.id) |> EctoSample.Repo.preload(:users)
Logger.info "==============================="
Logger.info "id: " <> Integer.to_string(c.id)
Logger.info "comp_name:" <> c.comp_name
Logger.info "addr : " <> c.addr
Logger.info "users: *********"
c.users |> Enum.map(fn (u) ->
Logger.info "id: " <> Integer.to_string(u.id)
Logger.info "username: " <> u.username
Logger.info "password: " <> u.password
end)
Logger.info "==============================="
end

M:N 示例

def many_to_many() do
alias EctoSample.Schema.User
alias EctoSample.Schema.Friend
import Ecto.Changeset # insert
user1 = EctoSample.Repo.insert!(%User{:username => "wyb001", :password => "123"})
user2 = EctoSample.Repo.insert!(%User{:username => "wyb002", :password => "321"})
friend1 = EctoSample.Repo.insert!(%Friend{:friend_name => "f001", :phone => "123456789"})
friend2 = EctoSample.Repo.insert!(%Friend{:friend_name => "f002", :phone => "987654321"}) EctoSample.Repo.get!(User, user1.id)
|> EctoSample.Repo.preload(:friends)
|> change
|> put_assoc(:friends, [friend1, friend2])
|> EctoSample.Repo.update!() EctoSample.Repo.get!(User, user2.id)
|> EctoSample.Repo.preload(:friends)
|> change
|> put_assoc(:friends, [friend1])
|> EctoSample.Repo.update!() # query
f1 = EctoSample.Repo.get!(Friend, friend1.id) |> EctoSample.Repo.preload(:users)
Logger.info "==============================="
Logger.info "id: " <> Integer.to_string(f1.id)
Logger.info "friend_name:" <> f1.friend_name
Logger.info "phone: " <> f1.phone
Logger.info "users: *********"
f1.users |> Enum.map(fn (u) ->
Logger.info "id: " <> Integer.to_string(u.id)
Logger.info "username: " <> u.username
Logger.info "password: " <> u.password
end)
Logger.info "===============================" f2 = EctoSample.Repo.get!(Friend, friend2.id) |> EctoSample.Repo.preload(:users)
Logger.info "==============================="
Logger.info "id: " <> Integer.to_string(f2.id)
Logger.info "friend_name:" <> f2.friend_name
Logger.info "phone: " <> f2.phone
Logger.info "users: *********"
f2.users |> Enum.map(fn (u) ->
Logger.info "id: " <> Integer.to_string(u.id)
Logger.info "username: " <> u.username
Logger.info "password: " <> u.password
end)
Logger.info "==============================="
end

运行测试

$ mix ecto.drop
$ mix ecto.create
$ mix ecto.migrate $ iex -S mix
Erlang/OTP 20 [erts-9.1] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:10] [hipe] [kernel-poll:false] Interactive Elixir (1.5.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> EctoSample.start
{:ok, #PID<0.182.0>} iex(2)> EctoSample.one_to_one
11:00:27.800 [info] =============================== 11:00:27.800 [info] id: 1 11:00:27.800 [info] username: wyb 11:00:27.800 [info] password: 123 11:00:27.800 [info] computer: ********* 11:00:27.800 [info] hostname: debian 11:00:27.800 [info] ip: 192.168.0.100 11:00:27.800 [info] =============================== iex(3)> EctoSample.one_to_many
11:01:32.327 [info] =============================== 11:01:32.327 [info] id: 1 11:01:32.327 [info] comp_name:yunbim 11:01:32.327 [info] addr : D216 11:01:32.327 [info] users: ********* 11:01:32.327 [info] id: 2 11:01:32.327 [info] username: wyb001 11:01:32.327 [info] password: 123 11:01:32.327 [info] id: 3 11:01:32.327 [info] username: wyb002 11:01:32.327 [info] password: 321 11:01:32.327 [info] =============================== iex(4)> EctoSample.many_to_many
11:02:22.086 [info] =============================== 11:02:22.086 [info] id: 1 11:02:22.086 [info] friend_name:f001 11:02:22.086 [info] phone: 123456789 11:02:22.086 [info] users: ********* 11:02:22.086 [info] id: 4 11:02:22.086 [info] username: wyb001 11:02:22.086 [info] password: 123 11:02:22.086 [info] id: 5 11:02:22.086 [info] username: wyb002 11:02:22.086 [info] password: 321 11:02:22.086 [info] =============================== 11:02:22.087 [info] =============================== 11:02:22.087 [info] id: 2 11:02:22.087 [info] friend_name:f002 11:02:22.087 [info] phone: 987654321 11:02:22.087 [info] users: ********* 11:02:22.087 [info] id: 4 11:02:22.087 [info] username: wyb001 11:02:22.087 [info] password: 123 11:02:22.087 [info] ===============================

ecto 中的事务

ecto 中的事务,首先通过 Multi 来组装需要进行的数据库操作,然后通过 Repo.transaction 来执行

def trans() do
alias EctoSample.Schema.User
alias EctoSample.Schema.Computer
import Ecto.Query, only: [from: 2]
alias Ecto.Multi # insert user and computer in one transaction, insert all success
Logger.info "=========== before transaction==============="
EctoSample.Repo.one(from u in User, select: count(u.id)) |> Logger.info
EctoSample.Repo.one(from c in Computer, select: count(c.id)) |> Logger.info Multi.new()
|> Multi.insert(:user, %User{username: "m-user", password: "m-password"})
|> Multi.insert(:computer, %Computer{hostname: "host-name", ip: "0.0.0.0"})
|> EctoSample.Repo.transaction
|> case do
{:ok, _} -> Logger.info "multi success"
{:error, _} -> Logger.error "multi error"
end Logger.info "=========== after transaction==============="
EctoSample.Repo.one(from u in User, select: count(u.id)) |> Logger.info
EctoSample.Repo.one(from c in Computer, select: count(c.id)) |> Logger.info end

插入成功之后,User 和 Computer 表的数据都会增加

其他

除了上述内容之外,Ecto 还有其他的 API 辅助查询和各种数据操作,具体参见 Ecto 文档

Ecto 总结的更多相关文章

  1. Mac 上真正替换LiveWriter 的工具 - ecto

    Mac 上真正替换LiveWriter 的工具 - ecto 13年开始使用mac.而后想把 windows 替换到.一直在寻找LiveWriter 的工具,至今终于找到 我先感谢这位博主 http: ...

  2. Ecto中的changeset,schema,struct,map

    概要 schema changeset struct map 总结 概要 Ecto 中, 对数据库的操作中经常用到 4 个类型: schema changeset struct map 在 Ecto ...

  3. Mac下写博客工具ecto相关资料

    下载地址: https://www.macupdate.com/app/mac/8918/ecto 相关注册码: http://www.cnblogs.com/yssgyw/p/3284501.htm ...

  4. MAC 上的 Live Writer : ecto

    ecto 在这里: http://illuminex.com/ecto/ 它是一款 MAC 上的 live writer,对我来说,没有 writer 之类的工具,我就宁可不写博客。 测试插入图片 测 ...

  5. phoenix 使用activerecord模式框架ecto 访问数据库

    备注:    需要先安装mysql 以及phoenix 框架,测试使用的是docker 进行安装,具可以参考代码    1. 创建项目 mix phx.new first --database mys ...

  6. ecto注册码

    First name: Good Last name: Life Serial: ecto_at585-RP00-MP3F-VB8R-L82N-N0CC   First Name: The Last ...

  7. ecto使用

  8. phoenix 开发API系列(三)phoenix api 结合数据库

    概述 介绍了 api 的各种写法之后,下面介绍构建 api 时与数据库连接的方式. 注 下面使用的工程的完整代码已经公开在: http://git.oschina.net/wangyubin/phoe ...

  9. opencv 61篇

    (一)--安装配置.第一个程序 标签: imagebuildincludeinputpathcmd 2011-10-21 16:16 41132人阅读 评论(50) 收藏 举报  分类: OpenCV ...

随机推荐

  1. npm install -g @angular/cli@latest 失败

    一开始的ERROR信息是 error "@angular/compiler-cli" package was not properly installed 尝试方案二时又出现了以下 ...

  2. 阿里云redis映射到阿里云服务器

    参考文档:https://help.aliyun.com/document_detail/43850.html?spm=a2c4g.11186623.2.3.7yg9VH ECS Windows 篇 ...

  3. PHP 依赖注入和控制反转再谈(二)

    今天有个朋友看到yii2中介绍的依赖注入一头雾水,之前我写过类似的文章发给他看了,可能还没深入理解吧,这里我再通俗点描述下依赖注入的原理吧,尽可能滴说通俗易懂一点吧:先还是扯下概念性滴问题(概念问题我 ...

  4. linux的 .bashrc文件是干什么的?

    使用man bash命令查看到的联机帮助文件中的相关解释如下: .bashrc - The individual per-interactive-shell startup file. 这个文件主要保 ...

  5. laravel 数据库操作(表、字段)

    1)创建表(make:migration create),例如创建 articles php artisan make:migration create_articles_table --create ...

  6. Git学习(二)Git命令

    1.创建新的git仓库 初始化一个Git仓库,使用git init命令. 上图中我们新建了目录/home/honey/cxf,并进入目录cxf执行命令git init完成新git仓库的初始化,初始化成 ...

  7. ChartControl ViewType.Pie3D 用法测试

    效果图一. public partial class Form3 : Form { public Form3() { InitializeComponent(); } private void For ...

  8. canvas-9NonZeroAroundPrinciples.html

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  9. 【20190221】HTTP-URI与URL

    URI(uniform resource identifier)统一资源标识符是由某个协议方案表示的资源的定位标识符, URI 用字符串标识某一互联网资源,而 URL 表示资源的地点(互联网上所处的位 ...

  10. 花十分钟,让你变成AI产品经理

    花十分钟,让你变成AI产品经理 https://www.jianshu.com/p/eba6a1ca98a4 先说一下你阅读本文可以得到什么.你能得到AI的理论知识框架:你能学习到如何成为一个AI产品 ...