diff --git a/src/Model/DB.py b/src/Model/DB.py
new file mode 100644
index 0000000000000000000000000000000000000000..5206555a11a8c2389cee9abc67444b6ef63bb557
--- /dev/null
+++ b/src/Model/DB.py
@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+
+import os
+import sqlite3
+
+from pathlib import Path
+
+from tools import SQL
+from Model.Except import NotImplementedMethodeError
+
+# Top level model class
+class SQLModel(SQL):
+    _sub_classes = []
+
+    def _init_db_file(self, db, is_new = True):
+        exists = Path(db).exists()
+
+        if exists and is_new:
+            os.remove(db)
+
+        self._db = sqlite3.connect(db)
+        self._cur = self._db.cursor()
+
+        if is_new:
+            self._create()      # Create db
+            self._save()        # Save
+        else:
+            self._update()      # Update db scheme if necessary
+            self._load()        # Load data
+
+    def _create_submodel(self):
+        for cls in self._sub_classes:
+            requests = cls._sql_create(
+                lambda sql: self.execute(
+                    sql,
+                    fetch_one = False,
+                    commit = True
+                )
+            )
+
+    def _create(self):
+        raise NotImplementedMethodeError(self, self._create)
+
+    def _update_submodel(self, version):
+        for cls in self._sub_classes:
+            requests = cls._sql_update(
+                lambda sql: self.execute(
+                    sql,
+                    fetch_one = False,
+                    commit = True
+                ),
+                version
+            )
+
+    def _update(self):
+        raise NotImplementedMethodeError(self, self._update)
+
+    def _save_submodel(self, objs):
+        for obj in objs:
+            requests = obj._sql_save(
+                lambda sql: self.execute(
+                    sql,
+                    fetch_one = False,
+                    commit = True
+                )
+            )
+
+    def _save(self):
+        raise NotImplementedMethodeError(self, self._save)
+
+    @classmethod
+    def _load(cls, filename):
+        raise NotImplementedMethodeError(cls, cls._load)
+
+# Sub model class
+class SQLSubModel(object):
+    _sub_classes = []
+
+    def _sql_format(self, value):
+        # Replace ''' by ''' to preserve SQL injection
+        if type(value) == str:
+            value = value.replace("'", "'")
+        return value
+
+    @classmethod
+    def _create_submodel(cls, execute):
+        for sc in cls._sub_classes:
+            sc._sql_create(execute)
+
+    @classmethod
+    def _sql_create(cls, execute):
+        """Create data base scheme
+
+        Args:
+            execute: Function to exec SQL resquest
+
+        Returns:
+            Return true, otherelse false if an issue appear
+        """
+        raise NotImplementedMethodeError(cls, cls._sql_create)
+
+    @classmethod
+    def _update_submodel(cls, execute, version):
+        for sc in cls._sub_classes:
+            sc._sql_update(execute, version)
+
+    @classmethod
+    def _sql_update(cls, execute, version):
+        """Update data base scheme
+
+        Args:
+            execute: Function to exec SQL resquest
+            version: Current database version
+
+        Returns:
+            Return true, otherelse false if an issue appear
+        """
+        raise NotImplementedMethodeError(cls, cls._sql_update)
+
+    @classmethod
+    def _sql_load(cls, execute, data = None):
+        """Load instance of this class from SQL data base
+
+        Args:
+            execute: Function to exec SQL request
+            data: Optional data for the class constructor
+
+        Returns:
+            Return new instance of class
+        """
+        raise NotImplementedMethodeError(cls, cls._sql_load)
+
+    def _save_submodel(self, execute):
+        for sc in self._sub_classes:
+            sc._sql_update(execute)
+
+    def _sql_save(self, execute, data = None):
+        """Save class data to data base
+
+        Args:
+            execute: Function to exec SQL resquest
+            data: Optional additional information for save
+
+        Returns:
+            Return true, otherelse false if an issue appear during
+            save
+        """
+        raise NotImplementedMethodeError(self, self._sql_save)
diff --git a/src/Model/Except.py b/src/Model/Except.py
index 8480e3ee953401902b797aff59cc2a23413db94b..ad311666c4c1d4eec651ee316bce82edb4695df5 100644
--- a/src/Model/Except.py
+++ b/src/Model/Except.py
@@ -70,7 +70,7 @@ class NotImplementedMethodeError(ExeceptionWithMessageBox):
             f" '{self.func.__name__}' " +
             _translate("Exception", "not implemented") +
             _translate("Exception", "for class") +
