Calibre_EntityFrameworkCore

Details

diff --git a/src/Calibre.Model.Database/CalibreContext.cs b/src/Calibre.Model.Database/CalibreContext.cs
index bbfda10..ab153ee 100644
--- a/src/Calibre.Model.Database/CalibreContext.cs
+++ b/src/Calibre.Model.Database/CalibreContext.cs
@@ -18,7 +18,7 @@ namespace Calibre.Model.Database
         public string ConnectionString { private set; get; }
 
         public bool AsNoTracking { set; get; }
-        public DirectoryInfo LibraryDirectory { set; get; }
+        //public DirectoryInfo LibraryDirectory { set; get; }
 
 
         #region Sets
@@ -364,24 +364,7 @@ namespace Calibre.Model.Database
 
             modelBuilder.Entity<BooksLanguagesLink>(entity =>
             {
-                entity.ToTable("books_languages_link");
-
-                entity.HasIndex(e => new { e.Book, e.LangCode }, "IX_books_languages_link_book_lang_code")
-                    .IsUnique();
-
-                entity.HasIndex(e => e.LangCode, "books_languages_link_aidx");
-
-                entity.HasIndex(e => e.Book, "books_languages_link_bidx");
-
-                entity.Property(e => e.Id)
-                    .ValueGeneratedNever()
-                    .HasColumnName("id");
-
-                entity.Property(e => e.Book).HasColumnName("book");
-
-                entity.Property(e => e.ItemOrder).HasColumnName("item_order");
-
-                entity.Property(e => e.LangCode).HasColumnName("lang_code");
+                BooksLanguagesLink.EfSetup(entity);                
             });
 
             modelBuilder.Entity<BooksPluginDatum>(entity =>
diff --git a/src/Calibre.Model.Database/Entities/Book.cs b/src/Calibre.Model.Database/Entities/Book.cs
index 4012422..badfe05 100644
--- a/src/Calibre.Model.Database/Entities/Book.cs
+++ b/src/Calibre.Model.Database/Entities/Book.cs
@@ -31,7 +31,9 @@ namespace Calibre.Model.Database.Entities
         public virtual List<BooksTagsLink> Tags { get; set; } 
             = new List<BooksTagsLink>();
         public virtual List<BooksAuthorsLink> Autors { get; set; }
-            = new List<BooksAuthorsLink>();
+            = new List<BooksAuthorsLink>();                
+        public virtual List<BooksLanguagesLink> Languages { get; set; }
+            = new List<BooksLanguagesLink>();
         public virtual List<Data> FileData { get; set; }
             = new List<Data>();
 
diff --git a/src/Calibre.Model.Database/Entities/Languages/BooksLanguagesLink.cs b/src/Calibre.Model.Database/Entities/Languages/BooksLanguagesLink.cs
index 268b52e..cb6bf1b 100644
--- a/src/Calibre.Model.Database/Entities/Languages/BooksLanguagesLink.cs
+++ b/src/Calibre.Model.Database/Entities/Languages/BooksLanguagesLink.cs
@@ -3,13 +3,62 @@ using System.Collections.Generic;
 
 #nullable disable
 
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
 namespace Calibre.Model.Database.Entities
 {
     public partial class BooksLanguagesLink
     {
         public long Id { get; set; }
+
+
         public long Book { get; set; }
+        public virtual Book BookItem { get; set; }
+
         public long LangCode { get; set; }
+        public virtual Language LangItem { get; set; }
+
         public long ItemOrder { get; set; }
+
+
+        public override string ToString()
+        {
+            return LangItem?.LangCode;
+        }
+
+
+        internal static void EfSetup(
+            EntityTypeBuilder<BooksLanguagesLink> entity
+            )
+        {
+            entity.ToTable("books_languages_link");
+
+            entity.HasIndex(e => new { e.Book, e.LangCode }, "IX_books_languages_link_book_lang_code")
+                .IsUnique();
+
+            entity.HasIndex(e => e.LangCode, "books_languages_link_aidx");
+
+            entity.HasIndex(e => e.Book, "books_languages_link_bidx");
+
+            entity.Property(e => e.Id)
+                .ValueGeneratedNever()
+                .HasColumnName("id");
+
+            entity.Property(e => e.Book).HasColumnName("book");
+
+            entity.Property(e => e.ItemOrder).HasColumnName("item_order");
+
+            entity.Property(e => e.LangCode).HasColumnName("lang_code");
+
+            entity
+                .HasOne(e => e.BookItem)
+                .WithMany(e => e.Languages)
+                .HasForeignKey(e => e.Book);
+            entity
+                .HasOne(e => e.LangItem)
+                .WithMany(e => e.Books)
+                .HasForeignKey(e => e.LangCode);
+        }
     }
 }
diff --git a/src/Calibre.Model.Database/Entities/Languages/Language.cs b/src/Calibre.Model.Database/Entities/Languages/Language.cs
index 600c035..4327b70 100644
--- a/src/Calibre.Model.Database/Entities/Languages/Language.cs
+++ b/src/Calibre.Model.Database/Entities/Languages/Language.cs
@@ -9,5 +9,14 @@ namespace Calibre.Model.Database.Entities
     {
         public long Id { get; set; }
         public string LangCode { get; set; }
+
+        public virtual List<BooksLanguagesLink> Books { get; set; }
+            = new List<BooksLanguagesLink>();
+
+
+        public override string ToString()
+        {
+            return LangCode;
+        }
     }
 }
