dotConnect for SQLite Documentation
Full-Text Search Tutorial

In this tutorial you will learn how to create an application which will use the following technologies:

Note that SQLite full-text search is supported in Entity Framework v4 - v6. It is not supported in Entity Framework Core.

Follow the link at the end of this article to download the complete application sample.

Prerequisites

  1. Create a console C# project in Visual Studio 2010 SP1 and call it SQLiteFullTextSearch.
  2. In the project, add references to the following assemblies:
    • System.Data.Entity;
    • Devart.Data;
    • Devart.Data.SQLite;
    • Devart.Data.SQLite.Entity;
    • Devart.Data.SQLite.Entity.Migrations.
  3. Add EF Code-First NuGet package to the project (note that Entity Framework Core does not support full-text search, so you need to use Entity Framework v6): run Package Manager Console via the Visual Studio Tools menu -> Library Package Manager -> Package Manager Console, and in the displayed window run the following command:

    Install-Package EntityFramework

    As a result, the EntityFramework.dll assembly is added to the project, App.config is supplemented with records and the packages.config file is created.

  4. Add a new MyContext.cs file to the project, and place an empty model in it:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Data.Entity;
    using Devart.Data.SQLite;
    
    namespace SQLiteFullTextSearch {
    
      public class MyContext: DbContext {
    
        public MyContext() {
        }
    
        public MyContext(SQLiteConnection connection)
          : base(connection, false) {
        }
    
        static MyContext() {
        }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder) {
    
        }
    
      }
    }
    
    Imports System.Collections.Generic
    Imports System.Linq
    Imports System.Text
    Imports System.Data.Entity
    Imports Devart.Data.SQLite
    
    Namespace SQLiteFullTextSearch
    
      Public Class MyContext
        Inherits DbContext
    
        Public Sub New()
        End Sub
    
        Public Sub New(connection As SQLiteConnection)
          MyBase.New(connection, False)
        End Sub
    
        Shared Sub New()
        End Sub
    
        Protected Overrides Sub OnModelCreating(modelBuilder As DbModelBuilder)
    
        End Sub
    
      End Class
    End Namespace
    
    
  5. Run the following command in Package Manager Console:

    Enable-Migrations

    As a result, the Configuration class is added to the project.

  6. Complement the existing Configuration class constructor

        public Configuration()
        {
          AutomaticMigrationsEnabled = false;
        }
    
        Public Sub New()
          AutomaticMigrationsEnabled = False
        End Sub
    
    

    by specifying the SQL generator:

        public Configuration()
        {
          AutomaticMigrationsEnabled = false;
    
          SetSqlGenerator(
              Devart.Data.SQLite.Entity.Migrations.SQLiteConnectionInfo.InvariantName,
              new Devart.Data.SQLite.Entity.Migrations.SQLiteEntityMigrationSqlGenerator());
        }
    
        Public Sub New()
          AutomaticMigrationsEnabled = False
        
          SetSqlGenerator( _
            Devart.Data.SQLite.Entity.Migrations.SQLiteConnectionInfo.InvariantName, _
            New Devart.Data.SQLite.Entity.Migrations.SQLiteEntityMigrationSqlGenerator())
        End Sub
    
    
  7. Setting the connection string for our class of the MyContext model will be performed in the config file of the application. Add the connection string to the App.config file:
    <connectionStrings>
      <add name="MyContext" connectionString="Data Source=d:\Test.db;FailIfMissing=false"
      providerName="Devart.Data.SQLite"  />
    </connectionStrings>
    