-            f" '{self.obj.__class__}'"
+            f" '{self.obj.__class__ if self.obj.__class__ != type else self.obj}'"
         )
 
     def header(self):
diff --git a/src/Model/Geometry/PointXYZ.py b/src/Model/Geometry/PointXYZ.py
index 5f99f67bffd276d4d1b7023e17c0010913af7460..9321d5bf98ef645fac5c2000b22dcec3f9106a21 100644
--- a/src/Model/Geometry/PointXYZ.py
+++ b/src/Model/Geometry/PointXYZ.py
@@ -3,9 +3,12 @@
 from math import dist
 import numpy as np
 
+from Model.DB import SQLSubModel
 from Model.Geometry.Point import Point
 
-class PointXYZ(Point):
+class PointXYZ(Point, SQLSubModel):
+    _sub_classes = []
+
     def __init__(self, x:float = 0.0, y:float = 0.0, z:float = 0.0,
                  name:str = "", status = None):
         super(PointXYZ, self).__init__(name=name, status=status)
@@ -14,6 +17,36 @@ class PointXYZ(Point):
         self._y = float(y)
         self._z = float(z)
 
+    @classmethod
+    def _sql_create(cls, execute):
+        execute("""
+          CREATE TABLE geometry_pointXYZ(
+            id INTEGER NOT NULL PRIMARY KEY,
+            name TEXT,
+            x INTEGER NOT NULL,
+            y INTEGER NOT NULL,
+            z INTEGER NOT NULL,
+            profile INTEGER NOT NULL,
+            FOREIGN KEY(profile) REFERENCES profileXYZ(id)
+          )
+        """)
+
+        cls._create_submodel(execute)
+        return True
+
+    @classmethod
+    def _sql_update(cls, execute, version):
+        cls._update_submodel(execute, version)
+        return True
+
+    @classmethod
+    def _sql_load(cls, execute, data = None):
+        return None
+
+    def _sql_save(self, execute, data = None):
+        return True
+
+
     @classmethod
     def from_data(cls, header, data):
         point = None
diff --git a/src/Model/Geometry/ProfileXYZ.py b/src/Model/Geometry/ProfileXYZ.py
index 08cf2cf4c7b3f4716eb8f8101d2ae248bef32ffd..2013dea31808379f40f3a82c81c3b9fad5a70166 100644
--- a/src/Model/Geometry/ProfileXYZ.py
+++ b/src/Model/Geometry/ProfileXYZ.py
@@ -5,12 +5,17 @@ from typing import List
 
 from tools import timer
 
+from Model.DB import SQLSubModel
 from Model.Except import ClipboardFormatError
 from Model.Geometry.Profile import Profile
 from Model.Geometry.PointXYZ import PointXYZ
 from Model.Geometry.Vector_1d import Vector1d
 
-class ProfileXYZ(Profile):
+class ProfileXYZ(Profile, SQLSubModel):
+    _sub_classes = [
+        PointXYZ,
+    ]
+
     def __init__(self,
                  name: str = "",
                  kp: float = 0.,
@@ -41,6 +46,36 @@ class ProfileXYZ(Profile):
             status = status,
         )
 
+    @classmethod
+    def _sql_create(cls, execute):
+        execute("""
+          CREATE TABLE geometry_profileXYZ(
+            id INTEGER NOT NULL PRIMARY KEY,
+            name TEXT,
+            reach INTEGER NOT NULL,
+            kp REAL NOT NULL,
+            num INTEGER NOT NULL,
+            code1 INTEGER NOT NULL,
+            code2 INTEGER NOT NULL,
+            FOREIGN KEY(reach) REFERENCES river_reach(id)
+          )
+        """)
+
+        cls._create_submodel(execute)
+        return True
+
+    @classmethod
+    def _sql_update(cls, execute, version):
+        cls._update_submodel(execute, version)
+        return True
+
+    @classmethod
+    def _sql_load(cls, execute, data = None):
+        return None
+
+    def _sql_save(self, execute, data = None):
+        return True
+
     @classmethod
     def from_data(cls, header, data):
         profile = None