diff --git a/src/Calibre.Model.Domain/Calibre.Model.Domain.csproj b/src/Calibre.Model.Domain/Calibre.Model.Domain.csproj
new file mode 100644
index 0000000..58d422b
--- /dev/null
+++ b/src/Calibre.Model.Domain/Calibre.Model.Domain.csproj
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net5.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.10" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Calibre.Model.Database\Calibre.Model.Database.csproj" />
+    <ProjectReference Include="..\LibraryText\LibraryText.csproj" />
+    <ProjectReference Include="..\Tools.PdfProvider\Tools.PdfProvider.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Calibre.Model.Domain/Dal/SearchProvider.cs b/src/Calibre.Model.Domain/Dal/SearchProvider.cs
new file mode 100644
index 0000000..1822bbf
--- /dev/null
+++ b/src/Calibre.Model.Domain/Dal/SearchProvider.cs
@@ -0,0 +1,172 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using System.IO;
+
+using Microsoft.EntityFrameworkCore;
+
+using Tools.PdfProvider;
+
+using Calibre.Model.Database;
+using Calibre.Model.Database.Entities;
+
+using Calibre.Model.Domain.Entities;
+
+using LibraryText.Entities;
+
+namespace Calibre.Model.Domain.Dal
+{
+    public class SearchProvider
+    {
+        private readonly SimplePdfReader SimplePdfReader;
+
+
+        public SearchProvider(
+            SimplePdfReader simplePdfReader
+            )
+        {
+            SimplePdfReader = simplePdfReader;
+        }
+
+
+        public async Task<Dictionary<Library, BookWithLibraryContainer[]>> GetBooksByLibrariesAsync(            
+            Func<Book, bool> whereExpression = null,
+            bool includeTags = true,
+            bool includeAutors = true,
+            bool includeFileData = true,
+            bool includeLang = true,
+            params Library[] libraries
+            )
+        {
+            List<CalibreContext> contexts
+                = new List<CalibreContext>(libraries.Length);
+
+            Dictionary<Library, Task<Book[]>> data
+                = new Dictionary<Library, Task<Book[]>>(libraries.Length);
+
+            try
+            {
+                foreach (var elem in libraries)
+                {
+                    var connectionString = Library.BuildConnectionStringFromPath(elem.DbFile.FullName);
+
+                    var context = new CalibreContext(connectionString)
+                    {
+                        AsNoTracking = false,
+                        //LibraryDirectory = libraryDirectory
+                    };
+                    contexts.Add(context);
+
+                    IQueryable<Book> selectExpr = context
+                        .Books;
+
+                    //1) Подгружаем теги
+                    if (includeTags)
+                    {
+                        selectExpr = selectExpr
+                            .Include(e => e.Tags)
+                            .ThenInclude(e => e.TagItem);
+                    }
+
+                    //2) Подгружаем авторов
+                    if (includeAutors)
+                    {
+                        selectExpr = selectExpr
+                            .Include(e => e.Autors)
+                            .ThenInclude(e => e.AuthorItem);
+                    }
+
+                    //3) Подгружаем данные о фалах
+                    if (includeFileData)
+                    {
+                        selectExpr = selectExpr
+                            .Include(e => e.FileData);
+                    }
+
+                    if (includeLang)
+                    {
+                        selectExpr = selectExpr
+                            .Include(e => e.Languages)
+                            .ThenInclude(e => e.LangItem);
+                    }
+
+                    //4) Условие поиска
+                    if (whereExpression != null)
+                    {
+                        selectExpr = selectExpr
+                            .Where(whereExpression)
+                            .AsQueryable();
+                    }
+
+                    var selectResultTask = selectExpr.ToArrayAsync();
+                    data.Add(elem, selectResultTask);
+                }
+
+                await Task.WhenAll(
+                    data.Values.ToArray()
+                    );
+            }
+            finally
+            {
+                contexts.ForEach(
+                    e => e.Dispose()
+                    );
+            }
+
+
+            Dictionary<Library, BookWithLibraryContainer[]> result 
+                = new Dictionary<Library, BookWithLibraryContainer[]>(libraries.Length);
+            foreach (var elem in data)
+            {
+                var library = elem.Key;
+                var libraryProxy = new ImmutableProxy<Library>(library);
+
+                var books = elem.Value.Result
+                    .Select(
+                        e2 => BookWithLibraryContainer.Create(e2, libraryProxy)
+                        )
+                    .ToArray();
+
+                result.Add(library, books);
+            }
+
+            return result;
+        }
+
+        public async Task<Dictionary<BookWithLibraryContainer, string>> GetTextByBooksAsync(
+            IList<BookWithLibraryContainer> books,
+            bool ignoreError,
+            Func<BookWithLibraryContainer, string, bool> whereExpression = null
+            )
+        {
+            Dictionary<BookWithLibraryContainer, string> result 
+                = new Dictionary<BookWithLibraryContainer, string>(books.Count);
+
+            foreach (var elem in books)
+            {
+                var bookText = await elem.ReadFileAsync(
+                    async (s) => 
+                    {
+                        var res = await SimplePdfReader.ReadPdfAsync(s, ignoreError);
+                        return res;
+                    }
+                    );
+
+                if (whereExpression != null)
+                {
+                    if (!whereExpression(elem, bookText)) 
+                    {
+                        continue;
+                    }
+                }
+
+                result.Add(elem, bookText);
+            }
+
+            return result;
+        }
+    }
+}
diff --git a/src/Calibre.Model.Domain/Entities/BookWithLibraryContainer.cs b/src/Calibre.Model.Domain/Entities/BookWithLibraryContainer.cs
new file mode 100644
index 0000000..0b5296d
--- /dev/null
+++ b/src/Calibre.Model.Domain/Entities/BookWithLibraryContainer.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using System.IO;
+
+using Tools.PdfProvider;
+
+using Calibre.Model.Database.Entities;
+
+using LibraryClass = Calibre.Model.Domain.Entities.Library;
+
+namespace Calibre.Model.Domain.Entities
+{
+    /// <summary>
+    /// Immutable
+    /// </summary>
+    public class BookWithLibraryContainer
+    {
+        #region Properties
+
+        public Book Book { private set; get; }
+        public ImmutableProxy<LibraryClass> Library { private set; get; }
+
+        #endregion
+
+
+        private BookWithLibraryContainer() 
+        {
+            
+        }
+
+
+        #region methods
+                
+        public async Task ReadFileAsync(
+            Func<Stream, Task> readActionAsync
+            )
+        {
+            var filePath = LibraryClass.GetPdfFilePath(Library.Data, Book);
+            using (var stream = new FileStream(filePath, FileMode.Open))
+            {
+                await readActionAsync(stream);
+            }
+        }
+
+        public async Task<T> ReadFileAsync<T>(
+            Func<Stream, Task<T>> readActionAsync
+            ) 
+        {
+            var filePath = LibraryClass.GetPdfFilePath(Library.Data, Book);
+            using (var stream = new FileStream(filePath, FileMode.Open))
+            {
+                var result = await readActionAsync(stream);
+                return result;
+            }
+        }
+
+        #endregion
+
+
+        #region override
+
+        public override int GetHashCode()
+        {
+            return Book.GetHashCode();
+        }
+
+        public override string ToString()
+        {
+            return Book.ToString();
+        }
+
+        #endregion
+
+
+        #region static
+
+        public static BookWithLibraryContainer Create(
+            Book book, 
+            ImmutableProxy<LibraryClass> library
+            )
+        {            
+            var item = new BookWithLibraryContainer() 
+            {
+                Book = book,
+                Library = library
+            };
+
+            library.MutateValue(
+                e => LibraryClass.AddBookContainer(library.Data, item)
+                );
+
+            return item;
+        }
+
+        #endregion
+    }
+}
diff --git a/src/Calibre.Model.Domain/Entities/Library.cs b/src/Calibre.Model.Domain/Entities/Library.cs
new file mode 100644
index 0000000..7999c51
--- /dev/null
+++ b/src/Calibre.Model.Domain/Entities/Library.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using System.IO;
+using System.Collections.Immutable;
+
+using Calibre.Model.Database.Entities;
+
+namespace Calibre.Model.Domain.Entities
+{
+    /// <summary>
+    /// Immutable
+    /// </summary>
+    public class Library
+    {
+        #region Properties
+
+        public string Name { private set; get; }
+        public string FullName { private set; get; }
+
+        public FileInfo DbFile => new FileInfo(
+            Path.Combine(FullName, DbFileName)
+            );
+
+        public ImmutableDictionary<string, BookWithLibraryContainer> Books { private set; get; }
+            = ImmutableDictionary<string, BookWithLibraryContainer>.Empty;
+
+        #endregion
+
+
+        private Library() { }
+
+
+
+        #region override
+
+        public override int GetHashCode()
+        {
+            return FullName.GetHashCode();
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (obj is Library l)
+            {
+                return string.Equals(FullName, l.FullName);
+            }
+
+            return false;
+        }
+
+        public override string ToString()
+        {
+            return FullName;
+        }
+
+        #endregion
+
+
+        #region static
+
+        public static string DbFileName 
+            => "metadata.db";
+
+        public static Library[] SearchAll(DirectoryInfo directory) 
+        {
+            var dbFiles = directory
+                .GetFiles(
+                    DbFileName, 
+                    SearchOption.AllDirectories
+                    )
+                .ToArray();
+
+            var result = dbFiles.Select(
+                e => new Library() 
+                { 
+                    FullName = e.Directory.FullName,
+                    Name = e.Directory.Name
+                })
+                .ToArray();
+
+            return result;
+        }
+
+        public static string BuildConnectionStringFromPath(
+            string pathToDbFile
+            )
+        {
+            return $"Filename={pathToDbFile}";
+        }
+
+        public static Library AddBookContainer(
+            Library library,
+            BookWithLibraryContainer bookContainer
+            )
+        {
+            var result = new Library() 
+            {
+                Name = library.Name,
+                FullName = library.FullName
+            };
+
+            result.Books = library.Books.Add(
+                bookContainer.Book.Title, 
+                bookContainer
+                );
+
+            return result;
+        }
+
+        public static string GetPdfFilePath(
+            Library library, 
+            Book book
+            )
+        {
+            var file = book.FileData.First(
+                e => string.Equals(e.Format, "PDF", StringComparison.OrdinalIgnoreCase)
+                );
+
+            var result = Path.Combine(
+                library.FullName,
+                book.Path.Replace('/', '\\'),
+                $"{file.Name}.{file.Format}"
+                );
+
+            return result;
+        }
+
+        #endregion
+    }
+}