Creating Classes and Migration

  1. Add the Book.cs file to the project:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.Text;
    
    namespace SQLiteFullTextSearch {
    
      public class Book {
    
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        [Column("rowid")]
        public int Rowid { get; set; }
     
        public string Author { get; set; }
     
        public string Name { get; set; }
     
        public string Content { get; set; }
    
        [Column("Books")]
        public string Fts { get; set; }
    
      }
    }
    
    Imports System.Collections.Generic
    Imports System.ComponentModel.DataAnnotations
    Imports System.Linq
    Imports System.Text
    
    Namespace SQLiteFullTextSearch
    
      Public Class Book
    
        <Key> _
        <DatabaseGenerated(DatabaseGeneratedOption.Identity)> _
        <Column("rowid")> _
        Public Property Rowid() As Integer
          Get
            Return m_Rowid
          End Get
          Set
            m_Rowid = Value
          End Set
        End Property
        Private m_Rowid As Integer
    
        Public Property Author() As String
          Get
            Return m_Author
          End Get
          Set
            m_Author = Value
          End Set
        End Property
        Private m_Author As String
    
        Public Property Name() As String
          Get
            Return m_Name
          End Get
          Set
            m_Name = Value
          End Set
        End Property
        Private m_Name As String
    
        Public Property Content() As String
          Get
            Return m_Content
          End Get
          Set
            m_Content = Value
          End Set
        End Property
        Private m_Content As String
    
        <Column("Books")> _
        Public Property Fts() As String
          Get
            Return m_Fts
          End Get
          Set
            m_Fts = Value
          End Set
        End Property
        Private m_Fts As String
    
      End Class
    End Namespace
    
    

    Here we use the rowid column, which is auto-generated by SQLite for all the tables, to map the entity key. The Fts property is created for performing full-text search. It is mapped to the virtual column, having the same name as the table. For more information on these properties refer to Full-Text Search Support in Entity Framework.

  2. Add the following line to the MyContext class:

    public DbSet<Book> Books { get; set; }
    
    Public Property Books() As DbSet(Of Book)
      Get
        Return m_Books
      End Get
      Set
        m_Books = Value
      End Set
    End Property
    Private m_Books As DbSet(Of Book)
    
    
  3. To generate migration, run the following command in Package Manager Console:

    Add-Migration AddBook
    

    As a result, a migration class with the following content is added to the project:

        public partial class AddBook : DbMigration
        {
            public override void Up()
            {
                CreateTable(
                    "Books",
                    c => new
                        {
                            Rowid = c.Int(nullable: false, identity: true),
                            Author = c.String(maxLength: 200),
                            Name = c.String(maxLength: 300),
                            Content = c.String(),
                            Books = c.String(),
                        })
                    .PrimaryKey(t => t.Rowid);
    
            }
            
            public override void Down()
            {
                DropTable("Books");
            }
        }
    
    Public Partial Class AddBook
      Inherits DbMigration
      Public Overrides Sub Up()
        CreateTable("Books", Function(c) New With { 
          .Rowid = c.Int(nullable := False, identity := True), 
          .Author = c.[String](maxLength := 200), 
          .Name = c.[String](maxLength := 300), 
          .Content = c.[String](), 
          .Books = c.[String]() 
        }).PrimaryKey(Function(t) t.Rowid)
    
      End Sub
    
      Public Overrides Sub Down()
        DropTable("Books")
      End Sub
    End Class
    
    
  4. Complement the migration by specifying the storage engine type for the table, and creating a full-text index on the Content column, without specifying explicitly the name of the generated index:

        using Devart.Data.SQLite.Entity;
        using Devart.Data.SQLite.Entity.Migrations;
        
        public partial class AddBook : DbMigration
        {
            public override void Up()
            {
                CreateTable(
                    "Books",
                    c => new
                        {
                            Rowid = c.Int(nullable: false, identity: true),
                            Author = c.String(maxLength: 200),
                            Name = c.String(maxLength: 300),
                            Content = c.String(),
                            Books = c.String(),
                        },
                        anonymousArguments: new SQLiteCreateTableConfiguration()
                        {
                            TableType = SQLiteTableType.Fts4,
                            FtsTokenizer = SQLiteFtsTokenizer.Porter
                        })
                    .PrimaryKey(t => t.Rowid);
    
            }
            
            public override void Down()
            {
                DropTable("Books");
            }
        }
    
    Imports Devart.Data.SQLite.Entity
    Imports Devart.Data.SQLite.Entity.Migrations
    
    Public Partial Class AddBook
      Inherits DbMigration
      Public Overrides Sub Up()
        CreateTable("Books", Function(c) New With { 
          .Rowid = c.Int(nullable := False, identity := True), 
          .Author = c.[String](maxLength := 200), 
          .Name = c.[String](maxLength := 300), 
          .Content = c.[String](), 
          .Books = c.[String]() 
        }, anonymousArguments := New SQLiteCreateTableConfiguration() With { _
          .TableType = SQLiteTableType.Fts4, _
          .FtsTokenizer = SQLiteFtsTokenizer.Porter _
        }).PrimaryKey(Function(t) t.Rowid)
    
      End Sub
    
      Public Overrides Sub Down()
        DropTable("Books")
      End Sub
    End Class
    
    
  5. Update the database using the migration, by running the following command in Package Manager Console:

    Update-Database -Verbose
    

    As a result, commands for model objects creation will be executed

    CREATE VIRTUAL TABLE Books USING fts4(Author, Name, Content, tokenize=porter)
    

    Also, commands for creating the system MigrationHistory table and filling it with migration data will be executed.

Full-Text Search Sample

In this tutorial, we have turned on SQLiteMonitor to view executed DDL and DML statements in the Devart dbMonitor application. However, keep in mind that the use of SQLiteMonitor is feasible for the purpose of testing and debugging but should be limited in production environments, since monitoring can decrease the performance of your application. To use monitoring, add this code to your application