diff --git a/src/Model/Geometry/Reach.py b/src/Model/Geometry/Reach.py
index ebff2dc71f4765a56ca408cc322c87266efa12d9..c5415c7693b7596fdfdcaaf97e534a299986bd58 100644
--- a/src/Model/Geometry/Reach.py
+++ b/src/Model/Geometry/Reach.py
@@ -10,12 +10,18 @@ from functools import reduce
 
 from tools import flatten, timer, trace
 
+from Model.DB import SQLSubModel
+
 from Model.Geometry.Profile import Profile
 from Model.Geometry.ProfileXYZ import ProfileXYZ
 
 from Model.Except import FileFormatError, exception_message_box
 
-class Reach:
+class Reach(SQLSubModel):
+    _sub_classes = [
+        ProfileXYZ,
+    ]
+
     def __init__(self, status=None, parent=None):
         self._status = status
         self._parent = parent
@@ -24,6 +30,34 @@ class Reach:
         self._guidelines_is_valid = False
         self._guidelines = {}
 
+    @classmethod
+    def _sql_create(cls, execute):
+        cls._create_submodel(execute)
+        return True
+
+    @classmethod
+    def _sql_update(cls, execute, version):
+        cls._update_submodel(execute, version)
+        return None
+
+    @classmethod
+    def _sql_load(cls, execute, data = None):
+        new = cls(status = data["status"], parent = data["parent"])
+
+        new._profiles = ProfileXYZ._sql_load(
+            execute,
+            data = {
+                "status": data["status"],
+                "reach": new,
+            }
+        )
+
+        return new
+
+    def _sql_save(self, execute, data = None):
+        cls._save_submodel(execute, data)
+        return True
+
     def profile(self, i):
         """Returns profile at index i
 
diff --git a/src/Model/Network/Node.py b/src/Model/Network/Node.py
index 5da21909d2aff22fa9ac7d03a34ed3fa8a9d0569..5281a23627e89d4df97f3292ccc79e4b008d5437 100644
--- a/src/Model/Network/Node.py
+++ b/src/Model/Network/Node.py
@@ -38,6 +38,15 @@ class Node(object):
     def name(self):
         return self._name
 
+    @property
+    def x(self):
+        return self.pos.x
+
+    @property
+    def y(self):
+        return self.pos.y
+
+
     def setPos(self, x, y):
         self.pos.x = x
         self.pos.y = y
diff --git a/src/Model/River.py b/src/Model/River.py
index 580d2b8fa59999ef85e38cb29ca59aa3b0bff60e..63137639517416f6c9c27134946baa68926e36cc 100644
--- a/src/Model/River.py
+++ b/src/Model/River.py
@@ -1,5 +1,7 @@
 # -*- coding: utf-8 -*-
 
+from Model.DB import SQLSubModel
+
 from Model.Network.Node import Node
 from Model.Network.Edge import Edge
 from Model.Network.Graph import Graph
@@ -16,7 +18,9 @@ from Model.SolverParameters.SolverParametersList import SolverParametersList
 
 from Solver.Solvers import solver_type_list
 
-class RiverNode(Node):
+class RiverNode(Node, SQLSubModel):
+    _sub_classes = []
+
     def __init__(self, id:str, name:str,
                  x:float, y:float,
                  status = None):
@@ -28,6 +32,31 @@ class RiverNode(Node):
 
         self._locker = None
 
+    @classmethod
+    def _sql_create(cls, execute):
+        execute("""
+          CREATE TABLE river_node(
+            id INTEGER NOT NULL PRIMARY KEY,
+            name TEXT NOT NULL,
+            x REAL NOT NULL,
+            y REAL NOT NULL
+          )
+        """)
+
+        cls._create_submodel(execute)
+        return True
+
+    @classmethod
+    def _sql_update(cls, execute, version):
+        return None
+
+    @classmethod
+    def _sql_load(cls, execute, data = None):
+        return True
+
+    def _sql_save(self, execute, data = None):
+        return True
+
     @property
     def locker(self):
         return self._locker
@@ -37,7 +66,12 @@ class RiverNode(Node):
         self._locker = locker
 
 
-class RiverReach(Edge):
+class RiverReach(Edge, SQLSubModel):
+    _sub_classes = [
+        Reach,
+        # SectionList,
+    ]
+
     def __init__(self, id:str, name:str,
                  node1:RiverNode = None,
                  node2:RiverNode = None,
@@ -51,6 +85,33 @@ class RiverReach(Edge):
         self._reach = Reach(status=self._status, parent=self)
         self._sections = SectionList(status=self._status)
 
+    @classmethod
+    def _sql_create(cls, execute):
+        execute("""
+          CREATE TABLE river_reach(
+            id INTEGER NOT NULL PRIMARY KEY,
+            name TEXT NOT NULL,
+            node1 INTEGER,
+            node2 INTEGER,
+            FOREIGN KEY(node1) REFERENCES river_node(id),
+            FOREIGN KEY(node2) REFERENCES river_node(id)
+          )
+        """)
+
+        cls._create_submodel(execute)
+        return True
+
+    @classmethod
+    def _sql_update(cls, execute, version):
+        return True
+
+    @classmethod
+    def _sql_load(cls, execute, data = None):
+        return None
+
+    def _sql_save(self, execute, data = None):
+        return True
+
     @property
     def reach(self):
         return self._reach
@@ -59,7 +120,17 @@ class RiverReach(Edge):
     def sections(self):
         return self._sections
 
-class River(Graph):
+class River(Graph, SQLSubModel):
+    _sub_classes = [
+        RiverNode,
+        RiverReach,
+        # BoundaryConditionList,
+        # LateralContributionList,
+        # InitialConditionsDict,
+        # StricklersList,
+        # SolverParametersList,
+    ]
+
     def __init__(self, status=None):
         super(River, self).__init__(status=status)
 
@@ -74,6 +145,31 @@ class River(Graph):
         self._stricklers = StricklersList(status=self._status)
         self._parameters = {}
 
+    @classmethod
+    def _sql_create(cls, execute):
+        cls._create_submodel(execute)
+        return True
+
+    @classmethod
+    def _sql_update(cls, execute, version):
+        return True
+
+    @classmethod
+    def _sql_load(cls, execute, data = None):
+        return None
+
+    def _sql_save(self, execute, data = None):
+        return True
+
+    @property
+    def reach(self):
+        return self._reach
+
+    @property
+    def sections(self):
+        return self._sections
+
+
     @property
     def boundary_condition(self):
         return self._boundary_condition
diff --git a/src/Model/Serializable.py b/src/Model/Serializable.py
index 17b12345196a0fe7244b9145f92758ea08316f50..57f2a4c16fa01424e625685756efabcb59363d11 100644
--- a/src/Model/Serializable.py
+++ b/src/Model/Serializable.py
@@ -3,8 +3,8 @@
 import pickle
 
 class Serializable():
-    def __init__(self, filename):
-        self.filename = filename
+    def __init__(self):
+        return
 
     @classmethod
     def open(cls, filename):
diff --git a/src/Model/Study.py b/src/Model/Study.py
index a07d32c49e8a94fc305c9db10c3ef2a2780ef2d4..238da3b482f87b40fd74e4b3f89d12fbe20772f0 100644
--- a/src/Model/Study.py
+++ b/src/Model/Study.py
@@ -3,18 +3,28 @@
 import os
 from datetime import datetime
 
+from Model.DB import SQLModel
 from Model.Saved import SavedStatus
 from Model.Serializable import Serializable
-
+from Model.Except import NotImplementedMethodeError
 from Model.River import River
 
 from Checker.Study import *
 
-class Study(Serializable):
-    def __init__(self):
-        # Serialization information
-        super(Study, self).__init__("")
-        self.filename = ""
+class Study(SQLModel):
+    _sub_classes = [
+        River,
+    ]
+
+    def __init__(self, filename = None, init_new = True):
+        # Metadata
+        self._version = "0.0.0"
+        self.creation_date = datetime.now()
+        self.last_modification_date = datetime.now()
+        self.last_save_date = datetime.now()
+
+        self._filename = filename
+        super(Study, self).__init__(filename = filename)
 
         self.status = SavedStatus()
 
@@ -25,12 +35,9 @@ class Study(Serializable):
         self._time_system = "time"
         self._date = datetime.fromtimestamp(0)
 
-        self.creation_date = datetime.now()
-        self.last_modification_date = datetime.now()
-        self.last_save_date = datetime.now()
-
-        # Study data
-        self._river = River(status = self.status)
+        if init_new:
+            # Study data
+            self._river = River(status = self.status)
 
     @classmethod
     def checkers(cls):
@@ -66,6 +73,15 @@ class Study(Serializable):
         self._name = str(name)
         self.status.modified()
 
+    @property
+    def filename(self):
+        return self._filename
+
+    @filename.setter
+    def filename(self, filename):
+        self._filename = str(filename)
+        self._init_db_file(filename, is_new = True)
+
     @property
     def time_system(self):
         return self._time_system
@@ -88,9 +104,9 @@ class Study(Serializable):
         self._date = timestamp
         self.status.modified()
 
-    @classmethod
-    def new(cls):
-        return cls()
+    # @classmethod
+    # def new(cls):
+    #     return cls()
 
     @classmethod
     def new(cls, name, description, date = None):
@@ -101,3 +117,46 @@ class Study(Serializable):
             me.use_date()
             me.date = date
         return me
+
+    @classmethod
+    def open(cls, filename):
+        me = cls._load(filename)
+        return me
+
+    #######
+    # SQL #
+    #######
+
+    def _create(self):
+        # Info (metadata)
+        self.execute("CREATE TABLE info(key TEXT NOT NULL UNIQUE, value TEXT NOT NULL)")
+        self.execute(
+            f"INSERT INTO info VALUES ('version', '{self._sql_format(self._version)}')",
+            commit = True
+        )
+
+        self._create_submodel()
+        self.commit()
+
+    def _update(self):
+        version = self.execute(f"SELECT value FROM info WHERE key='version'")
+
+        print(f"{version} == {self._version}")
+        if version == self._version:
+            return True
+
+        print("TODO: update")
+        raise NotImplementedMethodeError(self, self._update)
+
+    @classmethod
+    def _load(cls, filename):
+        new = cls(init_new = False)
+
+        # Load river data
+        self._river = River.load()
+
+        return new
+
+    def _save(self):
+
+        self.commit()
diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py
index ea23101ad97dc9ef9501642d36cfa9aef57825f8..340e3cf000fcb7236de06b12af3a7b9250bcf3e0 100644
--- a/src/View/MainWindow.py
+++ b/src/View/MainWindow.py
@@ -213,7 +213,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
         Returns:
             Nothing
         """