src/Calibre.sln 16(+15 -1)

diff --git a/src/Calibre.sln b/src/Calibre.sln
index cb6c14a..c5a1d79 100644
--- a/src/Calibre.sln
+++ b/src/Calibre.sln
@@ -11,7 +11,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Model", "Model", "{951387AC
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{5654144E-E0B3-4C6D-AFEB-799E77215B4E}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tools.PdfProvider", "Tools.PdfProvider\Tools.PdfProvider.csproj", "{057A6C6C-DC6B-4BA2-9D25-C263C6F04B68}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tools.PdfProvider", "Tools.PdfProvider\Tools.PdfProvider.csproj", "{057A6C6C-DC6B-4BA2-9D25-C263C6F04B68}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibraryText", "LibraryText\LibraryText.csproj", "{EA856697-8295-45DF-A730-D9A082AEEFCE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Calibre.Model.Domain", "Calibre.Model.Domain\Calibre.Model.Domain.csproj", "{81BA693F-7887-4155-B877-B65966EB9B76}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -31,6 +35,14 @@ Global
 		{057A6C6C-DC6B-4BA2-9D25-C263C6F04B68}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{057A6C6C-DC6B-4BA2-9D25-C263C6F04B68}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{057A6C6C-DC6B-4BA2-9D25-C263C6F04B68}.Release|Any CPU.Build.0 = Release|Any CPU
+		{EA856697-8295-45DF-A730-D9A082AEEFCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{EA856697-8295-45DF-A730-D9A082AEEFCE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{EA856697-8295-45DF-A730-D9A082AEEFCE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{EA856697-8295-45DF-A730-D9A082AEEFCE}.Release|Any CPU.Build.0 = Release|Any CPU
+		{81BA693F-7887-4155-B877-B65966EB9B76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{81BA693F-7887-4155-B877-B65966EB9B76}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{81BA693F-7887-4155-B877-B65966EB9B76}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{81BA693F-7887-4155-B877-B65966EB9B76}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -39,6 +51,8 @@ Global
 		{39E1440C-9955-4744-8A22-02ED52502C11} = {5654144E-E0B3-4C6D-AFEB-799E77215B4E}
 		{4AA0EAB2-7B70-495A-9769-1665B857A91B} = {951387AC-1771-4B72-B885-B5DFAFB55D4D}
 		{057A6C6C-DC6B-4BA2-9D25-C263C6F04B68} = {951387AC-1771-4B72-B885-B5DFAFB55D4D}
+		{EA856697-8295-45DF-A730-D9A082AEEFCE} = {951387AC-1771-4B72-B885-B5DFAFB55D4D}
+		{81BA693F-7887-4155-B877-B65966EB9B76} = {951387AC-1771-4B72-B885-B5DFAFB55D4D}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {362F0FEB-FB13-46F0-825D-15E6F10100D4}
diff --git a/src/Calibre/Calibre.csproj b/src/Calibre/Calibre.csproj
index a69768f..6c3d137 100644
--- a/src/Calibre/Calibre.csproj
+++ b/src/Calibre/Calibre.csproj
@@ -15,6 +15,8 @@
 
   <ItemGroup>
     <ProjectReference Include="..\Calibre.Model.Database\Calibre.Model.Database.csproj" />
+    <ProjectReference Include="..\Calibre.Model.Domain\Calibre.Model.Domain.csproj" />
+    <ProjectReference Include="..\LibraryText\LibraryText.csproj" />
     <ProjectReference Include="..\Tools.PdfProvider\Tools.PdfProvider.csproj" />
   </ItemGroup>
 

src/Calibre/Program.cs 269(+142 -127)

diff --git a/src/Calibre/Program.cs b/src/Calibre/Program.cs
index f90f828..ed5ec33 100644
--- a/src/Calibre/Program.cs
+++ b/src/Calibre/Program.cs
@@ -4,104 +4,24 @@ using System.Threading.Tasks;
 using System.Linq;
 using System.IO;
 using System.Diagnostics;
-using System.IO;
 
 using Microsoft.EntityFrameworkCore;
 
 using Tools.PdfProvider;
 
+using LibraryText;
+using LibraryText.Entities;
+
 using Calibre.Model.Database;
 using Calibre.Model.Database.Entities;
+using Calibre.Model.Domain.Entities;
+using Calibre.Model.Domain.Dal;
 
 namespace Calibre
 {
     class Program
     {
-        private static string[] SelectDbFiles(DirectoryInfo directory)
-        {
-            var dbFiles = directory
-                .GetFiles("metadata.db", SearchOption.AllDirectories)
-                .Select(e => e.FullName)
-                .ToArray();
-
-            return dbFiles;
-        }
-
-        private static string BuildConnectionStringFromPath(string path)
-        {
-            return $"Filename={path}";
-        }
-
-        private static async Task<Dictionary<string, Book[]>> SearchBooksByTitle(
-            string[] dbFiles,
-            string containsTitle
-            )
-        {
-            List<CalibreContext> contexts 
-                = new List<CalibreContext>(containsTitle.Length);
-            Dictionary<string, Task<Book[]>> data 
-                = new Dictionary<string, Task<Book[]>>(containsTitle.Length);
-
-            try
-            {
-                foreach (var elem in dbFiles)
-                {
-                    var connectionString = BuildConnectionStringFromPath(elem);
-                    var libraryDirectory = new DirectoryInfo(
-                        Path.GetDirectoryName(elem)
-                        );
-
-                    var context = new CalibreContext(connectionString) 
-                    {
-                        AsNoTracking = false,
-                        LibraryDirectory = libraryDirectory
-                    };
-                    contexts.Add(context);
-
-                    IQueryable<Book> selectExpr = context
-                        .Books
-                        .Include(e => e.Tags)
-                        .ThenInclude(e => e.TagItem);
-
-                    selectExpr = selectExpr
-                        .Include(e => e.FileData);
-
-                    selectExpr = selectExpr
-                        .Include(e => e.Autors)
-                        .ThenInclude(e => e.AuthorItem);
-
-                    if (string.IsNullOrEmpty(containsTitle))
-                    {
-                        selectExpr = selectExpr
-                            .Where(
-                                e => e.Title.Contains(containsTitle)
-                            );
-                    }
-
-                    var selectResultTask = selectExpr.ToArrayAsync();
-                    data.Add(elem, selectResultTask);
-                }
-
-                await Task.WhenAll(
-                    data.Values.ToArray()
-                    );
-            }
-            finally 
-            {
-                contexts.ForEach(
-                    e => e.Dispose()
-                    );
-            }
-
-            var result = data
-                .ToDictionary(
-                    e => e.Key, 
-                    e => e.Value.Result
-                    );
-            return result;
-        }
-
-        static void Main(string[] args)
+        static async Task Main(string[] args)
         {
             //var connectionString = @"";
 
@@ -109,28 +29,41 @@ namespace Calibre
             //{
             //    var books = context.Books
             //        .OrderBy(e => e.Id)
-            //        .ToArray();
+            //       .ToArray();
             //}
 
+            //await SearchByTitleInDirectory(
+            //    new DirectoryInfo(@"S:\BooksText\Calibre"),
+            //    "Python Guide"
+            //    );
 
-            SearchByTitleInDirectory(
-                new DirectoryInfo(@""),
-                ""
-                )
-                .GetAwaiter()
-                .GetResult();
+
+            //await SearchInPdfText(
+            //    new DirectoryInfo(@"S:\BooksText\Calibre"),
+            //    "NumPy"
+            //    );
+
+            await LoadToDb(
+                new DirectoryInfo(@"S:\BooksText\Calibre")
+                );
         }
 
 
-        static async Task SearchByTitleInDirectory(
-            DirectoryInfo directory, 
-            string containsTitle
+        static async Task<BookWithLibraryContainer[]> SearchByTitleInDirectory(
+            DirectoryInfo directory,
+            string containsTitle,
+            bool printLine = true
             )
         {
             var watch = Stopwatch.StartNew();
+            var searchProvider = new SearchProvider(
+                new SimplePdfReader()
+                );
 
-            var dbFiles = SelectDbFiles(directory);
-            var searchResult = await SearchBooksByTitle(dbFiles, "")
+            var libraries = Library.SearchAll(directory);
+            var searchResult = await searchProvider.GetBooksByLibrariesAsync(
+                libraries: libraries
+                )
                 .ConfigureAwait(false);
             watch.Stop();
 
@@ -140,60 +73,142 @@ namespace Calibre
                 .ToArray();
 
             var allTags = allBooks
-                .SelectMany(e => e.Tags)
+                .SelectMany(e => e.Book.Tags)
                 .Select(e => e.TagItem)
                 .Distinct()
                 .ToHashSet();
 
-            var searchIgnoreCase = allBooks
+            var result = allBooks;
+
+            if (!string.IsNullOrEmpty(containsTitle))
+            {
+                result = allBooks
                 .Where(
-                    e => 
+                    e =>
                         //Заголовок
-                        e.Title.Contains(containsTitle, StringComparison.OrdinalIgnoreCase)
+                        e.Book.Title.Contains(containsTitle, StringComparison.OrdinalIgnoreCase)
                         //Теги
-                        || e.Tags.Any(
+                        || e.Book.Tags.Any(
                                 e => e.TagItem.Name.Contains(containsTitle, StringComparison.OrdinalIgnoreCase)
                                 )
                     )
                 .ToArray();
+            }
 
-            foreach (var elem in searchIgnoreCase)
+            if (printLine)
             {
-                Console.WriteLine($"{elem.Title}|{elem.AuthorSort}");
+                foreach (var elem in result)
+                {
+                    Console.WriteLine($"{elem.Library.Data.Name}|{elem.Book.Title}|{elem.Book.AuthorSort}");
+                }
             }
 
-            var book = searchIgnoreCase.First();
+            return result;
+        }
 
-            var dbFile = searchResult
-                .First(
-                    e => e.Value.Any(e2 => e2.Title == book.Title)
-                )
-                .Key;
 
-            BookFileProvider bookFileProvider = new BookFileProvider();
-            //var path = await bookFileProvider.BuildFilePathAsync(
-            //    Path.GetDirectoryName(dbFile),
-            //    book,
-            //    (b) => Task.FromResult(b.FileData.First())
-            //    );
+        static async Task<Dictionary<BookWithLibraryContainer, string>> SearchInPdfText(
+            DirectoryInfo directory,
+            string containsText = null,
+            string containsTitle = null,
+            bool printLine = true
+            )
+        {
+            var searchProvider = new SearchProvider(
+                new SimplePdfReader()
+                );
+
+            var books = await SearchByTitleInDirectory(directory, containsTitle, false);
+            books = books
+                .Where(
+                    e => e.Book.FileData
+                        .Any(
+                            e2 => string.Equals(e2.Format, "pdf", StringComparison.OrdinalIgnoreCase)
+                            )
+                )
+                .ToArray();
 
-            var bookText = await bookFileProvider.ReadFileAsync(
-                    Path.GetDirectoryName(dbFile),
-                    book,
-                    (b) => Task.FromResult(b.FileData.First()),
-                    async (s) =>
+            var result = await searchProvider.GetTextByBooksAsync(
+                books,
+                true,
+                (item, text) =>
+                {
+                    if (string.IsNullOrEmpty(containsText))
                     {
-                        SimplePdfReader simplePdfReader = new SimplePdfReader();
-                        var result = await simplePdfReader.ReadPdfAsync(s);
-                        return result;
+                        return true;
                     }
+                    return text.Contains(containsText, StringComparison.OrdinalIgnoreCase);
+                }
+                );
+
+            if (printLine)
+            {
+                foreach (var elem in result)
+                {
+                    Console.WriteLine($"{elem.Key.Library.Data.Name}|{elem.Key.Book.Title}|{elem.Key.Book.AuthorSort}");
+                }
+            }
+
+            return result;
+        }
+
+
+        static async Task LoadToDb(
+            DirectoryInfo directory,
+            string containsText = null,
+            string containsTitle = null,
+            bool printLine = true
+            )
+        {
+            var searchProvider = new SearchProvider(
+                new SimplePdfReader()
                 );
 
-            var booksWithPdf = allBooks
+            var books = await SearchByTitleInDirectory(directory, containsTitle, false);
+            books = books
                 .Where(
-                    e => e.FileData.Any(e2 => e2.Format == "PDF")
+                    e => e.Book.FileData
+                        .Any(
+                            e2 => string.Equals(e2.Format, "pdf", StringComparison.OrdinalIgnoreCase)
+                            )
                 )
                 .ToArray();
+
+
+            await searchProvider.GetTextByBooksAsync(
+                books,
+                true,
+                (item, text) =>
+                {
+                    if (!string.IsNullOrEmpty(containsText))
+                    {
+                        if (!text.Contains(containsText, StringComparison.OrdinalIgnoreCase))
+                        {
+                            return false;
+                        }
+                    }
+                    using (ApplicationContext context = new ApplicationContext())
+                    {
+
+                        context.Books.Add(                                
+                            new BookTextData()
+                            {
+                                Directory = item.Library.Data.FullName,
+                                Text = text,
+                                Title = item.Book.Title
+                            }                                
+                            );
+                        context.SaveChanges();
+                    }
+
+                    if (printLine)
+                    {
+                        Console.WriteLine($"{item.Library.Data.Name}|{item.Book.Title}|{item.Book.AuthorSort}|{text?.Length}");
+                    }
+
+                    return false;
+                }
+                );
         }
     }
 }
diff --git a/src/LibraryText/ApplicationContext.cs b/src/LibraryText/ApplicationContext.cs
new file mode 100644
index 0000000..326b088
--- /dev/null
+++ b/src/LibraryText/ApplicationContext.cs
@@ -0,0 +1,26 @@
+using System;
+
+using Microsoft.EntityFrameworkCore;
+
+using LibraryText.Entities;
+
+namespace LibraryText
+{
+    public class ApplicationContext 
+        : DbContext
+    {
+        public DbSet<BookTextData> Books { get; set; }
+
+
+        public ApplicationContext()
+            : base()
+        {
+            Database.EnsureCreated();   // создаем базу данных при первом обращении
+        }
+
+        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+        {
+            optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Books;Trusted_Connection=True;MultipleActiveResultSets=true");
+        }
+    }
+}
diff --git a/src/LibraryText/Entities/BookTextData.cs b/src/LibraryText/Entities/BookTextData.cs
new file mode 100644
index 0000000..e43be4b
--- /dev/null
+++ b/src/LibraryText/Entities/BookTextData.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LibraryText.Entities
+{
+    public class BookTextData
+    {
+        public int Id { set; get; }
+        public string Directory { set; get; }
+        public string Title { set; get; }
+        public string Text { set; get; }
+    }
+}
diff --git a/src/LibraryText/LibraryText.csproj b/src/LibraryText/LibraryText.csproj
new file mode 100644
index 0000000..7664622
--- /dev/null
+++ b/src/LibraryText/LibraryText.csproj
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net5.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.10" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.10" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Tools.PdfProvider/ImmutableProxy.cs b/src/Tools.PdfProvider/ImmutableProxy.cs
new file mode 100644
index 0000000..ee75db2
--- /dev/null
+++ b/src/Tools.PdfProvider/ImmutableProxy.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tools.PdfProvider
+{
+    public class ImmutableProxy<T>
+    {
+        public T Data { private set; get; }
+
+
+        public ImmutableProxy(T data)
+        {
+            Data = data;
+        }
+        public static ImmutableProxy<T> Create<T>(T data)
+        {
+            return new ImmutableProxy<T>(data);
+        }
+
+
+        public ImmutableProxy<T> MutateValue(Func<T, T> mutateFunc)
+        {
+            Data = mutateFunc(Data);
+            return this;
+        }
+
+
+        #region override
+
+        public override bool Equals(object obj)
+        {
+            return Data.Equals(obj);
+        }
+
+        public override int GetHashCode()
+        {
+            return Data.GetHashCode();
+        }
+
+        public override string ToString()
+        {
+            return Data.ToString();
+        }
+
+        #endregion
+    }
+}
diff --git a/src/Tools.PdfProvider/SimplePdfReader.cs b/src/Tools.PdfProvider/SimplePdfReader.cs
index a429748..1493589 100644
--- a/src/Tools.PdfProvider/SimplePdfReader.cs
+++ b/src/Tools.PdfProvider/SimplePdfReader.cs
@@ -11,36 +11,53 @@ namespace Tools.PdfProvider
 {
     public class SimplePdfReader
     {
-        public Task<string> ReadPdfAsync(Stream stream)
+        public Task<string> ReadPdfAsync(
+            Stream stream, 
+            bool ignoreError
+            )
         {
-            StringBuilder text = new StringBuilder();
-
-            using (PdfReader iTextReader = new PdfReader(stream))
-            using (PdfDocument pdfDoc = new PdfDocument(iTextReader))
+            try
             {
-                int numberofpages = pdfDoc.GetNumberOfPages();
-                for (int page = 1; page <= numberofpages; page++)
+
+                StringBuilder text = new StringBuilder();
+
+                using (PdfReader iTextReader = new PdfReader(stream))
+                using (PdfDocument pdfDoc = new PdfDocument(iTextReader))
                 {
-                    ITextExtractionStrategy strategy = new SimpleTextExtractionStrategy();
-                    string currentText = PdfTextExtractor.GetTextFromPage(
-                        pdfDoc.GetPage(page), 
-                        strategy
-                        );
-
-                    //currentText = Encoding.UTF8.GetString(
-                    //    ASCIIEncoding.Convert(
-                    //        Encoding.Default,
-                    //        Encoding.UTF8,
-                    //        Encoding.Default.GetBytes(currentText)
-                    //        )
-                    //    );
-                    text.Append(currentText);
+                    int numberofpages = pdfDoc.GetNumberOfPages();
+                    for (int page = 1; page <= numberofpages; page++)
+                    {
+                        ITextExtractionStrategy strategy = new SimpleTextExtractionStrategy();
+                        string currentText = PdfTextExtractor.GetTextFromPage(
+                            pdfDoc.GetPage(page),
+                            strategy
+                            );
+
+                        //currentText = Encoding.UTF8.GetString(
+                        //    ASCIIEncoding.Convert(
+                        //        Encoding.Default,
+                        //        Encoding.UTF8,
+                        //        Encoding.Default.GetBytes(currentText)
+                        //        )
+                        //    );
+                        text.Append(currentText);
+                    }
                 }
+
+
+                return Task.FromResult(
+                    text.ToString()
+                    );
             }
+            catch (Exception ex)
+            {
+                if (ignoreError)
+                {
+                    return Task.FromResult(ex.Message);
+                }
 
-            return Task.FromResult(
-                text.ToString()
-                );
+                throw;
+            }
         }
     }
 }