var monitor = new SQLiteMonitor() { IsActive = true };
Dim monitor = New SQLiteMonitor() With { _
  .IsActive = True _
}

and run dbMonitor.

Let's write the code filling the table with data and forming full-text search queries.

using (var ctx = new SQLiteFullTextSearch.MyContext()) {

  // Load information into the Books table
  ctx.Books.Add(new SQLiteFullTextSearch.Book() {
    Author = "William Shakespeare",
    Name = "The Tragedy of King Richard the Second",
    Content = @"ACT 1
SCENE I. London. A Room in the palace.
[Enter KING RICHARD, attended; JOHN OF GAUNT, with other NOBLES.]
KING RICHARD.
  Old John of Gaunt, time-honoured Lancaster,
  Hast thou, according to thy oath and band,
  Brought hither Henry Hereford thy bold son,
  Here to make good the boisterous late appeal,
  Which then our leisure would not let us hear,
  Against the Duke of Norfolk, Thomas Mowbray?
..."
  });
  ctx.Books.Add(new SQLiteFullTextSearch.Book() {
    Author = "William Shakespeare",
    Name = "The Life and Death of King John",
    Content = @"ACT 1.
SCENE I. Northampton. A Room of State in the Palace.
[Enter KING JOHN, QUEEN ELINOR, PEMBROKE, ESSEX, SALISBURY, and others, with CHATILLON.]
KING JOHN.
  Now, say, Chatillon, what would France with us?
CHATILLON.
  Thus, after greeting, speaks the King of France,
  In my behaviour, to the majesty,
  The borrow'd majesty of England here.
..."
  });
  ctx.Books.Add(new SQLiteFullTextSearch.Book() {
    Author = "William Shakespeare",
    Name = "The Life of Henry the Fifth",
    Content = @"ACT 1.
SCENE I. London. An ante-chamber in the King's palace.
[Enter the Archbishop of Canterbury and the Bishop of Ely.]
CANTERBURY.
  My lord, I'll tell you: that self bill is urg'd,
  Which in the eleventh year of the last king's reign
  Was like, and had indeed against us pass'd,
  But that the scambling and unquiet time
  Did push it out of farther question.
..."
  });
  ctx.SaveChanges();

  // Write an analogue of the SQL query:
  //  SELECT rowid, name FROM Books
  //  WHERE Books MATCH 'king palace Norfolk';
  var firstQuery = ctx.Books
    .Where(b => SQLiteTextFunctions.Match(b.Fts, "king palace Norfolk"))
    .Select(b => new { b.Rowid, b.Name } );
  var firstQueryResults = firstQuery.ToList();

  // Output:
  Console.WriteLine();
  Console.WriteLine("== Querying the table ==");
  Console.WriteLine();
  Console.WriteLine("1) Natural language");
  Console.WriteLine();
  Console.WriteLine("SQL:");
  Console.WriteLine(firstQuery.ToString());
  Console.WriteLine();
  Console.WriteLine("Data:");
  Console.WriteLine("{0,-5} | {1}", "Rowid", "Name");
  Console.WriteLine(new string('-', 50));
  foreach (var item in firstQueryResults)
    Console.WriteLine("{0,-5} | {1} ", item.Rowid, item.Name);        

  // Write an analogue of the SQL query:
  //   SELECT rowid, name FROM Books
  //   WHERE Books MATCH '"king''s palace"'
  var secondQuery = ctx.Books
            .Where(b => SQLiteTextFunctions.Match(
              b.Content, "\"king's palace\""))
            .Select(b => new { b.Rowid, b.Name } );
  var secondQueryResults = secondQuery.ToList();

  // Output:
  Console.WriteLine();
  Console.WriteLine("2) Phrase query");
  Console.WriteLine();
  Console.WriteLine("SQL:");
  Console.WriteLine(secondQuery.ToString());
  Console.WriteLine();
  Console.WriteLine("Data:");
  Console.WriteLine("{0,-5} | {1}", "Rowid", "Name");
  Console.WriteLine(new string('-', 50));
  foreach (var item in secondQueryResults)
    Console.WriteLine("{0,-5} | {1} ", item.Rowid, item.Name);
  Console.ReadLine();
}
Using ctx = New SQLiteFullTextSearch.MyContext()

  ' Load information into the Books table
  ctx.Books.Add(New SQLiteFullTextSearch.Book() With { _
    .Author = "William Shakespeare", _
    .Name = "The Tragedy of King Richard the Second", _
    .Content = "ACT 1" & vbCr & vbLf & "SCENE I. London. A Room in the palace." _
      & vbCr & vbLf & "[Enter KING RICHARD, attended; JOHN OF GAUNT, with other NOBLES.]" _
      & vbCr & vbLf & "KING RICHARD." & vbCr & vbLf _
      & "  Old John of Gaunt, time-honoured Lancaster," & vbCr & vbLf _
      & "  Hast thou, according to thy oath and band," & vbCr & vbLf _
      & "  Brought hither Henry Hereford thy bold son," & vbCr & vbLf _
      & "  Here to make good the boisterous late appeal," & vbCr & vbLf _
      & "  Which then our leisure would not let us hear," & vbCr & vbLf _
      & "  Against the Duke of Norfolk, Thomas Mowbray?" & vbCr & vbLf & "..." _
  })
  ctx.Books.Add(New SQLiteFullTextSearch.Book() With { _
    .Author = "William Shakespeare", _
    .Name = "The Life and Death of King John", _
    .Content = "ACT 1." & vbCr & vbLf & _
      "SCENE I. Northampton. A Room of State in the Palace." & vbCr & vbLf & _
      "[Enter KING JOHN, QUEEN ELINOR, PEMBROKE, ESSEX, SALISBURY, and others, with CHATILLON.]" _
      & vbCr & vbLf & "KING JOHN." & vbCr & vbLf & _
      "  Now, say, Chatillon, what would France with us?" & vbCr & vbLf & _
      "CHATILLON." & vbCr & vbLf & "  Thus, after greeting, speaks the King of France," _
      & vbCr & vbLf & "  In my behaviour, to the majesty," & vbCr & vbLf & _
      "  The borrow'd majesty of England here." & vbCr & vbLf & "..." _
  })
  ctx.Books.Add(New SQLiteFullTextSearch.Book() With { _
    .Author = "William Shakespeare", _
    .Name = "The Life of Henry the Fifth", _
    .Content = "ACT 1." & vbCr & vbLf & _
      "SCENE I. London. An ante-chamber in the King's palace." & vbCr & vbLf & _
      "[Enter the Archbishop of Canterbury and the Bishop of Ely.]" & vbCr & vbLf & _
      "CANTERBURY." & vbCr & vbLf & "  My lord, I'll tell you: that self bill is urg'd," _
      & vbCr & vbLf & "  Which in the eleventh year of the last king's reign" & vbCr _
      & vbLf & "  Was like, and had indeed against us pass'd," & vbCr & vbLf & _
      "  But that the scambling and unquiet time" & vbCr & vbLf & _
      "  Did push it out of farther question." & vbCr & vbLf & "..." _
  })
  ctx.SaveChanges()

  ' Write an analogue of the SQL query:
  '  SELECT rowid, name FROM Books
  '  WHERE Books MATCH 'king palace Norfolk';
  Dim firstQuery = From b In ctx.Books _
                   Where SQLiteTextFunctions.Match(b.Fts, "king palace Norfolk") _
                   Select _
                      b.Rowid, _
                      b.Name
  Dim firstQueryResults = firstQuery.ToList()

  ' Output:
  Console.WriteLine()
  Console.WriteLine("== Querying the table ==")
  Console.WriteLine()
  Console.WriteLine("1) Natural language")
  Console.WriteLine()
  Console.WriteLine("SQL:")
  Console.WriteLine(firstQuery.ToString())
  Console.WriteLine()
  Console.WriteLine("Data:")
  Console.WriteLine("{0,-5} | {1}", "Rowid", "Name")
  Console.WriteLine(New String("-"C, 50))
  For Each item In firstQueryResults
    Console.WriteLine("{0,-5} | {1} ", item.Rowid, item.Name)
  Next

  ' Write an analogue of the SQL query:
  '   SELECT rowid, name FROM Books
  '   WHERE Books MATCH '"king''s palace"'
  Dim firstQuery = From b In ctx.Books _
                   Where SQLiteTextFunctions.Match(b.Fts, """king's palace""") _
                   Select _
                      b.Rowid, _
                      b.Name
  Dim secondQueryResults = secondQuery.ToList()

  ' Output:
  Console.WriteLine()
  Console.WriteLine("2) Phrase query")
  Console.WriteLine()
  Console.WriteLine("SQL:")
  Console.WriteLine(secondQuery.ToString())
  Console.WriteLine()
  Console.WriteLine("Data:")
  Console.WriteLine("{0,-5} | {1}", "Rowid", "Name")
  Console.WriteLine(New String("-"C, 50))
  For Each item In secondQueryResults
    Console.WriteLine("{0,-5} | {1} ", item.Rowid, item.Name)
  Next
  Console.ReadLine()
End Using

Conclusion

Thus, we have shown how to use SQLite full-text search functionality in Entity Framework applications, including the development process, based on Code-First Migrations. We are looking forward to receiving feedback from you via comments, the Entity Framework forum and our contact form, if it's necessary to rework or improve support of full-text indexing and searching in our provider. You can download the archive with the complete version of the sample from this tutorial here.

See Also

Full-Text Search Support