-        if self.model.filename == "":
+        if self.model.filename is None or self.model.filename == "":
             file_name, _ = QFileDialog.getSaveFileName(
                 self, "Save File",
                 "", "Pamhyr(*.pkl)"
@@ -282,9 +282,9 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
         if self.model is None:
             dialog = QFileDialog(self)
             dialog.setFileMode(QFileDialog.FileMode.ExistingFile)
-            dialog.setDefaultSuffix(".pkl")
+            dialog.setDefaultSuffix(".pamhyr")
             #dialog.setFilter(dialog.filter() | QtCore.QDir.Hidden)
-            dialog.setNameFilters(['PamHyr (*.pkl)'])
+            dialog.setNameFilters(['PamHyr (*.pamhyr)'])
 
             if dialog.exec_():
                 file_name = dialog.selectedFiles()
diff --git a/src/config.py b/src/config.py
index 61c52272d0d30a773638fe4847b3eade7fc082ed..64f5664a3d92519093026085f11580cd6932ca99 100644
--- a/src/config.py
+++ b/src/config.py
@@ -19,7 +19,7 @@ class Config(SQL):
         self.filename = Config.filename()
         self.set_default_value()
 
-        super(Config, self).__init__(db = self.filename)
+        super(Config, self).__init__(filename = self.filename)
 
     def _create(self):
         # Info (meta data)
diff --git a/src/tools.py b/src/tools.py
index a3f20d049e382dd132bc2667a2dd4f22c72031ab..17d24b4e931487962013ec05d64ba054ab8fcfd0 100644
--- a/src/tools.py
+++ b/src/tools.py
@@ -165,7 +165,7 @@ def old_pamhyr_date_to_timestamp(date:str):
 # from sqlite3.
 
 class SQL(object):
-    def __init__(self, db = "db.sqlite3"):
+    def _init_db_file(self, db):
         exists = Path(db).exists()
 
         self._db = sqlite3.connect(db)
@@ -178,6 +178,13 @@ class SQL(object):
             self._update()      # Update db scheme if necessary
             self._load()        # Load data
 
+
+    def __init__(self, filename = None):
+        self._db = None
+
+        if filename is not None:
+            self._init_db_file(filename)
+
     def commit(self):
         self._db.commit()