diff --git a/EntityFramework/01-01_Introduction.dib b/EntityFramework/01-01_Introduction.dib new file mode 100644 index 0000000..c963afc --- /dev/null +++ b/EntityFramework/01-01_Introduction.dib @@ -0,0 +1,37 @@ +#!meta + +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"name":"csharp"}]}} + +#!markdown + +# Introduction + +Throughout these Notebooks, we will use several external libraries. + +#!markdown + +## Install Entity Framework Core + +To install EF Core, install the package for the EF Core database provider(s) you want to target. This tutorial uses **SQLite**. + +> Run the code block to load the package: We will need to load the package at the beginning of each Notebook. + +#!csharp + +// <- Press Execute to install the SQLite EF Nuget Package +#r "nuget: Microsoft.EntityFrameworkCore.Sqlite" + +#!markdown + +## Code First or DataBase First? + +Entity Framework will use a model of the database, that our application will *consume*. +There are several approaches to obtain it + +### Code First + +We will define the Objects that we will manipulate via classes, and we will create the database from this model + +### DataBase First + +The database exists before the development of the application, and we will use tools that will *generate* the corresponding classes. diff --git a/EntityFramework/01-02_CodeFirst.dib b/EntityFramework/01-02_CodeFirst.dib new file mode 100644 index 0000000..b851394 --- /dev/null +++ b/EntityFramework/01-02_CodeFirst.dib @@ -0,0 +1,184 @@ +#!meta + +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"languageName":"csharp","name":"csharp"},{"aliases":[],"languageName":"X#","name":"xsharp"}]}} + +#!markdown + +# Code First + +> **Prerequisites** +Remember to load **NuGet** packages before running the code cells below. + +#!markdown + +# Load the *XSharp Language kernel*, and *SQLite EF package* + +#!csharp + +// <-= Press on the Arrow to run Me +#r "nuget:Microsoft.EntityFrameworkCore.Sqlite" +#r "nuget:XSharpInteractive" + +#!markdown + + +

Warning !! Dirty Hack !!

