Ecto 總結

来源:http://www.cnblogs.com/wang_yb/archive/2017/11/25/7897066.html
-Advertisement-
Play Games

ecto 簡介 ecto 相當於 elixir 的 ORM,但是得益於 elixir 語言,和傳統的 ORM 相比,更加簡潔和強大。 ecto 主要分為 4 部分: 1. Repo: 這是和真正資料庫交互的部分 2. Schema: 相當於是資料庫中表的定義,但不僅僅是定義 3. Changeset ...


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 文檔


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、面向介面: 通過介面約束對象的一些方法和屬性,屬於面向對象中的一部分。 更好的方式是「面向抽象」:先定義介面約束,再定義抽象類,在抽象類中實現公共方法,在進行具體實現。 總結:統一行為 2、面向對象: 通過封裝、繼承、多態更加有效的組織程式。 總結:提高復用 3、面向方面: 把業務的主邏輯和次邏 ...
  • 最近總是記不住表單提交的幾種方式,並且各種方式的適應場景也不知道,乾脆來總結一次,當再學習過程。 首先從最簡單的開始練手: 【1】、純form表單形式,無js和ajax ,提交路徑有action決定,方式由method決定,如果需要傳輸文件加上enctype 我的表單內容:兩個下拉選擇、一個文件選擇 ...
  • 本人學習.Net開發已有很長一段時間了,但平時疏於對知識系統化的歸納和總結,多次面試中屢屢碰壁。所以痛定思痛,決心把一些知識整理到博客中,以便自己瀏覽學習。園子中有很多好文和技術大牛分享的經驗。我會抄錄一些內容到我的博客,並標明出處。先謝過各位了。 ...
  • 通過ADO.NET技術,我們可以高效的完成客戶端同資料庫之間的數據訪問操作,便於我們在客戶端程式簡便高效的訪問以及獲取資料庫中的有用數據,同時也可以對資料庫中的數據進行更新,即可以完成客戶端與資料庫之間的雙向操作。本文簡單介紹如何在客戶端程式中利用ADO.NET技術來訪問以及使用資料庫中的數據。 A ...
  • 返回總目錄 本小節目錄 Introduce Foreign Method(引入外加函數) Introduce Local Extension(引入本地擴展) Introduce Foreign Method(引入外加函數) Introduce Local Extension(引入本地擴展) 7Int ...
  • 學習java之後,到企業的崗位 技術:java軟體開發工程師(中初級):技術一般; 高級工程師:技術高等; 技術架構師;技術頂級; 管理:項目經理;產品經理; 質詢:質詢顧問;銷售經理; 學會之後可以根據個人的愛好去從事相關的職位,但是不管是做哪一個都是需要技術的底子。希望能幫到你們。在這裡我也提醒 ...
  • 兩種用法介紹如下:1.range([start], stop[, step])返回等差數列。構建等差數列,起點是start,終點是stop,但不包含stop,公差是step。start和step是可選項,沒給出start時,從0開始;沒給出step時,預設公差為1。例如: 2.xrange([sta ...
  • 設計模式是對問題行之有效的解決方案,它其實是一種思想。 單例設計模式: 解決的問題:可以保證一個類在記憶體中只能有一個對象。(比如多個程式使用相同的配置信息對象時,就需要保證對象的唯一性) 如何保證唯一性:1、不允許其他程式用new創建類對象 2、在該類中創建一個本類實例 3、對外提供一個方法讓其他程 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...