+As XSharp scripting engine doesn't support Nuget package load for now, we will have to force the load of the .DLL itself in the XSharp Interactive memory : Entity Framework and the SQLite version
+When you load a package, it is placed in your profile folder in the path :
+
+"C:\Users\fabri\.nuget\packages\microsoft.entityframeworkcore.sqlite.core\8.0.8\lib\net8.0\Microsoft.EntityFrameworkCore.Sqlite.dll" +"C:\Users\fabri\.nuget\packages\microsoft.entityframeworkcore\8.0.8\lib\net8.0\Microsoft.EntityFrameworkCore.dll" +
+In the following Notebooks, don't forget to adapt the path to **your** context. +
+ +#!xsharp + +// Load the DLL in the XSharpInteractive context +#r "C:\Users\fabri\.nuget\packages\microsoft.entityframeworkcore\8.0.8\lib\net8.0\Microsoft.EntityFrameworkCore.dll" +#r "C:\Users\fabri\.nuget\packages\microsoft.entityframeworkcore.sqlite.core\8.0.8\lib\net8.0\Microsoft.EntityFrameworkCore.Sqlite.dll" + +#!markdown + +## Table Model + +In the following example, we will create an application to manage an address book. +We will define the model in a "simple" class to handle our contacts. + +The definition of the class specifies the structure of the table in which we will store our information. + +Identifying a field with the *suffix* **Id** indicates that it is a **primary** key, in **Auto Increment**, and that cannot be **NULL**, this is the case for **ContactId**. + +#!xsharp + +CLASS Contact + PUBLIC PROPERTY ContactId AS INT AUTO + PUBLIC PROPERTY Name AS STRING AUTO + PUBLIC PROPERTY FirstName AS STRING AUTO + PUBLIC PROPERTY Adress AS STRING AUTO + PUBLIC PROPERTY ZipCode AS STRING AUTO + PUBLIC PROPERTY City AS STRING AUTO + PUBLIC PROPERTY Phone AS STRING AUTO + PUBLIC PROPERTY EMail AS STRING AUTO +END CLASS + +#!markdown + +## Database Structure + +### DbContext + +The **DbContext** class is an part of Entity Framework. A DbContext instance represents a session with the database that can be used to query and save instances of your entities to a database. + +**DbContext** in EF Core allows us to do the following: + +- Manage the database connection +- Configure the model and relationship +- Query the database +- Save data to the database +- Configure change tracking +- Caching +- Transaction management + +To use **DbContext** in our application, we need to create a class derived from **DbContext**: Here we will define an "abstraction" of our **Adress Book** DB. + +### DbSet + +The **DbSet\** type allows EF Core to query and save objects of the specified entity in the database. +**LINQ** queries (we'll talk about **LINQ** a bit later) on a **DbSet\** will be translated into database queries. +The ***EF Core API*** will create the *Contacts* table in the underlying SQLite database where each property of this class will be a column in the corresponding table. + +#!xsharp + +using Microsoft.EntityFrameworkCore +using System +using System.Collections.Generic + +PUBLIC CLASS AddressBookContext INHERIT DbContext + PUBLIC PROPERTY Contacts AS DbSet AUTO + + PUBLIC PROPERTY DbPath AS STRING AUTO GET + + PUBLIC CONSTRUCTOR() + // We are looking for the FullPath definition of your "Documents" Folder. + // First get a 'System.Environment.SpecialFolder' object + var folder := Environment.SpecialFolder.MyDocuments + // Get the String for this folder + var path := Environment.GetFolderPath(folder) + // Set the FullPath for the SQLite DB file + DbPath := System.IO.Path.Join(path, "addressBook.db") + END CONSTRUCTOR + + // On va configurer EF pour creer une BDD Sqlite + // dans le dossier "Mes Documents". + PROTECTED OVERRIDE METHOD OnConfiguring( options AS DbContextOptionsBuilder ) AS VOID + options:UseSqlite(i"Data Source={DbPath}") + END METHOD +END CLASS + +#!markdown + +The `OnConfiguring()` method allows us to define the connection string to the database. In the case of SQLite, this allows us to specify the path to the file that contains the database. + +The creation of the `AddressBookContext` type object will define the structure of the database, its tables and their relationships. + +EF also provides us with tools to create it, via the call to `Database.EnsureCreated()`. + +#!xsharp + +using System +using System.Linq + +var db := AddressBookContext{} + +Console.WriteLine(i"FullPath of DB: {db:DbPath}"); +// Please, create the DB if necessary +db:Database:EnsureCreated() + +#!markdown + +> **!!! WARNING !!!** +If you modify the database structure, do not forget to delete the file to force its creation at the next launch. + + +#!markdown + +### DbSet Add or DbContext Add? +The `Add()` method will allow us to add an object to the data set (the table) named **Contacts**. +But it is also possible to call `Add()` on the RDB management object, here **AddressBookContext**: It is the type of the passed object that will determine the table to manipulate. + +### DbContext SaveChanges +To apply the modification made, simply call the `SaveChanges()` method on the **AddressBookContext** object, and EF will generate and execute the queries necessary for the update. + +#!xsharp + +// Create a Contact +? "Add a Contact" +// Create a Contact Object +var someBody := Contact{} +someBody:Name := "Foray" +someBody:FirstName := "Fabrice" +// Add to the DbSet +db:Contacts:Add( someBody ) +// But you can also add it to the DbContext +// db:Add( someBody ) +// and Write to the DataBase +db:SaveChanges() + +#!markdown + +Write the code block to add your information to the database, and do the same with the information of your closest friends. + +#!xsharp + +// Add a Contact + +#!markdown + +And to display all the elements of a table, simply browse the corresponding **DbSet**: + +#!xsharp + +FOREACH VAR unContact IN db:Contacts + ? i"{unContact:ContactId} - {unContact:Name} {unContact:FirstName}" +NEXT diff --git a/EntityFramework/01-03_Linq.dib b/EntityFramework/01-03_Linq.dib new file mode 100644 index 0000000..752f72d --- /dev/null +++ b/EntityFramework/01-03_Linq.dib @@ -0,0 +1,234 @@ +#!meta + +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"languageName":"csharp","name":"csharp"},{"aliases":[],"languageName":"X#","name":"xsharp"}]}} + +#!markdown + +## LINQ + +Entity Framework allows you to manipulate the underlying database, without building queries. +But we're going to need to express the manipulations we want to do on the object model, and that's where **LINQ** comes in. + +Language-Integrated Query (**LINQ**) is the name of a set of technologies based on embedding query functionality directly into the C# language. +Traditionally, queries over data are expressed as simple strings without compile-time type checking or IntelliSense support. +In addition, you have to learn a different query language for each type of data source: SQL databases, XML documents, various web services, etc. + +With **LINQ**, a query is a first-class language construct, just like classes, methods, and events. + +When writing queries, the most visible "language-integrated" part of **LINQ** is the query expression. Query expressions are written in declarative query syntax. +Using query syntax, you perform filtering, ranking, and grouping operations on data sources with minimal code. You use the same query expression patterns to query and transform data from any type of data source. + +#!markdown + +# Loading XSharpInteractive and all the packages + +#!csharp + +// <-= Press on the Arrow to run Me +#r "nuget:Microsoft.EntityFrameworkCore.Sqlite" +#r "nuget:XSharpInteractive" + +#!xsharp + +// Load the DLL in the XSharpInteractive context +#r "C:\Users\fabri\.nuget\packages\microsoft.entityframeworkcore\8.0.8\lib\net8.0\Microsoft.EntityFrameworkCore.dll" +#r "C:\Users\fabri\.nuget\packages\microsoft.entityframeworkcore.sqlite.core\8.0.8\lib\net8.0\Microsoft.EntityFrameworkCore.Sqlite.dll" + +#!markdown + +### Redefinition of the elements of the previous Notebook + +#!xsharp + +using Microsoft.EntityFrameworkCore +using System +using System.Linq +using System.Collections.Generic + + +CLASS Contact + PUBLIC PROPERTY ContactId AS INT AUTO + PUBLIC PROPERTY Name AS STRING AUTO + PUBLIC PROPERTY FirstName AS STRING AUTO + PUBLIC PROPERTY Adress AS STRING AUTO + PUBLIC PROPERTY ZipCode AS STRING AUTO + PUBLIC PROPERTY City AS STRING AUTO + PUBLIC PROPERTY Phone AS STRING AUTO + PUBLIC PROPERTY EMail AS STRING AUTO +END CLASS + +PUBLIC CLASS AddressBookContext INHERIT DbContext + PUBLIC PROPERTY Contacts AS DbSet AUTO + + PUBLIC PROPERTY DbPath AS STRING AUTO GET + + PUBLIC CONSTRUCTOR() + // We are looking for the FullPath definition of your "Documents" Folder. + // First get a 'System.Environment.SpecialFolder' object + var folder := Environment.SpecialFolder.MyDocuments + // Get the String for this folder + var path := Environment.GetFolderPath(folder) + // Set the FullPath for the SQLite DB file + DbPath := System.IO.Path.Join(path, "addressBook.db") + END CONSTRUCTOR + + // On va configurer EF pour creer une BDD Sqlite + // dans le dossier "Mes Documents". + PROTECTED OVERRIDE METHOD OnConfiguring( options AS DbContextOptionsBuilder ) AS VOID + options:UseSqlite(i"Data Source={DbPath}") + END METHOD +END CLASS + + + +var db := AddressBookContext{} + +Console.WriteLine(i"FullPath of DB: {db:DbPath}"); +// Please, create the DB if necessary +db:Database:EnsureCreated() + +#!markdown + +### Query + +For example, we can query the Contacts table by sorting the people by their ID, and here by retrieving the first element. +To do this, we use the `First()` method, but in this case we must check that the database is not empty. The other solution is to use the `FirstOrDefault()` method. We can also retrieve the last using the `Last()` or `LastOrDefault()` method. + +#!xsharp + +// Reading +? "Searching a Contact..." +var searchID := 0 +var unContact := db:Contacts:OrderBy( { c => c:ContactId } ):FirstOrDefault() + +IF unContact != null + ? i"Id - Nom : {unContact:ContactId} - {unContact:Name}" + searchID := unContact:ContactId +ELSE + ? "Contacts Table is empty." +ENDIF + +#!markdown + +Modify the above code by sorting by Name, and display the first and last retrieved objects. + +> If the database is empty, use the elements of the previous Notebook to add contacts. + +#!xsharp + +// Display first, last, all sorted by Name + +#!markdown + +### Selection by criteria + +You can also build more elaborate LINQ queries. + +The strength of LINQ is that it is possible to use two syntaxes: **Query** or **Method** (We also speak of **_FLUENT_** syntax) + +**Query Version** +> from \ in \ +> \ \ +> \select or groupBy \> \ + +#!markdown + +For example, browse a table by specifying a condition with `where`, and `select` the objects corresponding to this condition, as in the example below. + +> In fact, we build the query, and it is only executed when we want to read the result + +#!xsharp + +// The first line could be : LOCAL contactQuery AS IEnumerable +var contactQuery := from contact in db:Contacts ; + where contact:Name == "Foray" ; + select contact +? "Query Version" +FOREACH VAR oneContact IN contactQuery + ? i"Id - Name : {oneContact:ContactId} - {oneContact:Name}" +NEXT + +#!markdown + +Here is the organization of the LINQ query: +![Query Syntax](.\assets\linq-query-syntax.jpg) + +There are about **50** standard query operators that LINQ supports. +You can find more information on the [Microsoft Learn](https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/ef/language-reference/standard-query-operators-in-linq-to-entities-queries#join-methods) site + +#!markdown + +**Method Version (FLUENT)** +This syntax uses the principle of *extension methods*, which are special methods: +They do not belong directly to the class concerned, but their syntax indicates to the compiler that they add functionalities to a class: The EntityFramework package therefore adds these methods to the collections (among others). +> We will not detail here the syntax and the functioning of *extension methods* + +Here is the organization in **FLUENT** syntax +![Query Syntax](.\assets\linq-query-syntax-fluent.jpg) + +#!xsharp + +var contactQuery2 := db:Contacts:Where( { contact => contact:Name:ToLower():Contains("a") } ):ToList() +? "FLUENT Version" +FOREACH VAR oneContact IN contactQuery2 + ? i"Id - Name : {oneContact:ContactId} - {oneContact:Name}" +NEXT + +#!markdown + +## Update + +We now know how to add objects to the DataBase, and we know how to query this database to display certain elements according to criteria, and even to sort the results. +But then, do you want to make modifications?? The question is quickly answered! + +With Entity Framework, the question does not arise! :) + +When you manipulate an object, you are in direct connection with the BDD, but the **DbContext** hides all the complexity of manipulation. +So you just have to modify the properties of an object, and do a `SaveChanges()`! + +#!xsharp + +// Update +? "Update a Contact" +var oneContact := db:Contacts ; + :OrderBy( { c => c:ContactId } ); + :FirstOrDefault() +IF oneContact != null + oneContact:ZipCode := "07200" + oneContact:City := "Aubenas" + db:SaveChanges() + ? "Update Saved" +ENDIF + +#!markdown + +This code block allows you to modify the first object stored in your database. + +### Exercise +Check that you have at least 4 people in your database. +And modify them according to their `ContactId`: put different values ​​for even and odd numbers, then save the changes. +Finally, redisplay your entire address book. + +#!xsharp + +// Modify, Update and Print + +#!markdown + +## Delete + +If you have understood how Entity Framework works, you will have probably guessed that deleting an object in the DB works on the same principle as modifying it. +So we will delete the object that interests us using the DbContext, then update the DB using the `SaveChanges()` method. + +#!xsharp + +// Delete +? "Delete a Contact" +var oneContact := db:Contacts ; + :OrderBy( { c => c:ContactId } ); + :FirstOrDefault() +IF oneContact != null + db:Remove(oneContact) + db:SaveChanges() + ? "Contact Deleted" +ENDIF diff --git a/EntityFramework/01-04_DatabaseFirst.dib b/EntityFramework/01-04_DatabaseFirst.dib new file mode 100644 index 0000000..ac49890 --- /dev/null +++ b/EntityFramework/01-04_DatabaseFirst.dib @@ -0,0 +1,133 @@ +#!meta + +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"languageName":"csharp","name":"csharp"},{"aliases":[],"languageName":"X#","name":"xsharp"}]}} + +#!markdown + +# Database First + +We will now see how to obtain the Entity Framework object model from an existing database. + +> Unfortunately, we will not be able to do the manipulations *inside* the Notebook, but you will have to do a number of things from the command line. + +### **Prerequisites** +To be able to generate code, we must install the **Entity Framework Core tools**. +To do this, open a command prompt (**cmd**) or a **Terminal** or run a **Terminal panel** from VSCode, and type the following command: +> dotnet tool install --global dotnet-ef + +If you have a message indicating that `dotnet-ef is already installed`, we will check that you have the latest version. +To do this, type the following command: +> dotnet tool update --global dotnet-ef + +#!markdown + +## Creating a Library + +1. Open a command prompt (**cmd**) or a **Terminal** or run a **Terminal panel** from VSCode, and go to a working folder (eg C:\Temp) + +2. Create a folder named **DataLib**, and move with **cd** commands to this folder + +3. Type the following command: +> dotnet new classlib + +The system will create a *Class Library application* that is using **C# language** named `DataLib` with a `Class1.cs` file and a project file named `DataLib.csproj`. + +4. Now type the following command: +> dotnet add package Microsoft.EntityFrameworkCore.Design + +The system will add the **NuGet** dependencies of **Entity Framework** to your project. + +5. Type the command that will add SQLite support to **Entity Framework** +> dotnet add package Microsoft.EntityFrameworkCore.Sqlite + +Our project is ready to be used, but do not close the command window yet, we will have another manipulation to do. + +#!markdown + +## Adding the database definition + +1. Copy the **AddressBook.db** file (it should be in your *Documents* folder) created in the previous Notebooks into the **DataLib** folder. + +2. Return to the command window, and type the following command +> dotnet ef dbcontext scaffold "data source=addressBook.db" Microsoft.EntityFrameworkCore.Sqlite + +This command will generate the elements needed to manipulate our database with **Entity Framework**: +- a *class* called **AddressBookContext** *inheriting* from **DbContext** +- a *class* containing the definition of the **Contact** table +- All classes are inside a *namespace* called **DataLib** (the name of the library) + +Here is how the command is composed: +- `dotnet` executes the command linked to .NET +- `ef` specifies that we will use the **Entity Framework** tools +- `dbcontext` indicates that we will use the commands linked to the **DbContext** +- `scaffold` specifies that we want generate classes for the `data source` specified after. Here it is a SQLite connection string, but you can specify a connection string for other types of DB +- `Microsoft.EntityFrameworkCore.Sqlite` specifies the DB engine that must be used to analyze the DB. + +We could have added : +- `--output-dir` specifies in which folder the classes will be generated. +For eg with `--output-dir DataModel`, the `DataModel` folder that will be created, the generated code will be stored there and the generated classes will be contained in a *namespace* called **DataLib.DataModel**. + +In the end, the DLL has been generated in ***C:\temp\DataLib\bin\Debug\net8.0***, remember this path... + +3. Return to the command window, and type the following command +> dotnet build + +#!markdown + +## Test the DB Access + +1. First, remember to install & load the needed NuGet packages + +#!csharp + +// <-= Press on the Arrow to run Me (in CSharp Context) +#r "nuget:Microsoft.EntityFrameworkCore.Sqlite" +#r "nuget:XSharpInteractive" + +#!xsharp + +// Load the DLL in the XSharpInteractive context (in XSharp Context) +#r "C:\Users\fabri\.nuget\packages\microsoft.entityframeworkcore\8.0.8\lib\net8.0\Microsoft.EntityFrameworkCore.dll" +#r "C:\Users\fabri\.nuget\packages\microsoft.entityframeworkcore.sqlite.core\8.0.8\lib\net8.0\Microsoft.EntityFrameworkCore.Sqlite.dll" + +#!markdown + +2. Now, let's load the DataLib.DLL in the XSharp Context + +#!xsharp + +#r "C:\temp\DataLib\bin\Debug\net8.0\DataLib.DLL" + +#!markdown + +3. Try to create an AddressBookContext object. + +#!xsharp + +using DataLib + +var db := AddressBookContext{} +// Please, create the DB if necessary +db:Database:EnsureCreated() + +#!markdown + +You should see a file called `addressBook.db` appearing in your NoteBook folder. + +Try to enumerate all the Contacts : + +#!xsharp + +FOREACH VAR unContact IN db:Contacts + ? i"{unContact:ContactId} - {unContact:Name} {unContact:FirstName}" +NEXT + +#!markdown + +> It is possible that no contact is displayed .... + +Think about why, and fix the problem ;) + +#!xsharp + +// Manage your Database here diff --git a/EntityFramework/assets/linq-query-syntax-fluent.jpg b/EntityFramework/assets/linq-query-syntax-fluent.jpg new file mode 100644 index 0000000..d9702a6 Binary files /dev/null and b/EntityFramework/assets/linq-query-syntax-fluent.jpg differ diff --git a/EntityFramework/assets/linq-query-syntax.jpg b/EntityFramework/assets/linq-query-syntax.jpg new file mode 100644 index 0000000..4118ad6 Binary files /dev/null and b/EntityFramework/assets/linq-query-syntax.jpg differ diff --git a/EntityFramework/assets/linq-query-syntax.pdn b/EntityFramework/assets/linq-query-syntax.pdn new file mode 100644 index 0000000..ffb7ee8 Binary files /dev/null and b/EntityFramework/assets/linq-query-syntax.pdn differ diff --git a/README.md b/README.md index 731f3c1..07a113b 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ I hope you will enjoy your journey with **XSharp Interactive** ! | -------- |--- | ------- |---| | [First Steps](./FirstSteps/00-Index.ipynb) | Start learning X# language | Core | Beginner | [Simple SQL](./SimpleSQL/00-Index.dib) | Using SQLite in your X# Application, and change for MariaDB, PostgreSQL, ... | Core | Intermediate +| [Entity Framework](./EntityFramework/01-01_Introduction.dib) | How to access your DB with an ORM ? Try Entity Framework with X# ! | Core | Intermediate | [First Steps VFP](./WorkInProgress.ipynb) | Start X# with your VFP background | VFP | Beginner