├── src ├── unit3 │ ├── lesson1 │ │ ├── media │ │ │ ├── revisions.png │ │ │ ├── enabling_revisions.png │ │ │ └── j3j2jdk3ksdk2kfdk2kk23v23.PNG │ │ └── README.md │ ├── lesson6 │ │ ├── media │ │ │ ├── big_orders.png │ │ │ └── new_subscription_task.png │ │ └── README.md │ ├── lesson4 │ │ └── README.md │ ├── lesson5 │ │ └── README.md │ ├── lesson2 │ │ └── README.md │ └── lesson3 │ │ └── README.md ├── unit4 │ ├── lesson1 │ │ ├── media │ │ │ ├── max_cores.png │ │ │ ├── second-node.png │ │ │ ├── already_cluster.png │ │ │ ├── cluster_ready.png │ │ │ └── raven-second-node.png │ │ └── README.md │ ├── lesson2 │ │ ├── media │ │ │ ├── manage_group.png │ │ │ ├── new_database.png │ │ │ ├── database_remote.png │ │ │ └── status_cluster.png │ │ └── README.md │ └── lesson3 │ │ └── README.md ├── unit2 │ ├── lesson3 │ │ ├── media │ │ │ └── unit2-multi-map-output.png │ │ └── README.md │ ├── lesson2 │ │ ├── media │ │ │ ├── g82uh2j3n4h3ndj3ndj2jd2.png │ │ │ └── g82uh2jlj43rj3ndj3ndj2jd2.png │ │ └── README.md │ ├── lesson4 │ │ ├── media │ │ │ └── a92uh2jlj43rj3ndj3ndj2jd2.png │ │ └── README.md │ ├── lesson1 │ │ ├── media │ │ │ └── a5s6d678fdsfdfsf8768s7fsdf786876a.png │ │ └── README.md │ ├── lesson6 │ │ └── README.md │ ├── lesson7 │ │ └── README.md │ └── lesson5 │ │ └── README.md └── unit1 │ ├── lesson2 │ ├── media │ │ └── 23k4h1k2j4hk24kh12khj243.png │ └── README.md │ ├── lesson1 │ ├── media │ │ ├── 26de5d4d9b2cf6a0f8867677aa776b45.png │ │ ├── 3f24692d124b788b08cb11e49d8fb66f.png │ │ ├── 3f7ec9fbf9d626ebbe905e7a589e81ed.png │ │ ├── 4bcc55018cd05b354a0d98c3ce7bcfb7.png │ │ └── d1ff71a639f63e04488b56706a91f423.png │ └── README.md │ ├── lesson4 │ └── README.md │ ├── lesson5 │ ├── NorthwindModels.cs │ └── README.md │ ├── lesson6 │ └── README.md │ ├── lesson3 │ └── README.md │ └── lesson7 │ └── README.md ├── .gitattributes ├── README.md └── .gitignore /src/unit3/lesson1/media/revisions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit3/lesson1/media/revisions.png -------------------------------------------------------------------------------- /src/unit3/lesson6/media/big_orders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit3/lesson6/media/big_orders.png -------------------------------------------------------------------------------- /src/unit4/lesson1/media/max_cores.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit4/lesson1/media/max_cores.png -------------------------------------------------------------------------------- /src/unit4/lesson1/media/second-node.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit4/lesson1/media/second-node.png -------------------------------------------------------------------------------- /src/unit4/lesson2/media/manage_group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit4/lesson2/media/manage_group.png -------------------------------------------------------------------------------- /src/unit4/lesson2/media/new_database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit4/lesson2/media/new_database.png -------------------------------------------------------------------------------- /src/unit4/lesson1/media/already_cluster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit4/lesson1/media/already_cluster.png -------------------------------------------------------------------------------- /src/unit4/lesson1/media/cluster_ready.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit4/lesson1/media/cluster_ready.png -------------------------------------------------------------------------------- /src/unit4/lesson2/media/database_remote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit4/lesson2/media/database_remote.png -------------------------------------------------------------------------------- /src/unit4/lesson2/media/status_cluster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit4/lesson2/media/status_cluster.png -------------------------------------------------------------------------------- /src/unit4/lesson1/media/raven-second-node.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit4/lesson1/media/raven-second-node.png -------------------------------------------------------------------------------- /src/unit3/lesson1/media/enabling_revisions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit3/lesson1/media/enabling_revisions.png -------------------------------------------------------------------------------- /src/unit2/lesson3/media/unit2-multi-map-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit2/lesson3/media/unit2-multi-map-output.png -------------------------------------------------------------------------------- /src/unit3/lesson6/media/new_subscription_task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit3/lesson6/media/new_subscription_task.png -------------------------------------------------------------------------------- /src/unit1/lesson2/media/23k4h1k2j4hk24kh12khj243.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit1/lesson2/media/23k4h1k2j4hk24kh12khj243.png -------------------------------------------------------------------------------- /src/unit2/lesson2/media/g82uh2j3n4h3ndj3ndj2jd2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit2/lesson2/media/g82uh2j3n4h3ndj3ndj2jd2.png -------------------------------------------------------------------------------- /src/unit2/lesson2/media/g82uh2jlj43rj3ndj3ndj2jd2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit2/lesson2/media/g82uh2jlj43rj3ndj3ndj2jd2.png -------------------------------------------------------------------------------- /src/unit2/lesson4/media/a92uh2jlj43rj3ndj3ndj2jd2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit2/lesson4/media/a92uh2jlj43rj3ndj3ndj2jd2.png -------------------------------------------------------------------------------- /src/unit3/lesson1/media/j3j2jdk3ksdk2kfdk2kk23v23.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit3/lesson1/media/j3j2jdk3ksdk2kfdk2kk23v23.PNG -------------------------------------------------------------------------------- /src/unit1/lesson1/media/26de5d4d9b2cf6a0f8867677aa776b45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit1/lesson1/media/26de5d4d9b2cf6a0f8867677aa776b45.png -------------------------------------------------------------------------------- /src/unit1/lesson1/media/3f24692d124b788b08cb11e49d8fb66f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit1/lesson1/media/3f24692d124b788b08cb11e49d8fb66f.png -------------------------------------------------------------------------------- /src/unit1/lesson1/media/3f7ec9fbf9d626ebbe905e7a589e81ed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit1/lesson1/media/3f7ec9fbf9d626ebbe905e7a589e81ed.png -------------------------------------------------------------------------------- /src/unit1/lesson1/media/4bcc55018cd05b354a0d98c3ce7bcfb7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit1/lesson1/media/4bcc55018cd05b354a0d98c3ce7bcfb7.png -------------------------------------------------------------------------------- /src/unit1/lesson1/media/d1ff71a639f63e04488b56706a91f423.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit1/lesson1/media/d1ff71a639f63e04488b56706a91f423.png -------------------------------------------------------------------------------- /src/unit2/lesson1/media/a5s6d678fdsfdfsf8768s7fsdf786876a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravendb/bootcamp/HEAD/src/unit2/lesson1/media/a5s6d678fdsfdfsf8768s7fsdf786876a.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /src/unit2/lesson6/README.md: -------------------------------------------------------------------------------- 1 | # Unit 2, Lesson 6 - Goodbye Transformers, Welcome Server-Side Projections 2 | 3 | In the previous lesson, you learned how to create shorter results from the server 4 | using the `LoadDocument` server-side projections. 5 | 6 | In this lesson, I will show you how to get shape results using server-side projections. 7 | 8 | ## What Happened with Transformers? 9 | 10 | If you know RavenDB 3.5, you are probably looking for how to implement Transformers. 11 | Bad news: They are gone! Good news: You will not miss them. 12 | 13 | Transformers were removed and substituted by a server-side projection support. Methods 14 | like `TransformWith` are no longer available and simple `Select` should be used instead. 15 | 16 | ## Easy Start with Server-Side Projections 17 | 18 | Instead of pulling full documents in query results, you can just grab some pieces of data 19 | from documents. You can also transform the projected results. 20 | 21 | Let me share a short example: 22 | 23 | ```csharp 24 | // request Name, City and Country 25 | // for all entities from 'Companies' collection 26 | var results = session 27 | .Query() 28 | .Select(x => new 29 | { 30 | Name = x.Name, 31 | City = x.Address.City, 32 | Country = x.Address.Country 33 | }) 34 | .ToList(); 35 | ``` 36 | 37 | The related RQL is pretty simple as well: 38 | 39 | ```sql 40 | from Companies 41 | select Name, Address.City as City, Address.Country as Country 42 | ``` 43 | Another example? Here we go: 44 | 45 | ```csharp 46 | var results = (from e in session.Query() 47 | select new 48 | { 49 | FullName = e.FirstName + " " + e.LastName, 50 | }).ToList(); 51 | ``` 52 | 53 | And here it is the RQL: 54 | 55 | ```sql 56 | from Employees as e 57 | select { 58 | FullName : e.FirstName + " " + e.LastName 59 | } 60 | ``` 61 | 62 | ## Getting Some More Advanced Results Using Functions 63 | 64 | Let's do something more complex. 65 | 66 | ```csharp 67 | var results = (from e in session.Query() 68 | let format = 69 | (Func)(p => 70 | p.FirstName + " " + p.LastName) 71 | select new 72 | { 73 | FullName = format(e) 74 | }).ToList(); 75 | ``` 76 | 77 | We just created a function that will run on the server-side. 78 | 79 | Let's look at the RQL. 80 | 81 | ```sql 82 | declare function output(e) { 83 | var format = function(p){ return p.FirstName + " " + p.LastName; }; 84 | return { FullName : format(e) }; 85 | } 86 | from Employees as e select output(e) 87 | ``` 88 | 89 | Yes, we can define functions when coding with RQL! These 90 | functions are pure JavaScript. 91 | 92 | ## Great job! 93 | 94 | To learn more about this functionality, I recommend you to read to read our article that covers 95 | the projection functionality which can be found [here](https://ravendb.net/docs/article-page/4.0/csharp/indexes/querying/projections). 96 | 97 | **Let's move onto [Lesson 7](../lesson7/README.md).** -------------------------------------------------------------------------------- /src/unit3/lesson1/README.md: -------------------------------------------------------------------------------- 1 | # Unit 3, Lesson 1 - Revisions 2 | 3 | Hello and welcome to Unit 3. You already know the basics of using RavenDB. It is time to get started using some more advanced features. 4 | 5 | In this lesson you will learn how to enable document revisions on your databases. 6 | 7 | ## What are Revisions? 8 | 9 | Every time you update, or even delete, a document, RavenDB 4 creates a snapshot (revision) of the previous state. More than that, it's useful when you need to track the history of the documents or when you need a full audit trail.  10 | 11 | You can choose to keep track of the last N revisions. If necessary, you could "track everything". 12 | 13 | ## How to Enable Revisions 14 | 15 | You can configure the revisions feature using the Studio:  16 | 17 | ![Enabling revisions](media/enabling_revisions.png) 18 | 19 | When activated, by default, RavenDB will track the history for all documents. Also, by default, RavenDB will never purge old revisions.  20 | 21 | As the administrator, you can configure this for all collections.  But you could specify a different setup for a specific collection. By default, RavenDB will track history for all documents. 22 | 23 | The options you have are; 24 | 25 | * **Purge on Delete**: delete the revisions upon the document delete. 26 | * **Limit # of revisions**: How much revisions to keep. 27 | * **Limit # of revisions by age**: Configure a minimum retention time before the revisions can be expired. 28 | 29 | You can configure it programmatically following the recommendations from the [online documentation](https://ravendb.net/docs/article-page/4.0/csharp/server/extensions/revisions). 30 | 31 | ## Exercise: Enable revisions for the `Employees` collection 32 | 33 | Let's start enabling revisions to the `Employees` Collection. 34 | 35 | ### Step 1: Open the Northwind database 36 | Open the `RavenDB Management Studio`, then open the Northwind database. 37 | 38 | ### Step 2: Opening the `Document Revisions` form 39 | On the left panel, select the Settings section and then Document Revisions. 40 | 41 | ### Step 3: Set up revisions for the Employees collection 42 | 43 | * On the top right, click on the `Add a collection specific configuration` option. 44 | * Select the Employees collection. 45 | * Enable `Limit # of revisions` and inform that you want to save 10 revisions. 46 | * Click on `OK`. 47 | * Click on `Save` 48 | 49 | ![Limit of revisions](media/j3j2jdk3ksdk2kfdk2kk23v23.PNG) 50 | 51 | You can test revisions editing a document directly on the Studio. Edit any document and see the list of revisions. 52 | 53 | ![Revisions list](media/revisions.png) 54 | 55 | ## Retrieving Revisions from a Document 56 | 57 | Retrieving revisions for a document is pretty simple. 58 | 59 | ```csharp 60 | static void Main(string[] args) 61 | { 62 | using (var session = DocumentStoreHolder.Store.OpenSession()) 63 | { 64 | var revisions = session.Advanced.Revisions 65 | .GetFor("employees/7-A"); 66 | 67 | foreach (var revision in revisions) 68 | { 69 | // process revision 70 | } 71 | } 72 | } 73 | ``` 74 | 75 | ## Great Job! 76 | 77 | **Let's move onto [Lesson 2](../lesson2/README.md).** 78 | -------------------------------------------------------------------------------- /src/unit1/lesson4/README.md: -------------------------------------------------------------------------------- 1 | # Unit 1, Lesson 4 - The Basics of the DocumentStore 2 | 3 | In the previous lessons you have set up RavenDB, explored the Studio, written 4 | some code to connect to RavenDB, pulled data out, and defined typed classes 5 | that allow you to work with RavenDB easily. 6 | 7 | In this lesson, you will understand more about an important member of the Client API: 8 | the `DocumentStore`. 9 | 10 | ## What is the Purpose of the `DocumentStore`? 11 | 12 | You've already used the document store in the previous example. Now it is time 13 | to understand its purpose. 14 | 15 | ````csharp 16 | var documentStore = new DocumentStore 17 | { 18 | Urls = new [] {"http://localhost:8080"}, 19 | Database = "Northwind" 20 | }; 21 | 22 | documentStore.Initialize(); 23 | ```` 24 | 25 | The document store holds the RavenDB URL, the default database, and the certificate 26 | that should be used. 27 | 28 | The document store holds all client-side configuration for RavenDB - how we are 29 | going to serialize entities, how to load balance reads, cache sizes, timeouts, 30 | and much more. 31 | 32 | **In typical applications, you will have a single document store per application.** 33 | 34 | ## Exercise: Moving the `DocumentStore` instance to a singleton class 35 | 36 | This exercise picks up right where previous one in the last lesson left off. 37 | 38 | What you will do to ensure a single document store in your application is to adopt 39 | a typical initialization pattern. 40 | 41 | ### Step 1: Making the Document Store Instance Singleton 42 | 43 | ```csharp 44 | public static class DocumentStoreHolder 45 | { 46 | private static readonly Lazy LazyStore = 47 | new Lazy(() => 48 | { 49 | var store = new DocumentStore 50 | { 51 | Urls = new[] { "http://localhost:8080" }, 52 | Database = "Northwind" 53 | }; 54 | 55 | return store.Initialize(); 56 | }); 57 | 58 | public static IDocumentStore Store => 59 | LazyStore.Value; 60 | } 61 | ``` 62 | The use of Lazy ensures that the document store is only created once, without 63 | having to worry about locking or other thread safety issues. 64 | 65 | ### Step 2: Using the Singleton `DocumentStore` Instance 66 | 67 | Now you can improve your code to use the `DocumentStoreHolder`. 68 | 69 | ```csharp 70 | class Program 71 | { 72 | static void Main() 73 | { 74 | 75 | using (var session = DocumentStoreHolder.Store.OpenSession()) 76 | { 77 | var p = session.Load("products/1-A"); 78 | System.Console.WriteLine(p.Name); 79 | } 80 | } 81 | } 82 | ``` 83 | 84 | ## Introducing Conventions 85 | 86 | An important RavenDB concept is conventions. 87 | 88 | > Conventions are a series of policy decisions that have already been made for you. 89 | > They range from deciding which property holds the document ID, to how the entity 90 | > should be serialized to a document. 91 | 92 | A lot of thought and effort was put into ensuring you won't have to touch 93 | the conventions. But you can do it every time you need to. 94 | 95 | We will not touch RavenDB conventions right now simply because we don't need 96 | to do it. If you want to know more, you can access the [RavenDB conventions documentation](https://ravendb.net/docs/article-page/latest/csharp/client-api/configuration/conventions). 97 | 98 | ## Great Job! 99 | 100 | The fourth lesson is done and you know a lot about the `DocumentStore`. 101 | 102 | **Let's move on to [Lesson 5](../lesson5/README.md) and learn more about how to load documents.** 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RavenDB Bootcamp 2 | 3 | The [RavenDB](http://ravendb.net/ "RavenDB is the premier NoSQL database for .NET") Bootcamp is a free, self-directed learning course that is up-to-date for RavenDB 4.0. 4 | 5 | In just four units you will learn how to use RavenDB to create fully-functional, real-world programs with NoSQL Databases. If you are unfamiliar with NoSQL, don't worry, we will provide you with all the information you need. 6 | 7 | We suggest you go through at least one lesson per day, but you can learn at your own pace. 8 | 9 | ## What You Will Learn 10 | 11 | The RavenDB Bootcamp will teach you how to use RavenDB - a powerful NoSQL database engine that is Safe by Default and Optimized for Efficiency to combine the best of two worlds: relational and document–oriented databases. 12 | 13 | You will see how easy it is to store and query data using RavenDB and how fast you can produce results. 14 | 15 | After you complete this boot camp, not only will you be confident to store your data using NoSQL, database work will actually become fun. 16 | 17 | ### Unit 1 - Fundamentals 18 | 19 | You will learn how to install RavenDB and make it work on your machine. RavenDB makes starting easy by providing an embedded sample database – think "Northwind", which you can use for your learning process with minimal effort. 20 | 21 | You will write some code to connect to the database and store, load, modify, and delete documents. 22 | 23 | * [Lesson 1: Getting Started](src/unit1/lesson1) 24 | * [Lesson 2: It's Querying Time!](src/unit1/lesson2) 25 | * [Lesson 3: Let's Code](src/unit1/lesson3) 26 | * [Lesson 4: Basics of DocumentStore](src/unit1/lesson4) 27 | * [Lesson 5: Loading Documents](src/unit1/lesson5) 28 | * [Lesson 6: Querying Fundamentals in the C# side](src/unit1/lesson6) 29 | * [Lesson 7: Storing, Modifying and Deleting documents](src/unit1/lesson7) 30 | 31 | ### Unit 2 - Beyond the Basics 32 | 33 | In Unit 2 you will learn intermediate RavenDB features. You will see how fast RavenDB is and understand why. 34 | 35 | You will easily perform very complex queries without using additional tools. You will learn what Map-Reduce is and when to use it to extract more value from your data. 36 | 37 | * [Lesson 1: Getting Started with Indexes](src/unit2/lesson1) 38 | * [Lesson 2: Creating an Index and Querying It](src/unit2/lesson2) 39 | * [Lesson 3: Multi-map Indexes](src/unit2/lesson3) 40 | * [Lesson 4: Mapping, Filtering, and Reducing](src/unit2/lesson4) 41 | * [Lesson 5: The Powerful LoadDocument Server-Side Function](src/unit2/lesson5) 42 | * [Lesson 6: Goodbye Transformers, Welcome Server-Side Projections](src/unit2/lesson6) 43 | * [Lesson 7: Statistics and Some Words About Stale Indexes!](src/unit2/lesson7) 44 | 45 | ### Unit 3 - Advanced Features 46 | 47 | In Unit 3 you will learn some advanced RavenDB features. 48 | 49 | Use this knowledge to build powerful solutions, obtain better performance, improve safety, and create truly reactive user interfaces. 50 | 51 | * [Lesson 1: Revisions](src/unit3/lesson1) 52 | * [Lesson 2: Document Metadata](src/unit3/lesson2) 53 | * [Lesson 3: Getting Started with Operations and Commands!](src/unit3/lesson3) 54 | * [Lesson 4: Performing Batch Operations](src/unit3/lesson4) 55 | * [Lesson 5: I am Going Through Changes ...](src/unit3/lesson5) 56 | * [Lesson 6: Data Subscriptions - I would Like to be Notified When it Happens Please](src/unit3/lesson6) 57 | 58 | ### Unit 4 - Database Clusters 59 | 60 | In Unit 4 you will learn the basics about how to set up, manage and use clusters using RavenDB. 61 | 62 | * [Lesson 1: Setting up Your First Cluster](src/unit4/lesson1) 63 | * [Lesson 2: Database and Database Group](src/unit4/lesson2) 64 | * [Lesson 3: Considerations on the Client Side](src/unit4/lesson3) 65 | 66 | 67 | [LET'S BEGIN](src/unit1/lesson1) 68 | 69 | Enjoy your RavenDB Bootcamp and feel free to share your feedback with us. 70 | -------------------------------------------------------------------------------- /src/unit3/lesson4/README.md: -------------------------------------------------------------------------------- 1 | # Unit 3, Lesson 4 - Performing Batch Operations 2 | 3 | As you learned in the [previous lesson](../lesson3/README.md), RavenDB 4 | provides a low-level API that you can use to execute not trivial tasks. 5 | 6 | In this lesson you will learn how to use operations to update 7 | a large amount of documents answering a certain criteria. 8 | 9 | ## The Need to Batch Operations 10 | 11 | Sometimes you will need to update or delete a large amount of documents 12 | answering a certain criteria. 13 | 14 | Using SQL it would be easy. You just need to use the `UPDATE` and `DELETE` 15 | statements. 16 | 17 | ```sql 18 | UPDATE Products SET Price = Price * 1.1 WHERE Discontinued = false; 19 | ``` 20 | 21 | Anyway, this is not the case when using NoSQL databases, where set 22 | based operations are not supported. 23 | 24 | ## RavenDB Approach for Batch Operations 25 | 26 | Using RavenDB, the same queries and indexes that are used for data retrieval 27 | are used for the set based operations. The syntax defining which documents to 28 | work on is exactly the same as you'd specified for those documents to be pulled 29 | from the store. 30 | 31 | ## Exercise: Increasing the `PricePerUnit` for non-discontinued products 32 | 33 | In this exercise, you will learn how to increase prices of all 34 | non-discontinued products. 35 | 36 | ### Step 1: Create a new project and install the latest `RavenDB.Client` package 37 | 38 | Start Visual Studio and create a new `Console Application Project` named 39 | `BatchOperationsWithRavenDB`. Then, in the `Package Manager Console`, issue the following 40 | command: 41 | 42 | ```powershell 43 | Install-Package RavenDB.Client -Version 5.4.5 44 | ``` 45 | 46 | ### Step 2: Initialize the `DocumentStore` 47 | 48 | Here we go again. let's manage the `DocumentStore` using our great friend `DocumentStoreHolder` pattern. 49 | 50 | ````csharp 51 | using System; 52 | using Raven.Client; 53 | using Raven.Client.Documents; 54 | 55 | namespace BatchOperationsWithRavenDB 56 | { 57 | public static class DocumentStoreHolder 58 | { 59 | private static readonly Lazy LazyStore = 60 | new Lazy(() => 61 | { 62 | var store = new DocumentStore 63 | { 64 | Urls = new[] { "http://localhost:8080" }, 65 | Database = "Northwind" 66 | }; 67 | 68 | return store.Initialize(); 69 | }); 70 | 71 | public static IDocumentStore Store => 72 | LazyStore.Value; 73 | } 74 | } 75 | ```` 76 | 77 | ### Step 3: Performing a batch operation 78 | 79 | We now have everything we need to perform a batch operation. Let's do it. 80 | 81 | ```csharp 82 | static void Main() 83 | { 84 | var operation = DocumentStoreHolder.Store 85 | .Operations 86 | .Send(new PatchByQueryOperation(@"from Products as p 87 | where p.Discontinued = false 88 | update 89 | { 90 | p.PricePerUnit = p.PricePerUnit * 1.1 91 | }")); 92 | operation.WaitForCompletion(); 93 | } 94 | ``` 95 | 96 | There is a very good list of examples available on the [RavenDB documentation](https://ravendb.net/docs/article-page/4.0/csharp/client-api/operations/patching/set-based). 97 | 98 | 99 | Run it and check the results! 100 | 101 | ## Before you go ... 102 | 103 | You just learned how to update documents in the server without having to load 104 | it in the client side. This is an extremely powerful concept. But remember 105 | *"With great powers comes great responsibility"*. 106 | 107 | ## Great job! 108 | 109 | **Let's move onto [Lesson 5](../lesson5/README.md).** 110 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | bld/ 16 | [Bb]in/ 17 | [Oo]bj/ 18 | [Pp]ackages/ 19 | 20 | # Roslyn cache directories 21 | *.ide/ 22 | 23 | # MSTest test Results 24 | [Tt]est[Rr]esult*/ 25 | [Bb]uild[Ll]og.* 26 | 27 | #NUNIT 28 | *.VisualState.xml 29 | TestResult.xml 30 | 31 | # Build Results of an ATL Project 32 | [Dd]ebugPS/ 33 | [Rr]eleasePS/ 34 | dlldata.c 35 | 36 | *_i.c 37 | *_p.c 38 | *_i.h 39 | *.ilk 40 | *.meta 41 | *.obj 42 | *.pch 43 | *.pdb 44 | *.pgc 45 | *.pgd 46 | *.rsp 47 | *.sbr 48 | *.tlb 49 | *.tli 50 | *.tlh 51 | *.tmp 52 | *.tmp_proj 53 | *.log 54 | *.vspscc 55 | *.vssscc 56 | .builds 57 | *.pidb 58 | *.svclog 59 | *.scc 60 | *.lock.json 61 | 62 | # Chutzpah Test files 63 | _Chutzpah* 64 | 65 | # Visual C++ cache files 66 | ipch/ 67 | *.aps 68 | *.ncb 69 | *.opensdf 70 | *.sdf 71 | *.cachefile 72 | 73 | # Visual Studio profiler 74 | *.psess 75 | *.vsp 76 | *.vspx 77 | 78 | # TFS 2012 Local Workspace 79 | $tf/ 80 | 81 | # Guidance Automation Toolkit 82 | *.gpState 83 | 84 | # ReSharper is a .NET coding add-in 85 | _ReSharper*/ 86 | *.[Rr]e[Ss]harper 87 | *.DotSettings.user 88 | 89 | # JustCode is a .NET coding addin-in 90 | .JustCode 91 | 92 | # TeamCity is a build add-in 93 | _TeamCity* 94 | 95 | # DotCover is a Code Coverage Tool 96 | *.dotCover 97 | 98 | # NCrunch 99 | _NCrunch_* 100 | .*crunch*.local.xml 101 | 102 | # MightyMoose 103 | *.mm.* 104 | AutoTest.Net/ 105 | 106 | # Web workbench (sass) 107 | .sass-cache/ 108 | 109 | # Installshield output folder 110 | [Ee]xpress/ 111 | 112 | # DocProject is a documentation generator add-in 113 | DocProject/buildhelp/ 114 | DocProject/Help/*.HxT 115 | DocProject/Help/*.HxC 116 | DocProject/Help/*.hhc 117 | DocProject/Help/*.hhk 118 | DocProject/Help/*.hhp 119 | DocProject/Help/Html2 120 | DocProject/Help/html 121 | 122 | # Click-Once directory 123 | publish/ 124 | 125 | # Publish Web Output 126 | *.[Pp]ublish.xml 127 | *.azurePubxml 128 | ## TODO: Comment the next line if you want to checkin your 129 | ## web deploy settings but do note that will include unencrypted 130 | ## passwords 131 | #*.pubxml 132 | 133 | # NuGet Packages Directory 134 | packages/* 135 | ## TODO: If the tool you use requires repositories.config 136 | ## uncomment the next line 137 | #!packages/repositories.config 138 | 139 | # Enable "build/" folder in the NuGet Packages folder since 140 | # NuGet packages use it for MSBuild targets. 141 | # This line needs to be after the ignore of the build folder 142 | # (and the packages folder if the line above has been uncommented) 143 | !packages/build/ 144 | 145 | # Windows Azure Build Output 146 | csx/ 147 | *.build.csdef 148 | 149 | # Windows Store app package directory 150 | AppPackages/ 151 | 152 | # Others 153 | sql/ 154 | *.Cache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | bower_components/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # LightSwitch generated files 190 | GeneratedArtifacts/ 191 | _Pvt_Extensions/ 192 | ModelManifest.xml 193 | 194 | log/ 195 | .vs 196 | nuget.exe 197 | -------------------------------------------------------------------------------- /src/unit2/lesson7/README.md: -------------------------------------------------------------------------------- 1 | # Unit 2, Lesson 7 - Statistics and some words about stale indexes! 2 | 3 | In this lesson you will learn how to get details of a just executed query. 4 | 5 | ## Introducing `QueryStatistics` 6 | 7 | Every time you perform a query using the client API, you can request 8 | some additional info using the `Statistics` method. 9 | 10 | ````csharp 11 | var orders = ( 12 | from order in session.Query().Statistics(out var stats) 13 | where order.Company == "companies/1-a" 14 | orderby order.OrderedAt 15 | select order 16 | ).ToList(); 17 | 18 | Console.WriteLine($"Index used was: {stats.IndexName}"); 19 | ```` 20 | 21 | The results are available via a `QueryStatistics` object. 22 | Among the details you can get from the query statistics, you have: 23 | 24 | * Whether the index was stale or not. 25 | * The duration of the query on the server side. 26 | * The total number of results (regardless of paging). 27 | * The name of the index that this query ran against. 28 | * The last document etag indexed by the index. 29 | * The timestamp of the last document indexed by the index. 30 | 31 | ## Stale Index? 32 | 33 | This is a very important fact: **RavenDB queries are BASE. Reads and writes by document ID are always ACID**. 34 | 35 | RavenDB performs indexing in a background thread, which is executed whenever new data comes in 36 | or existing data is updated. This allows the server to respond quickly, even when large 37 | amounts of data have been changed. However, in that case you may query stale indexes and, because 38 | of that, some query results may not be fully up to date. Usually, the time between a document being 39 | updated and the relevant indexes being updated is measured in milliseconds. 40 | 41 | ### How to Know if an Index is Stale 42 | 43 | RavenDB is honest and clear about stale indexes. It is really easy to know if a query used a 44 | stale index just checking the statistics. 45 | 46 | ````csharp 47 | QueryStatistics stats; 48 | 49 | var orders = ( 50 | from order in session.Query().Statistics(out stats) 51 | where order.Company == "companies/1" 52 | orderby order.OrderedAt 53 | select order 54 | ).ToList(); 55 | 56 | if (stats.IsStale) 57 | { 58 | // .. 59 | } 60 | ```` 61 | 62 | The `IsStale` property will be `true` whenever the index used to perform the query is not 63 | up to date. In this sample, probably an Order was added or changed and the indexes didn't 64 | have enough time to fully update before the query. 65 | 66 | ### Forcing Non-Stale Results 67 | 68 | If you need to make sure that your results are up to date, then you can use the `Customize` method. 69 | 70 | ````csharp 71 | QueryStatistics stats; 72 | 73 | var query = session.Query() 74 | .Customize(q => q.WaitForNonStaleResults(TimeSpan.FromSeconds(5))); 75 | 76 | var orders = ( 77 | from order in query 78 | where order.Company == "companies/1" 79 | orderby order.OrderedAt 80 | select order 81 | ) 82 | .ToList(); 83 | ```` 84 | 85 | Here RavenDB is instructed to use a 5 second time-out. 86 | 87 | There are different strategies you could use. To learn more about how to deal with stale indexes, 88 | I strongly recommend you to read the [official documentation](https://ravendb.net/docs/article-page/latest/csharp/indexes/stale-indexes). 89 | 90 | ### Is Staleness a Real Problem? 91 | At first sight, the idea of a query that may not be fully up to date sounds scary. 92 | In practice, this is how we almost always work in the real world. 93 | 94 | Try this: call your bank and ask them how much money you have in your account (financial systems should be 95 | consistent, right?). The answer you’ll get is going to be some variant of: “As of last business day, you had…”. 96 | 97 | Much of management occurs with the previous day's data. 98 | 99 | ## Great Job! 100 | 101 | **Congratulations! Now you know some advanced aspects of RavenDB. Let's move to Unit 3.** 102 | -------------------------------------------------------------------------------- /src/unit1/lesson5/NorthwindModels.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace NorthwindModels 5 | { 6 | public class Company 7 | { 8 | public string Id { get; set; } 9 | public string ExternalId { get; set; } 10 | public string Name { get; set; } 11 | public Contact Contact { get; set; } 12 | public Address Address { get; set; } 13 | public string Phone { get; set; } 14 | public string Fax { get; set; } 15 | } 16 | 17 | public class Address 18 | { 19 | public string Line1 { get; set; } 20 | public string Line2 { get; set; } 21 | public string City { get; set; } 22 | public string Region { get; set; } 23 | public string PostalCode { get; set; } 24 | public string Country { get; set; } 25 | } 26 | 27 | public class Contact 28 | { 29 | public string Name { get; set; } 30 | public string Title { get; set; } 31 | } 32 | 33 | public class Category 34 | { 35 | public string Id { get; set; } 36 | public string Name { get; set; } 37 | public string Description { get; set; } 38 | } 39 | 40 | public class Order 41 | { 42 | public string Id { get; set; } 43 | public string Company { get; set; } 44 | public string Employee { get; set; } 45 | public DateTime OrderedAt { get; set; } 46 | public DateTime RequireAt { get; set; } 47 | public DateTime? ShippedAt { get; set; } 48 | public Address ShipTo { get; set; } 49 | public string ShipVia { get; set; } 50 | public decimal Freight { get; set; } 51 | public List Lines { get; set; } 52 | } 53 | 54 | public class OrderLine 55 | { 56 | public string Product { get; set; } 57 | public string ProductName { get; set; } 58 | public decimal PricePerUnit { get; set; } 59 | public int Quantity { get; set; } 60 | public decimal Discount { get; set; } 61 | } 62 | 63 | public class Product 64 | { 65 | public string Id { get; set; } 66 | public string Name { get; set; } 67 | public string Supplier { get; set; } 68 | public string Category { get; set; } 69 | public string QuantityPerUnit { get; set; } 70 | public decimal PricePerUnit { get; set; } 71 | public int UnitsInStock { get; set; } 72 | public int UnitsOnOrder { get; set; } 73 | public bool Discontinued { get; set; } 74 | public int ReorderLevel { get; set; } 75 | } 76 | 77 | public class Supplier 78 | { 79 | public string Id { get; set; } 80 | public Contact Contact { get; set; } 81 | public string Name { get; set; } 82 | public Address Address { get; set; } 83 | public string Phone { get; set; } 84 | public string Fax { get; set; } 85 | public string HomePage { get; set; } 86 | } 87 | 88 | public class Employee 89 | { 90 | public string Id { get; set; } 91 | public string LastName { get; set; } 92 | public string FirstName { get; set; } 93 | public string Title { get; set; } 94 | public Address Address { get; set; } 95 | public DateTime HiredAt { get; set; } 96 | public DateTime Birthday { get; set; } 97 | public string HomePhone { get; set; } 98 | public string Extension { get; set; } 99 | public string ReportsTo { get; set; } 100 | public List Notes { get; set; } 101 | 102 | public List Territories { get; set; } 103 | } 104 | 105 | public class Region 106 | { 107 | public string Id { get; set; } 108 | public string Name { get; set; } 109 | public List Territories { get; set; } 110 | } 111 | 112 | public class Territory 113 | { 114 | public string Code { get; set; } 115 | public string Name { get; set; } 116 | } 117 | 118 | public class Shipper 119 | { 120 | public string Id { get; set; } 121 | public string Name { get; set; } 122 | public string Phone { get; set; } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/unit3/lesson5/README.md: -------------------------------------------------------------------------------- 1 | # Unit 3, Lesson 5 - I am Going Through Changes ... 2 | 3 | At this point you already know how to perform low-level operations with RavenDB. 4 | 5 | In this lesson you will learn how to use the [Changes API](https://ravendb.net/docs/article-page/4.0/csharp/client-api/changes/what-is-changes-api) 6 | 7 | ## What is the `Changes API` 8 | 9 | Changes API is an amazing feature that allows you to receive messages from the server about the events occurred there. 10 | 11 | Using the Changes API, you will get notified by the server whenever an event you are interested is fired without polling. Polling is wasteful. Most of the time you spend a lot of time asking the same 12 | question and expecting to get the same answer. 13 | 14 | ## Exercise: Getting notified when a document changes 15 | 16 | In this exercise, you will learn how to get notifications whenever any document is changed. 17 | 18 | ### Step 1: Create a new project and install the latest `RavenDB.Client` and `System.Reactive.Core` packages 19 | 20 | Start Visual Studio and create a new `Console Application Project` named 21 | `BasicsOfChangesAPI`. Then, in the `Package Manager Console`, issue the following 22 | commands: 23 | 24 | ```powershell 25 | Install-Package RavenDB.Client -Version 5.4.5 26 | Install-Package System.Reactive.Core 27 | Install-Package System.Reactive.Linq 28 | ``` 29 | 30 | Yeap! RavenDB is reactive! 31 | 32 | ### Step 2: Initialize the `DocumentStore` 33 | 34 | As you already know, we will manage the `DocumentStore` using our great friend `DocumentStoreHolder` pattern. 35 | 36 | ````csharp 37 | using System; 38 | using Raven.Client; 39 | using Raven.Client.Documents; 40 | 41 | namespace BasicsOfChangesAPI 42 | { 43 | public static class DocumentStoreHolder 44 | { 45 | private static readonly Lazy LazyStore = 46 | new Lazy(() => 47 | { 48 | var store = new DocumentStore 49 | { 50 | Urls = new[] { "http://localhost:8080" }, 51 | Database = "Northwind" 52 | }; 53 | 54 | store.Initialize(); 55 | 56 | var asm = Assembly.GetExecutingAssembly(); 57 | IndexCreation.CreateIndexes(asm, store); 58 | 59 | return store; 60 | }); 61 | 62 | public static IDocumentStore Store => 63 | LazyStore.Value; 64 | } 65 | } 66 | ```` 67 | 68 | ### Step 3: Subscribing to changes 69 | 70 | Now it's time to subscribe! 71 | 72 | ````csharp 73 | using System; 74 | using Raven.Client; 75 | using Raven.Client.Documents; 76 | 77 | namespace BasicsOfChangesAPI 78 | { 79 | using static Console; 80 | 81 | class Program 82 | { 83 | static void Main(string[] args) 84 | { 85 | var subscription = DocumentStoreHolder.Store 86 | .Changes() 87 | .ForAllDocuments() 88 | .Subscribe(change => 89 | WriteLine($"{change.Type} on document {change.Id}")); 90 | 91 | WriteLine("Press any key to exit..."); 92 | ReadKey(); 93 | 94 | subscription.Dispose(); 95 | } 96 | } 97 | } 98 | ```` 99 | 100 | Now every time something changes a document in the server (Put, Delete), your application will get notified. Test it! Change some documents using the Studio and confirm it. 101 | 102 | Notice that the change notification include the document (or index) ID and the 103 | type of the operation performed. Put or Delete in the case of documents, most 104 | often. If you want to actually access the document in question, you’ll need to 105 | load it using a session (as you already know). 106 | 107 | In a "real world" application, you can take actions, such as notify the user (e.g. using 108 | SignalR if you are running a web application). 109 | 110 | ## What Changes are Supported by the Changes API? 111 | 112 | You can register for notifications on specific documents, all documents with a specific prefix or of a 113 | specific collection, all documents changes, or for updates to indexes. 114 | 115 | ## Great job! 116 | 117 | You just learned how to use the Changes API. 118 | 119 | This is an extremely powerful feature that enables a whole host of interesting scenarios. 120 | 121 | **Let's move onto [Lesson 6](../lesson6/README.md).** 122 | 123 | -------------------------------------------------------------------------------- /src/unit3/lesson6/README.md: -------------------------------------------------------------------------------- 1 | # Unit 3, Lesson 6 - Data Subscriptions - I Would Like to be Notified when an Happens 2 | 3 | You already know the basics about how to use RavenDB and you are ready to move on. Please 4 | note that there is an [extensive documentation](http://ravendb.net/docs) available online. You can learn more about other features like clustering, for example. Thare is much more to learn than we covered. 5 | 6 | In this last lesson, you will learn how to work with Data Subscriptions. 7 | 8 | ## Data Subscriptions 9 | 10 | It's simpler to explain this concept with an example. Consider the following query: 11 | 12 | ```sql 13 | from Orders 14 | where Lines.length > 5 15 | ``` 16 | 17 | This would retrieve all the big orders from your database. But what if a new big order 18 | is added after you run this query. What if you want to be notified whenever a big order 19 | occurs? 20 | 21 | ## Exercise: Creating a Data Subscription 22 | 23 | Open the Studio and the Northwind database. 24 | 25 | To create a Data Subscription you need to open the `Settings` section of your database. 26 | Click on the `Manage Ongoing Tasks` option, then click on the `Add Task` button. 27 | 28 | ![Subscription task](media/new_subscription_task.png) 29 | 30 | Now, click on `Subscription`. 31 | 32 | RavenDB is asking you for a Task Name (let's call it `Big Orders`) and let's provide the very 33 | same query we used before. 34 | 35 | ![Big orders](media/big_orders.png) 36 | 37 | The user interface allows you to do a test before saving. 38 | 39 | Save it! 40 | 41 | ## Exercise: Subscribing the Data Subscription 42 | 43 | In this exercise, you will learn how to subscribe and consume a 44 | data subscription. 45 | 46 | >Data subscriptions are consumed by clients, called subscription workers. 47 | In any given moment, **only one worker can be connected to a data subscription**. 48 | A worker connected to a data subscription receives a batch of documents and gets to process it. 49 | When it's done (depends on the code that the client gave the worker, can take from seconds to hours), 50 | it informs the server about the progress and the server is ready to send the next batch. 51 | 52 | ### Step 1: Create a new project and install the latest `RavenDB.Client` package 53 | 54 | Start Visual Studio and create a new `Console Application Project` named 55 | `BatchOperationsWithRavenDB`. Then, in the `Package Manager Console`, issue the following 56 | command: 57 | 58 | ```powershell 59 | Install-Package RavenDB.Client -Version 5.4.5 60 | ``` 61 | 62 | ### Step 2: Initialize the `DocumentStore` 63 | 64 | Let's manage the `DocumentStore` using our great friend the `DocumentStoreHolder` pattern. 65 | 66 | ````csharp 67 | using System; 68 | using Raven.Client; 69 | using Raven.Client.Documents; 70 | 71 | namespace BatchOperationsWithRavenDB 72 | { 73 | public static class DocumentStoreHolder 74 | { 75 | private static readonly Lazy LazyStore = 76 | new Lazy(() => 77 | { 78 | var store = new DocumentStore 79 | { 80 | Urls = new[] { "http://localhost:8080" }, 81 | Database = "Northwind" 82 | }; 83 | 84 | return store.Initialize(); 85 | }); 86 | 87 | public static IDocumentStore Store => 88 | LazyStore.Value; 89 | } 90 | } 91 | ```` 92 | 93 | ### Step 3: Subscribing... 94 | 95 | Subscriptions are consumed by processing batches of documents received from the server. 96 | We need to get an instance of `SubscriptionWorker`. 97 | 98 | ```csharp 99 | static void Main(string[] args) 100 | { 101 | var subscriptionWorker = DocumentStoreHolder.Store 102 | .Subscriptions 103 | .GetSubscriptionWorker("Big Orders"); 104 | 105 | var subscriptionRuntimeTask = subscriptionWorker.Run(batch => 106 | { 107 | foreach (var order in batch.Items) 108 | { 109 | // business logic here. 110 | Console.WriteLine(order.Id); 111 | } 112 | }); 113 | 114 | WriteLine("Press any key to exit..."); 115 | ReadKey(); 116 | } 117 | ``` 118 | 119 | ## How Data Subscriptions Work 120 | 121 | There are some important facts that you need to know to use this feature correctly. 122 | 123 | * Documents that match a pre-defined criteria are sent in batches from the server to the client. 124 | * The client sends an acknowledgment to the server once it is done with processing the batch. 125 | * The server keeps track of the latest document that was acknowledged by the client, so that processing can be continued from the latest acknowledged position if it was paused or interrupted. 126 | 127 | ## Great job! Next stop: Clusters! 128 | 129 | Congratulations! Now you know some advanced features of RavenDB. In the next Unit we will start to work with clusters. 130 | -------------------------------------------------------------------------------- /src/unit4/lesson1/README.md: -------------------------------------------------------------------------------- 1 | # Unit 4, Lesson 1 - Setting up your first cluster 2 | 3 | Now it is time to start learning about how to use clusters. 4 | 5 | It is not a difficult task. You do not need to be an expert to manage a RavenDB cluster (We did our best to make it mostly self-managed). 6 | 7 | ## The Point of Setting up a Cluster 8 | 9 | A RavenDB cluster is a set of machines that have been joined together. Each machine in the cluster is a node. 10 | 11 | When you create a database, it can live on a single node (one machine in the cluster), some number of the nodes, or even all the nodes. Each node will hold a complete copy of the database and will be able to serve all queries, operations, and writes. RavenDB cluster implements [multi-master replication](https://en.wikipedia.org/wiki/Multi-master_replication). 12 | 13 | The primary reason for this duplicating data is to allow high availability. If a node goes down, the cluster will still have a copies of the data and the can shuffle things around so clients can talk to another node without realizing that anything happened. 14 | 15 | The cluster can distribute the work, handle the failures, and adopt recovery procedures automatically. 16 | 17 | ## Let’s Set up a Cluster 18 | 19 | Assuming that you are already started the RavenDB server on your computer, you are already using a cluster. It's a cluster with a single node, but it is still a cluster. 20 | 21 | ![You already have a cluster](media/already_cluster.png) 22 | 23 | What you need to do is add more nodes to your cluster. 24 | 25 | ## Can We Simply Add Nodes to a Cluster? 26 | 27 | Yes. But first we need to review our license: The RavenDB Community edition only allows you to set up a cluster using 3 cores. Before setting a cluster we will need to reduce the number of cores in use of the current node. You can skip this depending on your license. 28 | 29 | In the left panel, select the option `Manage Server`, and then `Cluster`. Then, in the list of nodes (there is only one), click in `Operations` and `Reassign Cores`. 30 | 31 | ![Number of cores](media/max_cores.png) 32 | 33 | Change the number of Cores in Use to 1 and click on `Save`. 34 | 35 | ## Exercise: Starting the second node 36 | 37 | In this excercise you will learn how to add a second node to the cluster. 38 | 39 | ### Step 1: Starting a second server instance 40 | 41 | Now that the “nodes problem” is solved, let’s start a second instance of the RavenDB server. Typically you would do that on a separate machine, but to make this exercise easier we’ll run it on the same computer. Open a new terminal session and run the following command: 42 | 43 |
.\Server\Raven.Server.exe --ServerUrl=http://127.0.0.2:8080 --ServerUrl.Tcp=tcp://127.0.0.2:38888 --Logs.Path=Logs/B --DataDir=Data/B
44 | 45 | ![starting a second server](media/max_cores.png) 46 | 47 | We are specifying the server http address as `http://127.0.0.2:8080` and the tcp address as `tcp://127.0.0.2:38888`. The logs directory is `Logs/B` and the data directory is `Data/B`. **Now there are two servers running on your computer**. 48 | 49 | ### Step 2: Add the second server as a node in the cluster 50 | 51 | Open the `Management Studio` from the first server, then, in the left panel, select the option `Manage Server` and then `Cluster`. Click in `Add Node To Cluster`. Inform the address of the second server and hit `Test Connection`. 52 | 53 | ![adding the second node](media/second-node.png) 54 | 55 | Success? Hit `Add`. 56 | 57 | ## Exercise: Starting the third node 58 | 59 | I strongly recommend you to repeat the process to create the third node on `http://127.0.0.3:8080` and storing data at `Data/C` and logs at `Logs/C`. 60 | 61 | This is our result: 62 | 63 | ![cluster is ready](media/cluster_ready.png) 64 | 65 | In a future lesson, you will learn why it makes little sense to setup a cluster with only two nodes. 66 | 67 | ## Some New Important Concepts 68 | 69 | Now that you know what is a cluster, it's time to learn some new concepts. 70 | 71 | * Database – the named database we’re talking about, regardless of whether we’re speaking about a specific instance or the whole group. 72 | * Database Instance – exists on a single node. 73 | * Database Group – the grouping of all the different instances, typically used to explicitly refer to its distributed nature. 74 | * Database Topology – the specific nodes that all the database instances in a database group reside on in a particular point in time. 75 | 76 | Consider you have a cluster with 5 nodes. It is possible to create a database setting with a replication factor of just 3. In this case, only 3 nodes will have instances of the database. The selected nodes will form the Database Topology and all the three instances will constitute the Database Group. 77 | 78 | ## Great Job! 79 | 80 | In the next lesson I will help you to create a database, set the replication factor, and define what nodes will compose the Database Topology. 81 | 82 | **Let's move onto [Lesson 2](../lesson2/README.md).** -------------------------------------------------------------------------------- /src/unit1/lesson6/README.md: -------------------------------------------------------------------------------- 1 | # Unit 1, Lesson 6 - Querying Fundamentals in C# 2 | 3 | Querying is a large part of what a database does, and RavenDB doesn't disappoint. 4 | 5 | As you learned in the lesson 2, querying with RavenDB is really easy. In this lesson, you will learn the fundamentals of querying using RavenDB in C#. 6 | 7 | ## LINQ Support via the `Query` Session Method 8 | 9 | RavenDB takes full advantage of LINQ support in C#. This allows you to 10 | express very natural queries on top of RavenDB in a strongly typed and safe 11 | manner. 12 | 13 | Queries allow you to load documents that match a particular predicate. 14 | 15 | You get access to LINQ support via the `Query` method from the session object. 16 | 17 | Like documents loaded via the `Load` call, documents that were loaded via 18 | `Query` are managed by the session (unless you are doing a projection). 19 | 20 | Queries in RavenDB don't behave like queries in relational databases. RavenDB 21 | does not allow computation during queries and it doesn't have problems with 22 | table scans because all queries are indexed -- even if you didn't create any indexes! 23 | 24 | ## Exercise: Querying Orders of a Company 25 | 26 | This time, you will write an application which requests a company ID. Then 27 | you will list the orders made by this company. 28 | 29 | I think you got the basics from the previous exercises so I will not repeat details that you already know. 30 | 31 | ### Step 1: Create a new project and install the latest `RavenDB.Client` package 32 | 33 | ### Step 2: Create the `DocumentStoreHolder` 34 | 35 | ### Step 3: Add Northwind model classes to your project 36 | 37 | ### Step 4: Request a company ID 38 | 39 | Back to `Program.cs`, let's create a minimal user interface which requests a 40 | company ID. 41 | 42 | ````csharp 43 | using System; 44 | using System.Linq; 45 | using static System.Console; 46 | using Raven.Client.Documents; 47 | using NorthwindModels; 48 | 49 | namespace OrdersExplorer 50 | { 51 | class Program 52 | { 53 | static void Main(string[] args) 54 | { 55 | while (true) 56 | { 57 | WriteLine("Please, enter a company id (0 to exit): "); 58 | 59 | if (!int.TryParse(ReadLine(), out var companyId)) 60 | { 61 | WriteLine("Company # is invalid."); 62 | continue; 63 | } 64 | 65 | if (companyId == 0) break; 66 | 67 | QueryCompanyOrders(companyId); 68 | } 69 | 70 | WriteLine("Goodbye!"); 71 | } 72 | 73 | private static void QueryCompanyOrders(int companyId) 74 | { 75 | } 76 | } 77 | } 78 | ```` 79 | 80 | ### Step 5: Query the orders for the specified company 81 | 82 | With an ID, you can now query the orders. 83 | 84 | ````csharp 85 | private static void QueryCompanyOrders(int companyId) 86 | { 87 | string companyReference = $"companies/{companyId}-A"; 88 | 89 | using (var session = DocumentStoreHolder.Store.OpenSession()) 90 | { 91 | var orders = ( 92 | from order in session.Query() 93 | .Include(o => o.Company) 94 | where order.Company == companyReference 95 | select order 96 | ).ToList(); 97 | 98 | var company = session.Load(companyReference); 99 | 100 | if (company == null) 101 | { 102 | WriteLine("Company not found."); 103 | return; 104 | } 105 | 106 | WriteLine($"Orders for {company.Name}"); 107 | 108 | foreach (var order in orders) 109 | { 110 | WriteLine($"{order.Id} - {order.OrderedAt}"); 111 | } 112 | } 113 | } 114 | ```` 115 | 116 | ## Why Not Use RQL? 117 | 118 | Using LINQ is natural for C# developers, but what if you want to discover the power of RQL? 119 | 120 | ```csharp 121 | private static void QueryCompanyOrders(int companyId) 122 | { 123 | string companyReference = $"companies/{companyId}-A"; 124 | 125 | using (var session = DocumentStoreHolder.Store.OpenSession()) 126 | { 127 | var orders = session.Advanced.RawQuery( 128 | "from Orders " + 129 | "where Company== $companyId " + 130 | "include Company" 131 | ).AddParameter("companyId", companyReference) 132 | .ToList(); 133 | 134 | var company = session.Load(companyReference); 135 | 136 | if (company == null) 137 | { 138 | WriteLine("Company not found."); 139 | return; 140 | } 141 | 142 | WriteLine($"Orders for {company.Name}"); 143 | 144 | foreach (var order in orders) 145 | { 146 | WriteLine($"{order.Id} - {order.OrderedAt}"); 147 | } 148 | } 149 | } 150 | ``` 151 | 152 | ## Great Job! 153 | 154 | **Let's move on to [Lesson 7](../lesson7/README.md) and learn how to create, change and delete documents.** 155 | -------------------------------------------------------------------------------- /src/unit4/lesson2/README.md: -------------------------------------------------------------------------------- 1 | # Unit 4, Lesson 2 - Database and Database Group 2 | 3 | Now that you already know how to set up a cluster, let's learn some in depth concepts about how it works. Let's also learn how to manage a database in the cluster. 4 | 5 | ## It's all about "Operations" 6 | 7 | Operations in RavenDB can be classified into two categories: 8 | 9 | 1) Cluster-Wide Operations: They impact the entire cluster, like creating a new database. 10 | 11 | 2) Internal Database Operations: They impact a single database, like creating a new document. 12 | 13 | This distinction is vital because cluster-wide operations demand consensus between the nodes of the cluster and it is only possible when the majority of the nodes is working. RavenDB uses a consensus protocol called RAFT. To create a database in a cluster with three nodes, it would be necessary that at least two nodes are working (the majority). This is the reason why you should not have a cluster with only two nodes. When one is down, there is no majority. 14 | 15 | > There are a lot of details about how the RAFT consensus protocol works in RavenDB that are beyond the scope of this bootcamp. If you want to understand it in-depth, I recommend you to read [Ayende's book](https://github.com/ravendb/book) (which is the definitive reference about RavenDB by the CEO, and it is free!). 16 | 17 | Database operations, on the other hand, are treated quite differently. Each node in the cluster has the full copy of the topology which specifies which nodes host which databases. The connection between the database instances does not use the consensus protocol. Instead of this, they're direct connections among the various nodes, and they form a multi-master mesh. A write to any of those nodes will be automatically replicated to all the other nodes. 18 | 19 | Cluster Consensus provides firm consistency. Unfortunately, we also need to ensure availability of the databases, and it is a tradeoff (look for the [CAP theorem](https://en.wikipedia.org/wiki/CAP_theorem) to understand the reasons). We decided to provide consistency where it is indispensable and chose explicitly for availability elsewhere. 20 | 21 | ## Exploratory Challenge: What Happens when a Node Goes Down? 22 | 23 | Assuming you are running a cluster with three nodes, this is the result you get when accessing the `Manage Server` screen on the `Management Studio`. 24 | 25 | ![cluster status](./media/status_cluster.png) 26 | 27 | I recommend you stop some nodes now and see what happens in the diagram on the right side. 28 | 29 | ## Focus on Robustness 30 | 31 | RavenDB's design is mainly focused on robustness. Once the cluster is set up, each database instance on the various nodes is independent. It can accept writes without consulting the other instances of the same database. 32 | 33 | Even if the majority of the cluster is down, as long as a single node containing a database instance is available, RavenDB can process reads and writes. If a user is writing to the database, RavenDB will hold the data. 34 | 35 | ## Creating a New Database with Replication 36 | 37 | Whenever you create a new database you can specify a replication factor. That is the number of the cluster nodes that will contain an instance of the database. 38 | 39 | ![new database](media/new_database.png) 40 | 41 | It is important to remember that creating a database is a cluster-wide operation and requires a majority to work. 42 | 43 | ## Exploratory Challenge: Creating a Distributed Database 44 | 45 | To practice, I would recommend you to create a new database setting the replication factor to two. I would recommend you to call it `MyDatabase`. (I am assuming that you are running the cluster we set up in the last lesson) 46 | 47 | ## Managing the Database Group 48 | 49 | As you already know, the database group is the set of cluster nodes containing the instances of a database. 50 | 51 | You can manage the database group of a specified database accessing the `Databases` option in the left panel. 52 | 53 | ![new database](media/manage_group.png) 54 | 55 | The image displays that the database we just created has instances on two nodes, Node C and Node A, but it can be different on your computer! 56 | 57 | There is a status `Online` indicating that my database is active (meaning that I have an instance of this database running on this cluster node). Accessing the same screen on the server that has no instance of my new database (in my example, Node B, which is running on `http://127.0.0.2:8080`), the status changes to `Remote`. 58 | 59 | ![new database](media/manage_group.png) 60 | 61 | Clicking on the `Manage Group` button of the database that you want to manage allows you to change the nodes where there are instances of the selected database. 62 | 63 | It is important to remember that managing a database group is a cluster-wide operation that requires a majority to work. 64 | 65 | ## Exploratory Challenge: Managing the Database Group 66 | 67 | Let's try to add a new cluster node to the Database Topology. After this, please, access the `Management Studio` of the corresponding node and see how amazing RavenDB is at replicating your data. 68 | 69 | ## Great Job! 70 | 71 | **Let's move onto [Lesson 3](../lesson3/README.md).** -------------------------------------------------------------------------------- /src/unit4/lesson3/README.md: -------------------------------------------------------------------------------- 1 | # Unit 4, Lesson 3 - Considerations on the Client Side 2 | 3 | In this lesson we will spend some time exploring the interaction between clients and distributed databases. 4 | 5 | ## Coming Back to the DocumentStore 6 | 7 | When creating an instance of the DocumentStore you should specify the list of nodes in the cluster that you are connecting on. 8 | 9 | ```csharp 10 | var store = new DocumentStore 11 | { 12 | Urls = 13 | { 14 | "http://127.0.0.1:8080" ,"http://127.0.0.2:8080" , 15 | "http://128.0.0.3:8080" 16 | }, 17 | Database = "Northwind" 18 | }; 19 | store.Initialize(); 20 | ``` 21 | 22 | Please note that the fact the database you are trying to access does not have instances on all cluster nodes is not important. 23 | 24 | As we mentioned earlier, all cluster nodes contain the full topology for all the databases hosted in the cluster. The very first thing that a client will do upon initialization is to query the defined URLs and figure out what the actual nodes are that it needs to get to the database you are trying to connect to. 25 | 26 | ## Why List All the Nodes in the Cluster 27 | 28 | To be honest, it is not necessary. 29 | 30 | By listing all the nodes in the cluster, we can ensure that if a single node is down and we bring a new client up, we’ll still be able to get the initial topology. If the cluster sizes are small (three to five), you will typically list all the nodes in the cluster. But for more massive clusters you will usually just list enough nodes that are having them all go down at once to mean that you have more pressing concerns than a new client coming up. 31 | 32 | The main idea is to list nodes that you assume will be live when the client tries to connect. 33 | 34 | For extra reliability, the client will also cache the topology on disk. Even if the document store were initialized with a single node that was down at the time the client was restarted, the client would still remember where to look for our database. It’s only an entirely new client that needs to have the full listing. A best practice is to list at least a few nodes, just in case. 35 | 36 | ## Exploratory Challenge: Testing the Client 37 | 38 | Now it is time for you to verify that all I am saying is true: 39 | 40 | 1. Create a client program (as you already know from the previous units) for some replicated database. 41 | 2. When configuring the `DocumentStore` object, specify the address of a cluster node that does not contain an instance of the database. 42 | 3. Try to do a query. 43 | 44 | ## Dealing with Failures 45 | 46 | RavenDB is safe by default! 47 | 48 | While it may seem that an alternate failing (the client isn’t even going to notice) or the preferred node failing (cluster will demote, clients will automatically switch to the first alternate) is all that we need to worry about, those are just the most straightforward and most obvious failure modes that you need to handle in a distributed environment. 49 | 50 | More exciting cases include a node that was split off from the rest of the cluster, along with some (but not all) of the clients. In that case, different clients have very different views about who they can talk to. That’s why each client can failover independently of the cluster. By having the database topology, they know about all the database instances and will try each in turn until they’re able to find an available server that can respond to them. 51 | 52 | This behavior is entirely transparent to your code and an error will be raised only if we can’t reach any of the database instances. 53 | 54 | ## How Database Group Works Saving Data 55 | 56 | Whenever a write is made to any of the database instances, it will disseminate that write to all the other instances in the group. That happens in the background and is continuously running. Most of the time, you don’t need to think about it. You write the data to RavenDB, and it shows up in all the nodes on its own. 57 | 58 | ## Dealing with Important Data 59 | 60 | Sometimes, it is not enough to ensure that you wrote that value to a single node (and made sure it hit the disk). You need to be sure that this value resides in more than one machine. You can do that using write assurance, which is available using the `WaitForReplicationAfterSaveChanges` method. 61 | 62 | ```csharp 63 | using (var session = store.OpenSession()) 64 | { 65 | var newprospect = new Prospect 66 | { 67 | Name = "John Doe", 68 | Company = "ABC Tech", 69 | Email = "johndoe@abctech.com" 70 | }; 71 | session.Store(newprospect); 72 | session.Advanced.WaitForReplicationAfterSaveChanges(replicas: 1); 73 | session.SaveChanges(); 74 | } 75 | ``` 76 | 77 | There isn’t much to change when you move from a single node to a cluster. But here we are asking the database instance we wrote to not to confirm that write until it has been replicated at least once. 78 | 79 | ## Great job! 80 | 81 | At this point you already know the basic (and not so basic) aspects of RavenDB. You can learn more using the [documentation](http://ravendb.net/docs) available online. 82 | 83 | Another valuable resource is the [Ayende's book](https://github.com/ravendb/book) (which is a definitive guide and it is free). 84 | 85 | Thanks for using this bootcamp to learn RavenDB. 86 | -------------------------------------------------------------------------------- /src/unit3/lesson2/README.md: -------------------------------------------------------------------------------- 1 | # Unit 3, Lesson 2 - Document Metadata 2 | 3 | It's time to learn about 4 | an important RavenDB concept: Metadata. 5 | 6 | ## What is the Document Metadata? 7 | 8 | The Metadata is a place that RavenDB uses to store additional information about the documents. 9 | 10 | Every document in RavenDB has metadata attached to it. The metadata, like the 11 | document data, is also stored in JSON format. 12 | 13 | You can use it if you want or need. 14 | 15 | ## Exercise: Loading metadata using a document 16 | 17 | In this exercise you will learn how to get access to a document metadata. 18 | 19 | ### Step 1: Create a new project and install the latest `RavenDB.Client` package 20 | 21 | Start Visual Studio and create a new `Console Application Project` named 22 | `GettingMetadata`. Then, in the `Package Manager Console`, issue the following 23 | command: 24 | 25 | ```powershell 26 | Install-Package RavenDB.Client -Version 5.4.5 27 | ``` 28 | 29 | This will install RavenDB.Client binaries, which you will need in order 30 | to compile your code. 31 | 32 | ### Step 2: Initialize the `DocumentStore` 33 | 34 | Let's manage the `DocumentStore` using the `DocumentStoreHolder` pattern. 35 | 36 | ````csharp 37 | using System; 38 | using Raven.Client; 39 | using Raven.Client.Documents; 40 | using Raven.Client.Documents.Indexes; 41 | 42 | namespace GettingMetadata 43 | { 44 | public static class DocumentStoreHolder 45 | { 46 | private static readonly Lazy LazyStore = 47 | new Lazy(() => 48 | { 49 | var store = new DocumentStore 50 | { 51 | Urls = new[] { "http://localhost:8080" }, 52 | Database = "Northwind" 53 | }; 54 | 55 | return store.Initialize(); 56 | }); 57 | 58 | public static IDocumentStore Store => 59 | LazyStore.Value; 60 | } 61 | } 62 | ```` 63 | 64 | Remember to start the RavenDB server. 65 | 66 | ### Step 3: Loading the metadata 67 | 68 | Now, it's time to load a document metadata. 69 | 70 | ````csharp 71 | static void Main() 72 | { 73 | using (var session = DocumentStoreHolder.Store.OpenSession()) 74 | { 75 | var product = session.Load("products/1-A"); 76 | var metadata = session.Advanced.GetMetadataFor(product); 77 | 78 | foreach (var info in metadata) 79 | { 80 | Console.WriteLine($"{info.Key}: {info.Value}"); 81 | } 82 | } 83 | } 84 | 85 | class Product { } 86 | ```` 87 | 88 | What are we doing here? First we are asking for an instance of the document. Then, using this instance, 89 | we get the metadata. 90 | 91 | This is the output on my machine: 92 | 93 | ```` 94 | @collection: Products 95 | @change-vector: A:96-ZzT6GeIVUkewYXBKQ6vJ9g 96 | @id: products/1-A 97 | @last-modified: 2018-02-28T11:21:24.1425879Z 98 | ```` 99 | 100 | ### Step 4 (bonus): adding a property to the metadata 101 | 102 | Once you have the metadata, you can modify it as you wish. The session tracks changes to both 103 | the document and its metadata, and changes to either of those will cause the document to be 104 | updated on the server once `SaveChanges` method has been called. 105 | 106 | ````csharp 107 | class Program 108 | { 109 | static void Main() 110 | { 111 | using (var session = DocumentStoreHolder.Store.OpenSession()) 112 | { 113 | var product = session.Load("products/1-A"); 114 | var metadata = session.Advanced.GetMetadataFor(product); 115 | 116 | metadata["last-modified-by"] = "Oren Eini"; 117 | session.SaveChanges(); 118 | } 119 | } 120 | } 121 | 122 | class Product { } 123 | ```` 124 | 125 | ## Some Words About the `@collection` Property 126 | 127 | `@collection` specifies the name of the document collection. As you will learn soon, 128 | you can change the value of a metadata property. But you should not try to do this with 129 | `@collection`. 130 | 131 | RavenDB engine does a lot of optimizations based on the collection name, and no support 132 | whatsoever for changing it. 133 | 134 | You can read more about collection concept [here](https://ravendb.net/docs/article-page/4.0/csharp/client-api/faq/what-is-a-collection) 135 | 136 | ## Exercise: Loading only document metadata 137 | 138 | Sometimes you would like to get only the metadata information. It's easy with RavenDB. 139 | 140 | This exercise picks up right where previous one left off. You will just need to change the `Main` method. 141 | 142 | ````csharp 143 | static void Main() 144 | { 145 | using (var session = DocumentStoreHolder.Store.OpenSession()) 146 | { 147 | var command = new GetDocumentsCommand( 148 | "products/1-A", null, metadataOnly: true); 149 | session.Advanced.RequestExecutor.Execute( 150 | command, session.Advanced.Context); 151 | var result = (BlittableJsonReaderObject)command.Result.Results[0]; 152 | var metadata = (BlittableJsonReaderObject)result["@metadata"]; 153 | 154 | foreach (var propertyName in metadata.GetPropertyNames()) 155 | { 156 | metadata.TryGet(propertyName, out var value); 157 | Console.WriteLine($"{propertyName}: {value}"); 158 | } 159 | } 160 | } 161 | ```` 162 | 163 | Don't be afraid of these new types. Take your time to get comfortable with it. 164 | 165 | ## Great Job! 166 | 167 | **Let's move onto [Lesson 3](../lesson3/README.md).** 168 | -------------------------------------------------------------------------------- /src/unit2/lesson1/README.md: -------------------------------------------------------------------------------- 1 | # Unit 2, Lesson 1 - Getting started with indexes 2 | 3 | ## RavenDB Does No Table Scans! 4 | 5 | What does it mean? In Unit-1 you wrote a query similar to the following: 6 | 7 | ````csharp 8 | var orders = ( 9 | from order in session.Query() 10 | where order.Company == "companies/1-A" 11 | orderby order.OrderedAt 12 | select order 13 | ).ToList(); 14 | ```` 15 | 16 | Remembering RQL syntax: 17 | 18 | ```sql 19 | from Orders 20 | where Company = "companies/1-A" 21 | order by OrderedAt 22 | ``` 23 | 24 | You might assume that the following pseudo code is run in the server side. Am I right? 25 | 26 | ````csharp 27 | //.. 28 | var results = new List(); 29 | 30 | foreach (var o in GetDocumentsFor("Orders")) 31 | { 32 | if (o.Company == "companies/1-A") 33 | results.Add(o); 34 | } 35 | 36 | var orderedResults = results.Sort((a,b) => a.OrderedAt.CompareTo(b.OrderedAt)); 37 | // .. 38 | ```` 39 | 40 | The previous code is known as *table scan* (tables?!). That is, all documents are analyzed 41 | and those that meet the predicate are selected. This is quite efficient, as long as the 42 | number of items you have in the database is small. But it fails pretty horribly 43 | the moment your data size reaches any significant size. 44 | 45 | ## RavenDB Uses Indexes to Speed Up the Queries 46 | 47 | Even when you do not create an index, RavenDB will use one to execute queries. 48 | In fact there are no *O(N)* operations in general in RavenDB queries. Using indexes, 49 | queries in RavenDB are *O(logN)* operations. For those who don't care about 50 | algorithms complexity analysis, the difference is between waiting 30 minutes for a result 51 | and getting it right away. 52 | 53 | RavenDB is safe by default and whenever you make a query, the query optimizer will try 54 | to select an appropriate index to use. **If there is no such appropriate index, then 55 | the query optimizer will create an index for you.** 56 | 57 | ## Exercise 1: Confirming that index can be automatically created 58 | In this lesson you will confirm that RavenDB creates indexes automatically when it 59 | is needed. 60 | 61 | ### Step 1: Access Northwind database using the Management Studio 62 | 63 | Start RavenDB console (if you didn't do it yet), and using the web browser, access the 64 | `RavenDB Management Studio` at the address `http://localhost:8080` (which is the 65 | default address. Change it if you need). Then open the `Northwind` database which you 66 | created in the previous unit ([Lesson 1](../../unit1/lesson1/README.md)). 67 | 68 | ### Step 2: Delete all the existing indexes 69 | In the left panel, select the `Indexes` section. Then, select all indexes and then click 70 | on the `Delete` button. 71 | 72 | ![delete all indexes](media/a5s6d678fdsfdfsf8768s7fsdf786876a.png) 73 | 74 | ### Step 3: Create and execute a program which will perform a query 75 | 76 | We could do the following query directly from RavenDB Management Studio. 77 | 78 | ```sql 79 | from Orders 80 | where Company = "companies/1-A" 81 | order by OrderedAt 82 | ``` 83 | 84 | Let's make it more interesting. 85 | 86 | Create a new Console Application, add the proper NuGet RavenDB reference (`RavenDB.Client`), then change the `Program.cs` content to the following: 87 | 88 | ````csharp 89 | using System; 90 | using System.Linq; 91 | using Raven.Client.Documents; 92 | 93 | namespace IndexingSample 94 | { 95 | class Program 96 | { 97 | static void Main() 98 | { 99 | using (var session = DocumentStoreHolder.Store.OpenSession()) 100 | { 101 | var ordersIds = ( 102 | from order in session.Query() 103 | where order.Company == "companies/1-A" 104 | orderby order.OrderedAt 105 | select order.Id 106 | ).ToList(); 107 | 108 | foreach (var id in ordersIds) 109 | { 110 | Console.WriteLine(id); 111 | } 112 | } 113 | } 114 | } 115 | 116 | public static class DocumentStoreHolder 117 | { 118 | private static readonly Lazy LazyStore = 119 | new Lazy(() => 120 | { 121 | var store = new DocumentStore 122 | { 123 | Urls = new[] { "http://localhost:8080" }, 124 | Database = "Northwind" 125 | }; 126 | 127 | return store.Initialize(); 128 | }); 129 | 130 | public static IDocumentStore Store => 131 | LazyStore.Value; 132 | } 133 | 134 | public class Order 135 | { 136 | public string Id { get; set; } 137 | public string Company { get; set; } 138 | public DateTimeOffset OrderedAt { get; set; } 139 | } 140 | } 141 | ```` 142 | Note that the `Order model class` has just enough code here. By default, `Raven.Client` will preserve old properties even if they are not in the model. 143 | This behavior can be changed via the `PreserveDocumentPropertiesNotFoundOnModel` convention 144 | 145 | ### Step 4: Run the program 146 | 147 | ### Step 5: Confirm that a new index was automatically created 148 | 149 | Return to the `Management Studio` and go to the `Indexes/List of Indexes` section. Surprise! A new 150 | index named `Auto/Orders/ByCompanyAndOrderedAt` was created for you. 151 | 152 | RavenDB chooses good names for "auto created indexes". 153 | 154 | ## Great Job! 155 | 156 | **Let's move onto [Lesson 2](../lesson2/README.md) and learn how to create and index on your own.** 157 | -------------------------------------------------------------------------------- /src/unit2/lesson5/README.md: -------------------------------------------------------------------------------- 1 | # Unit 2, Lesson 5 - The Powerful LoadDocument Server-Side Function 2 | 3 | It's time to learn about one of the most tricky features of RavenDB: The 4 | server-side `LoadDocument` function. 5 | 6 | ## Something about size! 7 | 8 | In the [previous lesson](../lesson4/README.md) you wrote this code: 9 | 10 | ````csharp 11 | static void Main(string[] args) 12 | { 13 | using (var session = DocumentStoreHolder.Store.OpenSession()) 14 | { 15 | var results = session 16 | .Query() 17 | .Include(x => x.Category) 18 | .ToList(); 19 | 20 | foreach (var result in results) 21 | { 22 | var category = session.Load(result.Category); 23 | Console.WriteLine($"{category.Name} has {result.Count} items."); 24 | } 25 | } 26 | } 27 | ```` 28 | 29 | As you learned in [unit 1, lesson 4](../../Unit-1/lesson4/README.md) the `Include` method 30 | ensures that all related documents will be returned in a single response from the server, and 31 | this is amazing. 32 | 33 | As you probably deduced, RavenDB uses HTTP as the communication protocol. The client API uses the POST method to issue the query request. 34 | However, GET requests are still handled by the server. Here is the URL to execute the query of the code above: 35 | 36 |
http://localhost:8080/databases/Northwind/queries?query=from%20index%20%27Products/ByCategory%27%20include%20Category
37 | 38 | Click [here](http://localhost:8080/databases/Northwind/queries?query=from%20index%20%27Products/ByCategory%27%20include%20Category) and 39 | check out this query result. 40 | 41 | What you get when you execute this query is a big JSON object. The nice thing is that you get all the data you need with a single request. The bad thing is you receive a lot more than you need. 42 | In the example, all related category documents will be present in the response, but you will use just one property (please, keep in mind that 43 | Category is a small document ...). 44 | 45 | What if you could load the documents in the server-side to produce only the information you need? 46 | 47 | ## Welcome `LoadDocument` 48 | 49 | When writing your index definitions, you can use the `LoadDocument` function to get information from related documents. 50 | 51 | Let's rewrite the `Products_ByCategory` index using the `LoadDocument` function: 52 | 53 | ````csharp 54 | public class Products_ByCategory : 55 | AbstractIndexCreationTask 56 | { 57 | public class Result 58 | { 59 | public string Category { get; set; } 60 | public int Count { get; set; } 61 | } 62 | 63 | public Products_ByCategory() 64 | { 65 | Map = products => 66 | from product in products 67 | let categoryName = LoadDocument(product.Category).Name 68 | select new 69 | { 70 | Category = categoryName, 71 | Count = 1 72 | }; 73 | 74 | Reduce = results => 75 | from result in results 76 | group result by result.Category into g 77 | select new 78 | { 79 | Category = g.Key, 80 | Count = g.Sum(x => x.Count) 81 | }; 82 | } 83 | } 84 | ```` 85 | 86 | Now we are no longer storing the category `Id`, but the `Name`. Now we 87 | can rewrite our program with no includes. 88 | 89 | ````csharp 90 | static void Main(string[] args) 91 | { 92 | using (var session = DocumentStoreHolder.Store.OpenSession()) 93 | { 94 | var query = session 95 | .Query(); 96 | //.Include(x => x.Category); 97 | 98 | var results = ( 99 | from result in query 100 | select result 101 | ).ToList(); 102 | 103 | foreach (var result in results) 104 | { 105 | //var category = session.Load(result.Category); 106 | //Console.WriteLine($"{category.Name} has {result.Count} items."); 107 | Console.WriteLine($"{result.Category} has {result.Count} items."); 108 | } 109 | } 110 | } 111 | ```` 112 | 113 | We will still have only one request... 114 | 115 |
http://localhost:8080/databases/Northwind/queries?query=from%20index%20%27Products/ByCategory%27
116 | 117 | ... but now we will have a smaller response. 118 | 119 | ## It's really good, but... 120 | 121 | The `LoadDocument` feature is a really awesome one, but it is also something that should 122 | be used carefully. 123 | 124 | As you know, `LoadDocument` allows you to load another document during indexing, and to use its data in your index and that is great! 125 | The problem with `LoadDocument` is that it allows users to keep a relational 126 | model when they work with RavenDB, and use `LoadDocument` to get away with 127 | it when they need to do something that is hard to do with RavenDB natively. 128 | That wouldn’t be so bad if `LoadDocument` didn’t have several important costs 129 | associated with it. 130 | 131 | `LoadDocument` is an important feature and you could and should use it. But 132 | I strongly recommend you to treat `LoadDocument` with caution. 133 | 134 | ## Great job! 135 | 136 | Before moving on, I would recommend you to read about the [indexing process for related documents](https://ravendb.net/docs/article-page/4.0/csharp/indexes/indexing-related-documents). 137 | 138 | **Let's move onto [Lesson 6](../lesson6/README.md).** 139 | -------------------------------------------------------------------------------- /src/unit1/lesson2/README.md: -------------------------------------------------------------------------------- 1 | # Unit 1, Lesson 2 - Let's Query! 2 | 3 | In the previous lesson, you learned how to install RavenDB on your computer, 4 | create a database, and load sample data. 5 | 6 | In this lesson you will learn how to write your own queries using RQL. 7 | 8 | ## What is RQL? 9 | 10 | RQL is a powerful and easy to learn language that we designed to make your job easier. 11 | 12 | From the Documentation: 13 | 14 | > RQL, the Raven Query Language, is a SQL-like language used to retrieve the data 15 | > from the server when queries are being executed. It is designed to expose the RavenDB 16 | > query pipeline in a way that is easy to understand, easy to use, and not 17 | > overwhelming to the user. 18 | 19 | Please consider reading the [RQL documentation](https://ravendb.net/docs/article-page/4.0/csharp/indexes/querying/what-is-rql). 20 | 21 | ## Exercise: Getting Ready to Write Queries 22 | 23 | 1. Open the `RavenDB Management Studio` ( by default.). 24 | 2. In the left panel, click on `Databases`. 25 | 3. Open the database we created in the previous lesson (Northwind, if you 26 | followed our recommendation). 27 | 4. In the left panel, select the `Documents` section. 28 | 5. Click on `Query`. 29 | 30 | ![Fig 1](media/23k4h1k2j4hk24kh12khj243.png) 31 | 32 | ## Exercise: You First Query 33 | 34 | Let’s start with an easy query. 35 | 36 | 1. Assuming you are already in the Query editor (inside the RavenDB Management 37 | Studio), type the following query. 38 | 39 | ```sql 40 | from Employees 41 | ``` 42 | 2. Click on the `Run` button. 43 | 44 | This query returns all the documents inside the `Employees` collection. 45 | 46 | Try other queries like these. Get all the documents from the `Products` collection. 47 | 48 | ## Exercise: Filtering 49 | 50 | Getting all documents from a collection is nice, but quite useless. Let’s make something more exciting. 51 | 52 | ```sql 53 | from Employees 54 | where FirstName == "Nancy" 55 | ``` 56 | 57 | FirstName is the name of one of the properties present in the documents from the Employees collection. 58 | 59 | ```json 60 | { 61 | "LastName": "Davolio", 62 | "FirstName": "Nancy", 63 | "Title": "Sales Representative", 64 | "Address": { 65 | "Line1": "507 - 20th Ave. E.\r\nApt. 2A", 66 | "Line2": null, 67 | "City": "Seattle", 68 | "Region": "WA", 69 | "PostalCode": "98122", 70 | "Country": "USA", 71 | "Location": { 72 | "Latitude": 47.623473, 73 | "Longitude": -122.306009 74 | } 75 | }, 76 | "HiredAt": "1992-05-01T00:00:00.0000000", 77 | "Birthday": "1948-12-08T00:00:00.0000000", 78 | "HomePhone": "(206) 555-9857", 79 | "Extension": "5467", 80 | "ReportsTo": "employees/2-A", 81 | "Notes": [ 82 | "Education includes a BA in psychology", 83 | "from Colorado State University in 1970.", 84 | "She also completed \"The Art of the Cold Call\".", 85 | "Nancy is a member of Toastmasters International." 86 | ], 87 | "Territories": [ 88 | "06897", 89 | "19713" 90 | ], 91 | "@metadata": { 92 | "@collection": "Employees", 93 | "@flags": "HasAttachments" 94 | } 95 | } 96 | ``` 97 | 98 | ## Exercise: Shaping the Query Result 99 | 100 | Up to this point we are simply retrieving documents. Let’s say we want to shape what we get. Consider the following query: 101 | 102 | ```sql 103 | from Orders 104 | where Lines.Count > 4 105 | select Lines[].ProductName as ProductNames, OrderedAt, ShipTo.City as City 106 | ``` 107 | 108 | Here, we are not interested in all data from the Orders documents. We are specifying a shape. 109 | 110 | One of the results will look like: 111 | 112 | ```json 113 | { 114 | "ProductNames": [ 115 | "Ikura", 116 | "Gorgonzola Telino", 117 | "Geitost", 118 | "Boston Crab Meat", 119 | "Lakkalikööri" 120 | ], 121 | "OrderedAt": "1996-08-05T00:00:00.0000000", 122 | "ShipTo.City": "Cunewalde", 123 | "@metadata": { 124 | "@flags": "HasRevisions", 125 | "@id": "orders/26-A", 126 | "@last-modified": "2018-02-28T11:21:24.1689975Z", 127 | "@change-vector": "A:275-ZzT6GeIVUkewYXBKQ6vJ9g", 128 | "@projection": true, 129 | "@index-score": 1 130 | } 131 | } 132 | ``` 133 | 134 | We will talk about the `metadata` in the future. 135 | 136 | ## Exercise: Using JavaScript in the Query Projections 137 | 138 | Say you want to do more customization. 139 | 140 | ```sql 141 | from Orders as o 142 | load o.Company as c 143 | select { 144 | Name: c.Name.toLowerCase(), 145 | Country: c.Address.Country, 146 | LinesCount: o.Lines.length 147 | } 148 | ``` 149 | RavenDB allows you to use JavaScript when defining projections for the query results. 150 | 151 | There is another interesting thing in this query. The 152 | Company field of an Order document contains the ID of another document stored in 153 | the database. The load instruction is smart enough to get that document for you. 154 | You can use it to project data as well. 155 | 156 | ## Exercise: Map and Reduce 157 | 158 | Consider the following query: 159 | 160 | ```sql 161 | from Orders 162 | group by Company 163 | where count() > 5 164 | order by count() desc 165 | select count() as Count, key() as Company 166 | ``` 167 | 168 | Here, we are grouping the Orders using the Company field as a 169 | grouping key. We are adding a filter to get only groups with six documents 170 | at least, then ordering these groups by the number of elements in descending 171 | order. Finally, we are projecting the number of documents per group and the 172 | group key. 173 | 174 | This query results in a list of top buyers companies. 175 | 176 | ## How it Works 177 | 178 | For a while, you shouldn’t care about our implementation details. But, it’s 179 | important to say that we are concerned about performance and we use a bunch of 180 | techniques to deliver results as fast as possible (even more!). 181 | 182 | All queries in RavenDB are supported by a sophisticated and efficient indexing 183 | mechanism. In simple words: We use indexes for all the queries. I will 184 | explain this in greater detail in future lessons. 185 | 186 | ## Lesson Complete! 187 | 188 | You have just completed the second lesson. Now you know the basics about Querying with RavenDB. 189 | 190 | **Let's move on to** [Lesson 3](../lesson3/README.md) **and start coding.** 191 | -------------------------------------------------------------------------------- /src/unit3/lesson3/README.md: -------------------------------------------------------------------------------- 1 | # Unit 3, Lesson 3 - Getting started with Operations and Commands 2 | 3 | You already know the basics of RavenDB. But 4 | there are a lot of specifics that can help you to create amazing solutions. 5 | 6 | In this lesson you will learn about RavenDB Operations and Commands. For the most part, that is 7 | something you rarely need to use. But is good to know that this is available, just 8 | in case. 9 | 10 | ## What are RavenDB Operations and Commands? 11 | 12 | Using Operations and Commands you can manipulate data and change 13 | configuration on a server. But isn't this exactly what you do using 14 | the `session` object? 15 | 16 | The `session` is a high level interface to RavenDB which provides the identity map 17 | and LINQ queries. But if you want do something in the low-level, then you should 18 | start using the Operations. Also, there are operations for Database Maintenance, Server Maintenance and Patching 19 | 20 | There is an exhaustive list of RavenDB operations available in the [official documentation](https://ravendb.net/docs/article-page/4.0/csharp/client-api/operations/what-are-operations). 21 | 22 | ## First-Time Using RavenDB Operations and Commands 23 | 24 | You already used a command in the [previous lesson](../lesson1/README.md) to get 25 | the metadata information from the server. Let's remember it. 26 | 27 | ````csharp 28 | static void Main() 29 | { 30 | using (var session = DocumentStoreHolder.Store.OpenSession()) 31 | { 32 | var command = new GetDocumentsCommand( 33 | "products/1-a", null, metadataOnly: true); 34 | session.Advanced.RequestExecutor.Execute( 35 | command, session.Advanced.Context); 36 | var result = (BlittableJsonReaderObject)command.Result.Results[0]; 37 | var metadata = (BlittableJsonReaderObject)result["@metadata"]; 38 | 39 | foreach (var propertyName in metadata.GetPropertyNames()) 40 | { 41 | metadata.TryGet(propertyName, out var value); 42 | Console.WriteLine($"{propertyName}: {value}"); 43 | } 44 | } 45 | } 46 | ```` 47 | 48 | The `GetDocumentsCommand` method exposes a low-level way to get the metadata from a potentially large document without loading it. 49 | 50 | ## Exercise: Adding an order's line into an order document without loading the entire document 51 | 52 | Consider the document `orders/816-a` from the Northwind database. 53 | 54 | ````csharp 55 | { 56 | "Company": "companies/37", 57 | "Employee": "employees/3", 58 | "OrderedAt": "1998-04-30T00:00:00.0000000", 59 | "RequireAt": "1998-05-28T00:00:00.0000000", 60 | "ShippedAt": "1998-05-06T00:00:00.0000000", 61 | "ShipTo": { 62 | "Line1": "8 Johnstown Road", 63 | "Line2": null, 64 | "City": "Cork", 65 | "Region": "Co. Cork", 66 | "PostalCode": null, 67 | "Country": "Ireland" 68 | }, 69 | "ShipVia": "shippers/2", 70 | "Freight": 81.73, 71 | "Lines": [ 72 | { 73 | "Product": "products/34", 74 | "ProductName": "Sasquatch Ale", 75 | "PricePerUnit": 14, 76 | "Quantity": 30, 77 | "Discount": 0 78 | }, 79 | { 80 | "Product": "products/40", 81 | "ProductName": "Boston Crab Meat", 82 | "PricePerUnit": 18.4, 83 | "Quantity": 40, 84 | "Discount": 0.1 85 | }, 86 | { 87 | "Product": "products/41", 88 | "ProductName": "Jack's New England Clam Chowder", 89 | "PricePerUnit": 9.65, 90 | "Quantity": 30, 91 | "Discount": 0.1 92 | } 93 | ] 94 | } 95 | ```` 96 | 97 | What if you want to add an order's line? Would we need to load the entire document? No! 98 | 99 | ### Step 1: Create a new project and install `RavenDB.Client` package 100 | 101 | Start Visual Studio and create a new `Console Application Project` named 102 | `UsingCommands`. Then, in the `Package Manager Console`, issue the following 103 | command: 104 | 105 | ```powershell 106 | Install-Package RavenDB.Client -Version 5.4.5 107 | ``` 108 | 109 | ### Step 2: Initialize the `DocumentStore` 110 | 111 | Let's manage the `DocumentStore` using our great friend `DocumentStoreHolder` pattern. 112 | 113 | ````csharp 114 | using System; 115 | using Raven.Client; 116 | using Raven.Client.Documents; 117 | 118 | namespace GettingMetadata 119 | { 120 | public static class DocumentStoreHolder 121 | { 122 | private static readonly Lazy LazyStore = 123 | new Lazy(() => 124 | { 125 | var store = new DocumentStore 126 | { 127 | Urls = new[] { "http://localhost:8080" }, 128 | Database = "Northwind" 129 | }; 130 | 131 | return store.Initialize(); 132 | }); 133 | 134 | public static IDocumentStore Store => 135 | LazyStore.Value; 136 | } 137 | } 138 | ```` 139 | 140 | Remember to have started the RavenDB server. 141 | 142 | ### Step 3: Update the document using a `PatchCommand` 143 | 144 | It's easy to change the document. 145 | 146 | We can use the untyped API, which is useful when you don't have 147 | C# types to represent your model. 148 | 149 | ````csharp 150 | static void Main() 151 | { 152 | using (var session = DocumentStoreHolder.Store.OpenSession()) 153 | { 154 | session.Advanced.Defer(new PatchCommandData( 155 | id: "orders/816-A", 156 | changeVector: null, 157 | patch: new PatchRequest 158 | { 159 | Script = "this.Lines.push(args.NewLine)", 160 | Values = 161 | { 162 | { 163 | "NewLine", new 164 | { 165 | Product = "products/1-a", 166 | ProductName = "Chai", 167 | PricePerUnit=18M, 168 | Quantity=1, 169 | Discount=0 170 | } 171 | } 172 | } 173 | 174 | }, 175 | patchIfMissing: null)); 176 | 177 | session.SaveChanges(); 178 | } 179 | } 180 | ```` 181 | 182 | In this example, you used the `Patch` command which performs partial document updates without having to load, 183 | modify, and save a full document. The `Script` needs to be in JavaScript. 184 | 185 | You can use the typed version as well. 186 | 187 | ```csharp 188 | static void Main() 189 | { 190 | using (var session = DocumentStoreHolder.Store.OpenSession()) 191 | { 192 | session.Advanced.Patch("orders/816-A", 193 | x => x.Lines, 194 | lines => lines.Add(new OrderLine 195 | { 196 | Product = "products/1-a", 197 | ProductName = "Chai", 198 | PricePerUnit = 18M, 199 | Quantity = 1, 200 | Discount = 0 201 | })); 202 | 203 | session.SaveChanges(); 204 | } 205 | } 206 | ``` 207 | As usual, you don't need the complete model classes. 208 | ```csharp 209 | public class Order 210 | { 211 | public List Lines { get; set; } 212 | } 213 | 214 | public class OrderLine 215 | { 216 | public string Product { get; set; } 217 | public string ProductName { get; set; } 218 | public decimal PricePerUnit { get; set; } 219 | public int Quantity { get; set; } 220 | public decimal Discount { get; set; } 221 | } 222 | ``` 223 | 224 | 225 | This version is a lot easier. Right? 226 | 227 | ## Great job! Onto Lesson 4! 228 | 229 | Awesome! 230 | 231 | If you want to know more about Patching we recomend you to use the [documentation](https://ravendb.net/docs/article-page/4.0/csharp/client-api/operations/patching/single-document) 232 | 233 | **Let's move onto [Lesson 4](../lesson4/README.md).** 234 | -------------------------------------------------------------------------------- /src/unit1/lesson1/README.md: -------------------------------------------------------------------------------- 1 | # Unit 1, Lesson 1 - Getting Started 2 | 3 | Working with databases does not have to be boring. RavenDB makes it easy! 4 | 5 | Gone are the days thinking about how to accommodate your data in rows and 6 | columns. 7 | 8 | Do you already know RavenDB? Great! Prepare to learn some new tricks in this Bootcamp. 9 | 10 | Let's start simple. 11 | 12 | In this lesson, you will learn how to install and start using RavenDB on your computer. 13 | 14 | ## Getting RavenDB up and Running 15 | 16 | Let's assume that you are a developer who wants RavenDB running on your computer. 17 | 18 | For production scenarios, I recommend you to check other online resources. 19 | 20 | ### Exercise: Running on the Live Demo Instance 21 | 22 | Don’t want to download bits and bytes? No problem! Point your browser to our public 23 | demo at . 24 | 25 | Feel free to use our live demo for quick checks and verifications. But it's not recommended 26 | to use it for anything more serious. All data in the live instance is 27 | public! There are no guarantees about availability. 28 | 29 | ### Exercise: Running on Docker 30 | 31 | If you have Docker installed, run the following command: 32 | 33 | ``` 34 | docker run -p 8080:8080 ravendb/ravendb 35 | ``` 36 | 37 | Docker will now get the latest RavenDB version and spin up a new container to 38 | host it. Note that we run it in developer mode without any authentication. 39 | 40 | You can learn a lot about how RavenDB works on Docker following the [official documentation](https://ravendb.net/docs/article-page/4.0/csharp/start/installation/running-in-docker-container). 41 | 42 | ### Exercise: Running on Windows, Linux or MacOS 43 | 44 | If you want to set up RavenDB on Windows, Linux, or MacOS, you will need to download 45 | it from . Make sure you are selecting the right 46 | distribution for your platform. You will get a compressed file that you can extract to 47 | a folder of your preference. 48 | 49 | Now run the `./run.ps1` (or `./run.sh` depending on your operational system) located in RavenDB folder. This will start 50 | a console application in interactive mode inside a console application. The script 51 | will open your browser and start the RavenDB Management Studio. 52 | 53 | Pay attention to an error that might occur on a Windows 10 versions - `script execution is disabled on this system` while running - `./run.ps1` command 54 | To address this issue open `PowerShell` with an administrator privilege and simply execute the given command. 55 | 56 | ``` 57 | Set-ExecutionPolicy -ExecutionPolicy Unrestricted 58 | ``` 59 | 60 | The RavenDB wizard will determine the right address for you. Let's assume in this tutorial it is `http://localhost:8080`. 61 | 62 | Running RavenDB for the first time, you will need to do a little setup. Just 63 | need to answer the questions. More information is available in the [official documentation](https://ravendb.net/docs/article-page/4.0/csharp/start/getting-started) 64 | 65 | ## Your First Database 66 | 67 | The RavenDB Management Studio was completely re-designed. We did a lot of work to make easier to use than ever. 68 | 69 | ![Fig 1](media/d1ff71a639f63e04488b56706a91f423.png) 70 | 71 | ### Exercise: Creating a Database 72 | 73 | To create your first database: 74 | 75 | 1. Select the `Database` option in the left panel. 76 | 77 | 2. Click on the `New Database` button. 78 | 79 | 3. Type a name for the new database (we recommend Northwind for this lesson) 80 | 81 | 4. Click on the `Create` button 82 | 83 | Congratulations! You just created your first RavenDB database. 84 | 85 | But it's empty. 86 | 87 | ![Fig 2](media/3f7ec9fbf9d626ebbe905e7a589e81ed.png) 88 | 89 | ### Exercise: Loading Sample Data 90 | 91 | Let’s load some sample data into our database. 92 | 93 | 1. Select `Databases` on the left panel 94 | 2. In the right panel, click on the name of the database you just created (that 95 | is empty for a while) 96 | 3. In the left panel, click on `Tasks`, and then `Create Sample Data` 97 | 98 | ![Fig 3](media/26de5d4d9b2cf6a0f8867677aa776b45.png) 99 | 100 | 4. Click on the big `Create` button 101 | 102 | > The Northwind database is the sample database that came with SQL Server; 103 | > it has been used for decades as the sample database in the Microsoft 104 | > community. We chose this database as our sample data because you will be familiar with its relational format. 105 | 106 | Going to the `Documents` section (left panel), you will see that we created a 107 | lot of documents for you. 108 | 109 | ![Fig 4](media/3f24692d124b788b08cb11e49d8fb66f.png) 110 | 111 | ## Exploring the Database 112 | 113 | We just launched RavenDB in interactive mode, created our first 114 | database, and loaded some sample data. But it looks remarkably similar to 115 | what you see in a relational database. 116 | 117 | The data is shown in a grid format with the tables on the left. 118 | 119 | If you click on any *record*, you will begin to see the NoSQL magic! 120 | 121 | ![Fig 5](media/4bcc55018cd05b354a0d98c3ce7bcfb7.png) 122 | 123 | All RavenDB data is stored as JSON. 124 | 125 | ## Understanding the `Document` Concept 126 | 127 | Using the `Go to document` feature (the text box in the Studio toolbar), go to 128 | the document `orders/101-A`. 129 | 130 | ```json 131 | { 132 | "Company": "companies/86-A", 133 | "Employee": "employees/4-A", 134 | "Freight": 0.78, 135 | "Lines": [ 136 | { 137 | "Discount": 0.15, 138 | "PricePerUnit": 14.4, 139 | "Product": "products/1-A", 140 | "ProductName": "Chai", 141 | "Quantity": 15 142 | }, 143 | { 144 | "Discount": 0, 145 | "PricePerUnit": 7.2, 146 | "Product": "products/23-A", 147 | "ProductName": "Tunnbröd", 148 | "Quantity": 25 149 | } 150 | ], 151 | "OrderedAt": "1996-11-07T00:00:00.0000000", 152 | "RequireAt": "1996-12-05T00:00:00.0000000", 153 | "ShipTo": { 154 | "City": "Stuttgart", 155 | "Country": "Germany", 156 | "Line1": "Adenauerallee 900", 157 | "Line2": null, 158 | "Location": { 159 | "Latitude": 48.7794494, 160 | "Longitude": 9.1852878 161 | }, 162 | "PostalCode": "70563", 163 | "Region": null 164 | }, 165 | "ShipVia": "shippers/2-A", 166 | "ShippedAt": "1996-11-15T00:00:00.0000000", 167 | "@metadata": { 168 | "@collection": "Orders", 169 | "@flags": "HasRevisions" 170 | } 171 | } 172 | ``` 173 | It is very different from what we're used to in relational databases. 174 | 175 | > A document is a self-describing, hierarchical tree data structure which 176 | > can consist of maps, collections, and scalar values. 177 | 178 | RavenDB database stores documents, which are plain JSON 179 | formatted data. We can aggregate related information into a common object, 180 | as in the case of the `ShipTo` property which has all the shipping information. 181 | 182 | In a Document Database documents are organized in collections. 183 | 184 | ## Understanding the `Collection` Concept 185 | 186 | Collections provide a good way to establish a level of organization. For 187 | example, documents holding customer data are very different from documents 188 | holding product information, and you want to talk about groups of them. RavenDB 189 | allows for a document to be stamped with a string value that will be evidence of 190 | its type (like "Customers" and "Products"). 191 | 192 | Documents that are in the same collection can have a completely 193 | different structures. Because RavenDB is schemaless, this is fine. 194 | 195 | ## Exercise: Exploring the Northwind Collections 196 | 197 | 1. Open the RavenDB Management Studio at 198 | 2. Open the Northwind Database 199 | 3. In the `Documents` section, explore all the collections. 200 | 201 | The user interface is pretty simple. We strongly recommend you to try to create your own documents, edit them, and so on. 202 | You can create another database and load the sample data if you want. 203 | 204 | ## Lesson Complete 205 | 206 | If you are just getting started with document databases and want to learn more about 207 | document modeling, we recommend you to watch [Ayende’s talk](https://www.youtube.com/watch?v=FY0BiZaJwL4). 208 | 209 | **Let's move onto** [Lesson 2](../lesson2/README.md) **and start querying.** 210 | -------------------------------------------------------------------------------- /src/unit1/lesson3/README.md: -------------------------------------------------------------------------------- 1 | # Unit 1, Lesson 3 - Let's Code! 2 | 3 | Until now, we were only using the Management Studio. Now it's time to write some code. 4 | 5 | In this lesson you will write some very simple C# programs which load data from the database. 6 | 7 | You can also use Java, Node, Python, Ruby, Go... There are different 8 | syntaxes but the main idea remains the same. 9 | 10 | This lesson picks up right where [Lesson 2](../lesson2/README.md) left off. 11 | 12 | ## Exercise 1: Your First RavenDB Client Application 13 | 14 | In this exercise we will write a very basic RavenDB client application. You 15 | can use .NET core or .NET full. 16 | 17 | ### Step 1: Create a New Project and Install the Latest `RavenDB.Client` Package 18 | 19 | Start Visual Studio and create a new `Console Application Project` named 20 | Northwind. Then, in the `Package Manager Console`, issue the following 21 | command: 22 | 23 | ```powershell 24 | Install-Package RavenDB.Client -Version 5.4.5 25 | ``` 26 | 27 | This will install RavenDB.Client binaries which you will need in order 28 | to compile your code. 29 | 30 | You will need to add the `using` namespace at the top of `Program.cs`: 31 | 32 | ````csharp 33 | using Raven.Client.Documents; 34 | ```` 35 | 36 | ### Step 2: Initialize the `DocumentStore` 37 | 38 | Start your `Main` method with the following code: 39 | 40 | ````csharp 41 | var documentStore = new DocumentStore 42 | { 43 | Urls = new [] {"http://localhost:8080"}, 44 | Database = "Northwind" 45 | }; 46 | 47 | documentStore.Initialize(); 48 | ```` 49 | 50 | The document store is the starting point for all your interactions with RavenDB. 51 | 52 | > A document store is the main client API object which establishes and manages the connection channel between an application and a database instance. 53 | It acts as the connection manager and also exposes methods to perform all operations that you can run against an associated server instance. 54 | The document store object has an array of URL addresses to the cluster nodes. However, it can work against multiple databases that exist there. 55 | 56 | > To learn more about the `DocumentStore`, access the [official documentation](https://ravendb.net/docs/article-page/4.0/csharp/client-api/what-is-a-document-store). 57 | 58 | ### Step 3: Load a Document from the Server 59 | 60 | After creating an instance of the `DocumentStore` we are ready to interact with the 61 | database. 62 | 63 | After creating a RavenDB document store, we are ready to use the database server instance it is pointing at. For any operation we want to perform on the DB, we start by obtaining a new Session object from the document store. The Session object will contain everything needed to perform any operation necessary. 64 | 65 | ````csharp 66 | using (var session = documentStore.OpenSession()) 67 | { 68 | var p = session.Load("products/1-A"); 69 | System.Console.WriteLine(p.Name); 70 | } 71 | ```` 72 | 73 | RavenDB is schemaless. You don't need to create any class to load documents from the server. 74 | 75 | > When you want to store data in a relational database, you first define a schema - 76 | > a defined structure for the database which says what tables and columns exist and 77 | > which data types each columns can hold. With RavenDB, storing and loading data is much 78 | > more casual. 79 | 80 | Here is the complete code of this exercise. 81 | 82 | ````csharp 83 | using Raven.Client.Documents; 84 | 85 | namespace Northwind 86 | { 87 | class Program 88 | { 89 | static void Main() 90 | { 91 | var documentStore = new DocumentStore 92 | { 93 | Urls = new[] { "http://localhost:8080" }, 94 | Database = "Northwind" 95 | }; 96 | 97 | documentStore.Initialize(); 98 | using (var session = documentStore.OpenSession()) 99 | { 100 | var p = session.Load("products/1-A"); 101 | System.Console.WriteLine(p.Name); 102 | } 103 | System.Console.WriteLine("Press to continue..."); 104 | System.Console.ReadLine(); 105 | } 106 | } 107 | } 108 | ```` 109 | 110 | ## Understanding the Concept of `Unique Identifiers` 111 | 112 | There is no need to inform the source collection when loading 113 | a document. It is possible because, as you learned in [Lesson 1](../lesson1/README.md), the collection 114 | in RavenDB is just "virtual." 115 | 116 | To load a document, you simply need to specify its ID. Any 117 | string can be used as a document ID. RavenDB uses a default convention 118 | where the collection name is used as a prefix followed by a unique value. 119 | 120 | The document ID is equivalent for the primary key in a relational system. Unlike 121 | a primary key which is unique per table, the document ID is unique per database. 122 | This also means that whenever you store a new document using a key that is already used 123 | by a document, that document will get overwritten with the new document data. 124 | 125 | > You can learn more about how to work with document IDs reading the [official documentation](https://ravendb.net/docs/article-page/4.0/csharp/client-api/document-identifiers/working-with-document-identifiers) 126 | 127 | ## Exercise 2: Improving the Code with Some Types 128 | 129 | This exercise picks up right where the previous one left off. 130 | 131 | In the previous exercise we started loading data from the server without defining 132 | any class. The schemaless nature of RavenDB, combined with the `dynamic` keyword in C# 133 | allows us to work in a completely dynamic world. But for most things, we 134 | do want some structure. 135 | 136 | ### Step 1: Introducing Model Classes 137 | 138 | You could define a very basic model to handle our product information. 139 | 140 | ````csharp 141 | public class Product 142 | { 143 | public string Id { get; set; } 144 | public string Name { get; set; } 145 | } 146 | ```` 147 | 148 | That's good enough if you just want to load partial information of the documents. 149 | But you should always define or model classes as complete as possible. 150 | 151 | In this exercise, you can ask the `Studio` to provide type information compatible with 152 | Northwind through `Settings`/`Create Sample Data`/`View C# classes`. 153 | 154 | You can even request the model class while editing a document clicking in the caret next to 155 | `Copy to clipboard`, then `Copy as C# class`. 156 | 157 | ````csharp 158 | public class Product 159 | { 160 | public string Name { get; set; } 161 | public string Supplier { get; set; } 162 | public string Category { get; set; } 163 | public string QuantityPerUnit { get; set; } 164 | public float PricePerUnit { get; set; } 165 | public int UnitsInStock { get; set; } 166 | public int UnitsOnOrder { get; set; } 167 | public bool Discontinued { get; set; } 168 | public int ReorderLevel { get; set; } 169 | } 170 | ```` 171 | 172 | You can see that there really isn't anything special about these classes. There 173 | is no base class, attributes or even virtual members. 174 | 175 | ### Step 2: Using Your Model Class when Interacting with RavenDB 176 | 177 | Starting to use model classes is really easy with RavenDB. 178 | 179 | ````csharp 180 | using (var session = documentStore.OpenSession()) 181 | { 182 | var p = session.Load("products/1-A"); 183 | System.Console.WriteLine(p.Name); 184 | } 185 | ```` 186 | The code is the same as before but you are using the `Product` class instead of `dynamic`. 187 | 188 | It just works! 189 | 190 | Here is the complete code for this exercise. 191 | 192 | ````csharp 193 | using Raven.Client.Documents; 194 | 195 | namespace Northwind 196 | { 197 | class Program 198 | { 199 | static void Main() 200 | { 201 | var documentStore = new DocumentStore 202 | { 203 | Urls = new { "http://localhost:8080" }, 204 | Database = "Northwind" 205 | }; 206 | 207 | documentStore.Initialize(); 208 | 209 | using (var session = documentStore.OpenSession()) 210 | { 211 | var p = session.Load("products/1-A"); 212 | System.Console.WriteLine(p.Name); 213 | } 214 | } 215 | } 216 | 217 | public class Product 218 | { 219 | public string Name { get; set; } 220 | public string Supplier { get; set; } 221 | public string Category { get; set; } 222 | public string QuantityPerUnit { get; set; } 223 | public float PricePerUnit { get; set; } 224 | public int UnitsInStock { get; set; } 225 | public int UnitsOnOrder { get; set; } 226 | public bool Discontinued { get; set; } 227 | public int ReorderLevel { get; set; } 228 | } 229 | } 230 | ```` 231 | 232 | ## Great job! 233 | 234 | The third lesson is done. 235 | 236 | **Let's move on to [Lesson 4](../lesson4/README.md) and learn more about the `DocumentStore`.** 237 | -------------------------------------------------------------------------------- /src/unit1/lesson5/README.md: -------------------------------------------------------------------------------- 1 | # Unit 1, Lesson 5 - Loading Documents 2 | 3 | We have been loading documents since [lesson 3](../lesson3/README.md), and now it's time to learn what alternatives you have. 4 | 5 | ## Before Loading, Establishing a `Session` 6 | 7 | The session is the primary way your code interacts with RavenDB. You need 8 | to create a session via the document store, and then use the session methods 9 | to perform operations. 10 | 11 | A session object is very cheap to create and very easy to use. To create a 12 | session, you simply call the `DocumentStore.OpenSession()` method. 13 | 14 | ````csharp 15 | using (var session = DocumentStoreHolder.Store.OpenSession()) 16 | { 17 | // ready to interact with the database 18 | } 19 | ```` 20 | 21 | ## The Basics of the `Load` Method 22 | 23 | As the name implies, the `Load` method gives you the option of loading a document 24 | or a set of documents, passing the document(s) ID(s) as parameters. The result will 25 | be an object representing the document or `null` if the document does not exist. 26 | 27 | A document is loaded only once in a session. Even though we call the `Load` method 28 | twice passing the same document ID, only a single remote call to the server will 29 | be made. Whenever a document is loaded, it is added to an internal dictionary managed 30 | by the session. 31 | 32 | ### Exercise: Confirming that a document is loaded only once in a session 33 | 34 | This exercise picks up right where the previous one in the last lesson left off. 35 | 36 | Change your program to call the `Load` method two times passing the same 37 | value as the parameter. Then, use the `Debug.Assert` method to confirm the 38 | resulting objects are the same. 39 | 40 | ````csharp 41 | using (var session = DocumentStoreHolder.Store.OpenSession()) 42 | { 43 | var p1 = session.Load("products/1-A"); 44 | var p2 = session.Load("products/1-A"); 45 | Debug.Assert(ReferenceEquals(p1, p2)); 46 | } 47 | ```` 48 | 49 | 50 | ## Loading Multiple Documents with a Single `Load` Call 51 | 52 | Load can also read more than a single document at a time. For example: 53 | 54 | ````csharp 55 | var products = session.Load(new [] { 56 | "products/1-A", 57 | "products/2-A", 58 | "products/3-A" 59 | }); 60 | ```` 61 | 62 | This will result in a dictionary with three documents in it, **retrieved in a single 63 | remote call from the server**. 64 | 65 | ## Loading Related Documents in a Single Remote Call 66 | 67 | The easiest way to kill your application performance is to make a lot of remote calls. RavenDB provides a lot of features to help you 68 | significantly reduce calls and boost performance. 69 | 70 | Consider the Northwind `products/1-A` document: 71 | 72 | ````csharp 73 | { 74 | "Name": "Chai", 75 | "Supplier": "suppliers/1-A", 76 | "Category": "categories/1-A", 77 | "QuantityPerUnit": "10 boxes x 20 bags", 78 | "PricePerUnit": 18, 79 | "UnitsInStock": 1, 80 | "UnitsOnOrder": 39, 81 | "Discontinued": false, 82 | "ReorderLevel": 10, 83 | "@metadata": { 84 | "@collection": "Products" 85 | } 86 | } 87 | ```` 88 | 89 | As you can see, the `Supplier` and `Category` properties are clearly references to 90 | other documents. 91 | 92 | Considering you need to load the product and the related category, how would you 93 | write the code? Your first attempt could be something like this: 94 | 95 | ````csharp 96 | var p = session.Load("products/1-A"); 97 | var c = session.Load(p.Category); 98 | ```` 99 | 100 | This approach will make two remote calls -- not good. 101 | 102 | ````csharp 103 | var p = session 104 | .Include(x => x.Category) 105 | .Load("products/1-A"); 106 | 107 | var c = session.Load(p.Category); 108 | ```` 109 | 110 | The `Include` session method changes the way RavenDB will process the request. 111 | 112 | It will: 113 | 114 | * Find a document with the ID: `products/1-A` 115 | * Read its `Category` property value 116 | * Find a document with that ID 117 | * Send both documents back to the client 118 | 119 | When the `session.Load(p.Category);` is executed, the document is in the 120 | session cache and no additional remote call is made. 121 | 122 | Here is a powerful example of the application of this technique in a complex scenario. 123 | 124 | ````csharp 125 | var order = session 126 | .Include(x => x.Company) 127 | .Include(x => x.Employee) 128 | .Include(x => x.Lines.Select(l => l.Product)) 129 | .Load("orders/1-A"); 130 | ```` 131 | 132 | This code will, in a single remote call, load the order, include the company 133 | and employee documents, and also load *all* the products in all the lines 134 | in the order. 135 | 136 | ## Exercise: Exploring Orders 137 | 138 | In this exercise we will create an "Orders Explorer" for the Northwind database. 139 | 140 | ### Step 1: Create a new project and install the latest `RavenDB.Client` package 141 | 142 | As you learned in lesson 2, start Visual Studio and create a new `Console Application Project` named 143 | Northwind. Then, in the `Package Manager Console`, issue the following command: 144 | 145 | ```powershell 146 | Install-Package RavenDB.Client -Version 5.4.5 147 | ``` 148 | 149 | Then you will need to add the `using` namespace at the top of `Program.cs`: 150 | 151 | ````csharp 152 | using Raven.Client.Documents; 153 | ```` 154 | 155 | ### Step 2: Create the `DocumentStoreHolder` 156 | 157 | As you learned in lesson 3, add a new class in your project named `DocumentStoreHolder`. 158 | Then replace the file content with: 159 | 160 | ````csharp 161 | using System; 162 | using Raven.Client; 163 | using Raven.Client.Documents; 164 | 165 | namespace OrdersExplorer 166 | { 167 | public static class DocumentStoreHolder 168 | { 169 | private static readonly Lazy LazyStore = 170 | new Lazy(() => 171 | { 172 | var store = new DocumentStore 173 | { 174 | Urls = new[] { "http://localhost:8080" }, 175 | Database = "Northwind" 176 | }; 177 | 178 | return store.Initialize(); 179 | }); 180 | 181 | public static IDocumentStore Store => 182 | LazyStore.Value; 183 | } 184 | } 185 | ```` 186 | 187 | ### Step 3: Add Northwind model classes to your project 188 | As you learned in lesson 2, you can work only with dynamic objects. It is a good idea to have compiler/IDE support in your projects. 189 | 190 | Add a new class in your project named NorthwindModels. Then replace the file content with [this](NorthwindModels.cs). 191 | 192 | ### Step 4: Request an order number 193 | 194 | Back to `Program.cs`, let's create a minimal user interface which requests order numbers. 195 | 196 | ````csharp 197 | using static System.Console; 198 | using Raven.Client.Documents; 199 | using NorthwindModels; 200 | 201 | namespace OrdersExplorer 202 | { 203 | class Program 204 | { 205 | static void Main(string[] args) 206 | { 207 | while (true) 208 | { 209 | WriteLine("Please, enter an order # (0 to exit): "); 210 | 211 | int orderNumber; 212 | if (!int.TryParse(ReadLine(), out orderNumber)) 213 | { 214 | WriteLine("Order # is invalid."); 215 | continue; 216 | } 217 | 218 | if (orderNumber == 0) break; 219 | 220 | PrintOrder(orderNumber); 221 | } 222 | 223 | WriteLine("Goodbye!"); 224 | } 225 | 226 | private static void PrintOrder(int orderNumber) 227 | { 228 | } 229 | } 230 | } 231 | ```` 232 | 233 | ### Step 5: Print order number, company, employee, and line products 234 | 235 | Now it's time to load order data, but only if there is an order with the specified order number. 236 | 237 | ````csharp 238 | private static void PrintOrder(int orderNumber) 239 | { 240 | using (var session = DocumentStoreHolder.Store.OpenSession()) 241 | { 242 | var order = session 243 | .Include(o => o.Company) 244 | .Include(o => o.Employee) 245 | .Include(o => o.Lines.Select(l => l.Product)) 246 | .Load($"orders/{orderNumber}-A"); 247 | 248 | if (order == null) 249 | { 250 | WriteLine($"Order #{orderNumber} not found."); 251 | return; 252 | } 253 | 254 | WriteLine($"Order #{orderNumber}"); 255 | 256 | var c = session.Load(order.Company); 257 | WriteLine($"Company : {c.Id} - {c.Name}"); 258 | 259 | var e = session.Load(order.Employee); 260 | WriteLine($"Employee: {e.Id} - {e.LastName}, {e.FirstName}"); 261 | 262 | foreach (var orderLine in order.Lines) 263 | { 264 | var p = session.Load(orderLine.Product); 265 | WriteLine($" - {orderLine.ProductName}," + 266 | $" {orderLine.Quantity} x {p.QuantityPerUnit}"); 267 | } 268 | } 269 | } 270 | ```` 271 | 272 | ## Great Job! 273 | 274 | **Let's move on to [Lesson 6](../lesson6/README.md) and learn about querying, now in C#.** 275 | -------------------------------------------------------------------------------- /src/unit2/lesson4/README.md: -------------------------------------------------------------------------------- 1 | # Unit 2, Lesson 4 - Mapping, Filtering, and Reducing 2 | 3 | In the [previous lesson](../lesson3/README.md), you learned how to create 4 | multi-map indexes. That was amazing, right? 5 | 6 | In this lesson you will learn how to create `Map-Reduce indexes`. 7 | 8 | ## What is Map-Reduce? 9 | 10 | In essence, it is a way to take a big task and divide it into discrete 11 | tasks that can be done in parallel. 12 | 13 | A Map-Reduce process is composed of a `Map` function that projects data from 14 | documents into a common output (expected) and a `Reduce` function that performs 15 | a summary operation. 16 | 17 | I strongly recommend you to read this [blog post](https://ayende.com/blog/179938/ravendb-4-0-unsung-heroes-map-reduce). 18 | 19 | Still confused? Let's write some code and make it clearer. 20 | 21 | ## Exercise: Your First Map-Reduce Index 22 | 23 | The best way to learn about Map-Reduce indexes is to write some code. 24 | 25 | In this first exercise, let's perform a very simple task. Let's just 26 | count the number of products for each category. 27 | 28 | Let's do it using the C# API. 29 | 30 | ### Step 1: Create a new project and install the latest `RavenDB.Client` package 31 | 32 | Start Visual Studio and create a new `Console Application Project` named 33 | `MapReduceIndexes`. Then, in the `Package Manager Console`, issue the following 34 | command: 35 | 36 | ```powershell 37 | Install-Package RavenDB.Client -Version 5.4.5 38 | ``` 39 | 40 | This will install the RavenDB.Client binaries, which you will need in order 41 | to compile your code. 42 | 43 | Then you will need to add the `using` name space at the top of `Program.cs`: 44 | 45 | ````csharp 46 | using Raven.Client.Documents; 47 | ```` 48 | 49 | ### Step 2: Write the model classes 50 | 51 | You don't need to write "complete" model classes when you are only reading 52 | from the database. 53 | 54 | ````csharp 55 | public class Category 56 | { 57 | public string Name { get; set; } 58 | } 59 | 60 | public class Product 61 | { 62 | public string Category { get; set; } 63 | } 64 | ```` 65 | 66 | Everything we need is here. In the `Product` documents, the category 67 | is specified by the Id in the `Category` property. 68 | 69 | ### Step 3: Write the `Map-Reduce index definition` 70 | 71 | One way to create a Map-Reduce index definition is inheriting from `AbstractIndexCreationTask`. 72 | 73 | In the [previous lesson](../lesson3/README.md), you learned how to create multi-map indexes. 74 | It's important you know that you can combine the power of multi-map with map-reduce by 75 | providing multiple map functions. 76 | 77 | ````csharp 78 | public class Products_ByCategory : 79 | AbstractIndexCreationTask 80 | { 81 | public class Result 82 | { 83 | public string Category { get; set; } 84 | public int Count { get; set; } 85 | } 86 | 87 | public Products_ByCategory() 88 | { 89 | Map = products => 90 | from product in products 91 | select new 92 | { 93 | Category = product.Category, 94 | Count = 1 95 | }; 96 | 97 | Reduce = results => 98 | from result in results 99 | group result by result.Category into g 100 | select new 101 | { 102 | Category = g.Key, 103 | Count = g.Sum(x => x.Count) 104 | }; 105 | } 106 | } 107 | ```` 108 | 109 | There are some points to note here. In 4.0 we do run map and reduce sequentially in the same transaction. 110 | It works as follows: the map phase produces the map entries which are stored into reduce trees (b+trees). Next, they are processed by the reduce worker under the same transaction. 111 | 112 | The output from the `Map` and `Reduce` steps needs to be the same. This 113 | allows the engine to perform multiple reduce stages. 114 | 115 | ### Step 4: Initialize the `DocumentStore` and register the index on the server 116 | 117 | Let's do it using our good friend pattern `DocumentStoreHolder`. 118 | 119 | ````csharp 120 | using System; 121 | using Raven.Client; 122 | using Raven.Client.Documents; 123 | using Raven.Client.Documents.Indexes; 124 | 125 | namespace MapReduceIndexes 126 | { 127 | public static class DocumentStoreHolder 128 | { 129 | private static readonly Lazy LazyStore = 130 | new Lazy(() => 131 | { 132 | var store = new DocumentStore 133 | { 134 | Urls = new[] { "http://localhost:8080" }, 135 | Database = "Northwind" 136 | }; 137 | 138 | store.Initialize(); 139 | 140 | var asm = Assembly.GetExecutingAssembly(); 141 | IndexCreation.CreateIndexes(asm, store); 142 | 143 | // Try to retrieve a record of this database 144 | var databaseRecord = store.Maintenance.Server.Send(new GetDatabaseRecordOperation(store.Database)); 145 | 146 | if (databaseRecord != null) 147 | return store; 148 | 149 | var createDatabaseOperation = 150 | new CreateDatabaseOperation(new DatabaseRecord(store.Database)); 151 | 152 | store.Maintenance.Server.Send(createDatabaseOperation); 153 | 154 | return store; 155 | }); 156 | 157 | public static IDocumentStore Store => 158 | LazyStore.Value; 159 | } 160 | } 161 | ```` 162 | 163 | We are asking the client API to find all indexes classes automatically and send them altogether to the server. 164 | You can do that using the `IndexCreation.CreateIndexes` method. 165 | 166 | ### Step 5: Consuming the index 167 | 168 | Now we are ready to perform some queries. 169 | 170 | ````csharp 171 | class Program 172 | { 173 | static void Main(string[] args) 174 | { 175 | using (var session = DocumentStoreHolder.Store.OpenSession()) 176 | { 177 | var results = session 178 | .Query() 179 | .Include(x => x.Category) 180 | .ToList(); 181 | 182 | foreach (var result in results) 183 | { 184 | var category = session.Load(result.Category); 185 | Console.WriteLine($"{category.Name} has {result.Count} items."); 186 | } 187 | } 188 | } 189 | } 190 | ```` 191 | 192 | This will list all the categories of the products. Remember that the `Include` function 193 | ensures that all data is returned from the server in a single response. 194 | 195 | If you are trying to figure out how to do this query using RQL, 196 | here it is: 197 | 198 | ```sql 199 | from index 'Products/ByCategory' 200 | include Category 201 | ``` 202 | 203 | ## Exercise: Employee of the month 204 | 205 | In Northwind, we have employees and orders. In this exercise you will 206 | create an index that will select the "Employee of the Month", which 207 | will be the employee with the most sales in a particular month. 208 | 209 | This exercise picks up right where the previous one left off. 210 | 211 | ### Step 1: Write the model classes 212 | 213 | Let's add two more model classes to your application. 214 | 215 | ````csharp 216 | public class Order { 217 | public string Employee { get; } 218 | public DateTime OrderedAt { get; } 219 | } 220 | 221 | public class Employee 222 | { 223 | public string FirstName { get; set; } 224 | public string LastName { get; set; } 225 | } 226 | ```` 227 | 228 | ### Step 2: Write the `Map-Reduce index definition` 229 | 230 | ````csharp 231 | public class Employees_SalesPerMonth : 232 | AbstractIndexCreationTask 233 | { 234 | public class Result 235 | { 236 | public string Employee { get; set; } 237 | public string Month { get; set; } 238 | public int TotalSales { get; set; } 239 | } 240 | 241 | public Employees_SalesPerMonth() 242 | { 243 | Map = orders => 244 | from order in orders 245 | select new 246 | { 247 | order.Employee, 248 | Month = order.OrderedAt.ToString("yyyy-MM"), 249 | TotalSales = 1 250 | }; 251 | 252 | Reduce = results => 253 | from result in results 254 | group result by new 255 | { 256 | result.Employee, 257 | result.Month 258 | } 259 | into g 260 | select new 261 | { 262 | g.Key.Employee, 263 | g.Key.Month, 264 | TotalSales = g.Sum(x => x.TotalSales) 265 | }; 266 | } 267 | } 268 | ```` 269 | The difference here is that you are grouping by two fields. 270 | 271 | ### Step 3: Consuming the index 272 | 273 | Now we are ready to perform some queries. 274 | 275 | ````csharp 276 | class Program 277 | { 278 | static void Main(string[] args) 279 | { 280 | using (var session = DocumentStoreHolder.Store.OpenSession()) 281 | { 282 | var query = session 283 | .Query() 284 | .Include(x => x.Employee); 285 | 286 | var results = ( 287 | from result in query 288 | where result.Month == "1998-03" 289 | orderby result.TotalSales descending 290 | select result 291 | ).ToList(); 292 | 293 | foreach (var result in results) 294 | { 295 | var employee = session.Load(result.Employee); 296 | Console.WriteLine( 297 | $"{employee.FirstName} {employee.LastName}" 298 | + $" made {result.TotalSales} sales."); 299 | } 300 | } 301 | } 302 | } 303 | ```` 304 | 305 | Before you go, I recommend you to check 306 | 307 | ## The Map-Reduce Visualizer 308 | 309 | If you want to understand better what is going on when using Map/Reduce, you 310 | can use the `Map-Reduce Visualizer` tool. It is available in the `Indexes` section. 311 | 312 | ![creating new index](media/a92uh2jlj43rj3ndj3ndj2jd2.png) 313 | 314 | ## Great Job! 315 | 316 | **Let's move onto [Lesson 5](../lesson5/README.md).** 317 | -------------------------------------------------------------------------------- /src/unit2/lesson3/README.md: -------------------------------------------------------------------------------- 1 | # Unit 2, Lesson 3 - Multi-Map Indexes 2 | 3 | In the [previous lesson](../lesson2/README.md), you learned how to create 4 | indexes with a single map function, and it is going to operate on a 5 | single collection. 6 | 7 | In this lesson you will learn how to create `multi-map indexes`. 8 | 9 | ## Why Create a `Multi-Map` Index? 10 | 11 | Using the Northwind database, assume you need to search for a particular 12 | person. It could be an employee, a company's contact, or a supplier's 13 | contact. How could you do that? Multiple queries? Using RavenDB you can 14 | do that using a single query with an index that maps multiple collections. 15 | 16 | ## Exercise: Creating your first multi-map index using the C# API 17 | 18 | As you learned in the [previous lesson](../lesson2/README.md), you can create indexes using the 19 | `RavenDB Management Studio` or using the C# API. 20 | 21 | In this exercise you will learn how to create a multi-map index using the C# API and you 22 | will use the `Northwind` database. You will write a program that allows the user to 23 | search people from multiple collections (employees, companies and suppliers). 24 | 25 | ### Step 1: Create a new project and install the latest `RavenDB.Client` package 26 | 27 | Start Visual Studio and create a new `Console Application Project` named 28 | `MultimapIndexes`. Then, in the `Package Manager Console`, issue the following 29 | command: 30 | 31 | ```powershell 32 | Install-Package RavenDB.Client -Version 5.4.5 33 | ``` 34 | 35 | This will install RavenDB.Client binaries which you will need in order 36 | to compile your code. 37 | 38 | Then you will need to add the `using` name space at the top of the `Program.cs`: 39 | 40 | ````csharp 41 | using Raven.Client.Documents; 42 | ```` 43 | 44 | ### Step 2: Write the model classes 45 | 46 | As you learned, you don't need to write "complete" model classes when you are only reading 47 | from the database. For this lesson, you will just need the names and IDs. 48 | 49 | ````csharp 50 | public class Employee 51 | { 52 | public string Id { get; set; } 53 | public string FirstName { get; set; } 54 | public string LastName { get; set; } 55 | } 56 | 57 | public class Contact 58 | { 59 | public string Name { get; set; } 60 | } 61 | 62 | public class Company 63 | { 64 | public string Id { get; set; } 65 | public Contact Contact { get; set; } 66 | } 67 | 68 | public class Supplier 69 | { 70 | public string Id { get; set; } 71 | public Contact Contact { get; set; } 72 | } 73 | ```` 74 | 75 | ### Step 3: Write the `Multi-map index definition` 76 | 77 | Every time you create a multi-map index, the index definition class inherits from `AbstractMultiMapIndexCreationTask`. 78 | 79 | ````csharp 80 | using System.Linq; 81 | using Raven.Client.Documents.Indexes; 82 | 83 | namespace MultimapIndexes 84 | { 85 | public class People_Search : 86 | AbstractMultiMapIndexCreationTask 87 | { 88 | public class Result 89 | { 90 | public string SourceId { get; set; } 91 | public string Name { get; set; } 92 | public string Type { get; set; } 93 | } 94 | 95 | public People_Search() 96 | { 97 | AddMap(companies => 98 | from company in companies 99 | select new Result 100 | { 101 | SourceId = company.Id, 102 | Name = company.Contact.Name, 103 | Type = "Company's contact" 104 | } 105 | ); 106 | 107 | AddMap(suppliers => 108 | from supplier in suppliers 109 | select new Result 110 | { 111 | SourceId = supplier.Id, 112 | Name = supplier.Contact.Name, 113 | Type = "Supplier's contact" 114 | } 115 | ); 116 | 117 | AddMap(employees => 118 | from employee in employees 119 | select new Result 120 | { 121 | SourceId = employee.Id, 122 | Name = $"{employee.FirstName} {employee.LastName}", 123 | Type = "Employee" 124 | } 125 | ); 126 | 127 | Index(entry => entry.Name, FieldIndexing.Search); 128 | 129 | Store(entry => entry.SourceId, FieldStorage.Yes); 130 | Store(entry => entry.Name, FieldStorage.Yes); 131 | Store(entry => entry.Type, FieldStorage.Yes); 132 | } 133 | } 134 | } 135 | ```` 136 | 137 | You can define as many `map functions` as you need. Each map function is defined using 138 | the `AddMap` method and has to produce the same output type. The "source" collection is specified 139 | by the generic parameter type you specify in the `AddMap` function. 140 | 141 | The `Index` method here was used to mark the `Name` property as `Search` 142 | which enables full text search with this field. 143 | 144 | The `Store` method was used to enable projections and to store that defined properties along with the Index. Thereby, the return of searched data comes from the Index, instead of having to load the document and get the fields from it. In most cases, this isn't an interesting optimization. RavenDB is already heavily optimized toward loading documents. Use this when you are creating new values in the indexing function and want to project them, not merely skip the (pretty cheap) loading of the document. 145 | 146 | ### Step 4: Initialize the `DocumentStore` and register the index on the server 147 | 148 | Again, let's do it using our good friend pattern `DocumentStoreHolder`. 149 | 150 | ````csharp 151 | using System; 152 | using Raven.Client; 153 | using Raven.Client.Documents; 154 | 155 | namespace MultimapIndexes 156 | { 157 | public static class DocumentStoreHolder 158 | { 159 | private static readonly Lazy LazyStore = 160 | new Lazy(() => 161 | { 162 | var store = new DocumentStore 163 | { 164 | Urls = new[] { "http://localhost:8080" }, 165 | Database = "Northwind" 166 | }; 167 | 168 | store.Initialize(); 169 | 170 | var asm = Assembly.GetExecutingAssembly(); 171 | IndexCreation.CreateIndexes(asm, store); 172 | 173 | // Try to retrieve a record of this database 174 | var databaseRecord = store.Maintenance.Server.Send(new GetDatabaseRecordOperation(store.Database)); 175 | 176 | if (databaseRecord != null) 177 | return store; 178 | 179 | var createDatabaseOperation = 180 | new CreateDatabaseOperation(new DatabaseRecord(store.Database)); 181 | 182 | store.Maintenance.Server.Send(createDatabaseOperation); 183 | 184 | return store; 185 | }); 186 | 187 | public static IDocumentStore Store => 188 | LazyStore.Value; 189 | } 190 | } 191 | ```` 192 | 193 | In the [previous lesson](../lesson2/README.md), you learned how to register 194 | each index definition individually. It's a lot easier to ask the 195 | client API to find all indexes classes automatically and send them all together to the server. 196 | You can do that using the `IndexCreation.CreateIndexes` method. 197 | 198 | The `DocumentStoreHolder` is the perfect place for registering indexes on the 199 | server. 200 | 201 | ### Step 5: Write the search utility function 202 | 203 | Here we have a very special piece of code: 204 | 205 | ````csharp 206 | public static IEnumerable Search( 207 | IDocumentSession session, 208 | string searchTerms 209 | ) 210 | { 211 | var results = session.Query() 212 | .Search( 213 | r => r.Name, 214 | searchTerms 215 | ) 216 | .ProjectInto() 217 | .ToList(); 218 | 219 | return results; 220 | } 221 | ```` 222 | 223 | The `Search` method allows us to perform advanced querying using all the power 224 | of the Lucene engine including wildcards. 225 | 226 | > RavenDB allows you to search by using such queries, but you have to be aware that leading wildcards drastically slow down searches. Consider if you really need to find substrings. In most cases, looking for whole words is enough. There are also other alternatives for searching without expensive wildcard matches, like indexing a reversed version of the text field or creating a custom analyzer. 227 | 228 | Here we are only accepting a list of terms and projecting 229 | the results directly from index stored values into a common result format. 230 | 231 | ### Step 6: Use the `Search` utility function 232 | 233 | ````csharp 234 | static void Main(string[] args) 235 | { 236 | Console.Title = "Multi-map sample"; 237 | using (var session = DocumentStoreHolder.Store.OpenSession()) 238 | { 239 | while (true) 240 | { 241 | Console.Write("\nSearch terms: "); 242 | var searchTerms = Console.ReadLine(); 243 | 244 | foreach (var result in Search(session, searchTerms)) 245 | { 246 | Console.WriteLine($"{result.SourceId}\t{result.Type}\t{result.Name}"); 247 | } 248 | } 249 | } 250 | } 251 | ```` 252 | 253 | This code asks the user to provide some search terms. Then it displays the 254 | search results. 255 | 256 | Not that even if we query against aggregated data the map-reduce index has 257 | done the pre-calculations in advance so we are not doing any full table scans, 258 | just a *O(logN)* operation 259 | 260 | ![creating new index](media/unit2-multi-map-output.png) 261 | 262 | ## Some Words About Projections 263 | 264 | In the previous exercise you created an inner class named `Result`. Right? This 265 | class was used to provide projections. Projections are a way to collect several 266 | fields from a document, instead of working with the whole document. 267 | 268 | In the index definition, we instructed RavenDB to store the fields in the index. So 269 | when querying, the documents will not be loaded from storage and all the data will come 270 | directly from the index. 271 | 272 | ## Great Job! 273 | 274 | Before you proceed, I strongly recommend that you try creating a multi-map index 275 | using the `RavenDB Management Studio`. You will see how easy it is. 276 | 277 | **Let's move onto [Lesson 4](../lesson4/README.md).** 278 | -------------------------------------------------------------------------------- /src/unit2/lesson2/README.md: -------------------------------------------------------------------------------- 1 | # Unit 2, Lesson 2 - Creating an Index and Querying it 2 | 3 | As you learned in the [previous lesson](../lesson1/README.md), RavenDB 4 | automatically creates and manages indexes for most common queries. But 5 | for some cases you will need to create indexes yourself. 6 | 7 | In this lesson you will learn two ways to create a basic index: Using the RavenDB Studio and 8 | the C# API. This is fundamental to get on to more advanced concepts. 9 | 10 | ## But .. What is an Index? 11 | 12 | An index is a data structure which the RavenDB engine uses to perform all queries. 13 | Thanks to this data structure, RavenDB can quickly locate data without having 14 | to search every document in the database. 15 | 16 | Internally, RavenDB uses Lucene as the index store, [Lucene.Net](https://lucenenet.apache.org/) to be more exact - a port of the Lucene Search engine library written in C#. 17 | 18 | ## The `Map` Function 19 | 20 | The first thing you need to realize is that RavenDB databases are schemaless, the engine has no knowledge of the structure of the documents 21 | it contains. The basic element of an index definition is the `Map` function which 22 | is responsible for converting a document in JSON format into an 23 | index entry. 24 | 25 | The `Map` function extracts pieces of data you will be willing to search on 26 | from the documents. 27 | 28 | You will use LINQ syntax to express how the data should be extracted (or mapped). 29 | 30 | Just as an example, this is the Map function automatically created by RavenDB in the 31 | [previous lesson](../lesson1/README.md). 32 | 33 | ````csharp 34 | from doc in docs.Orders 35 | select new { 36 | OrderedAt = doc.OrderedAt, 37 | Company = doc.Company 38 | } 39 | ```` 40 | 41 | This tells RavenDB to look only at documents of the `Orders` collection and 42 | to map the properties `OrderedAt` and `Company`. The output, as you can deduce, is a set of anonymous objects. 43 | 44 | ## Exercise: Creating your first index using the `RavenDB Management Studio` 45 | 46 | In this exercise you will learn how to create a basic index using the `RavenDB Management Studio`. 47 | 48 | ### Step 1: Access Northwind database using the Management Studio 49 | 50 | Start RavenDB console (if you haven't do so yet), and using the web browser, access the 51 | `RavenDB Management Studio` at the address `http://localhost:8080` (which is the 52 | default address. Change it if you need to). Then, open the `Northwind database` which you 53 | created in the previous unit ([Lesson 1](../../Unit-1/lesson1/README.md)). 54 | 55 | ### Step 2: Start the creation of a new Index 56 | Go to the `Indexes` section, then `List of Indexes`, click on the `New Index`. 57 | 58 | ### Step 3: Specify the index name 59 | There is no fixed naming rules with RavenDB. But, I strongly recommend 60 | you follow some convention. For this index, let's use `Employees/ByFirstAndLastName` (`collection name`/By 61 | `selected fields` Of `filtering criteria`) 62 | 63 | ### Step 4: Specify the `Map` function 64 | Enter the following Map function: 65 | 66 | ````csharp 67 | from doc in docs.Employees 68 | select new 69 | { 70 | FirstName = doc.FirstName, 71 | LastName = doc.LastName 72 | } 73 | ```` 74 | 75 | > PRO TIP: Use the `Format` to get a nice formatting of your code. You will receive an 76 | error message if there is something wrong. 77 | 78 | In the expression, `docs` represents the entire collection of documents 79 | stored in the database. `docs.Employees` represents all documents from the `Employees` collection. 80 | 81 | ![creating new index](media/g82uh2j3n4h3ndj3ndj2jd2.png) 82 | 83 | ### Step 5: Save the index 84 | Just click the save button. This will start a background indexing process 85 | that will feed the index with all the documents. 86 | 87 | That's it. You just created your first index. 88 | 89 | If you click on the `Copy C#` button you will see this code 90 | 91 | ```csharp 92 | 93 | 94 | using System; 95 | using System.Collections; 96 | using System.Collections.Generic; 97 | using System.Text.RegularExpressions; 98 | using System.Globalization; 99 | using System.Linq; 100 | using Raven.Client.Documents.Indexes; 101 | 102 | public class Index_Employees_ByFirstAndLastName :AbstractIndexCreationTask 103 | { 104 | public override string IndexName => "Employees/ByFirstAndLastName"; 105 | 106 | public override IndexDefinition CreateIndexDefinition() 107 | { 108 | return new IndexDefinition 109 | { 110 | Maps = 111 | { 112 | @"from doc in docs.Employees 113 | select new 114 | { 115 | FirstName = doc.FirstName, 116 | LastName = doc.LastName 117 | }" 118 | } 119 | }; 120 | } 121 | } 122 | ``` 123 | This looks nice. 124 | 125 | ## Exercise: Creating your first index using the C# API 126 | 127 | So far, we worked with indexes inside the studio. But you probably want 128 | to manage them within your codebase. That is why RavenDB allows you to define indexes 129 | using code. 130 | 131 | In this exercise you will learn how to create a basic index using the C# API and the `Northwind` database. 132 | 133 | ### Step 1: Create a new project and install the latest `RavenDB.Client` package 134 | 135 | Start Visual Studio and create a new `Console Application Project` named 136 | `CreatingARavenIndexWithCSharp`. Then, in the `Package Manager Console`, issue the following 137 | command: 138 | 139 | ```powershell 140 | Install-Package RavenDB.Client -Version 5.4.5 141 | ``` 142 | 143 | This will install RavenDB.Client binaries, which you will need in order 144 | to compile your code. 145 | 146 | Then you will need to add the `using` name space at the top of `Program.cs`: 147 | 148 | ````csharp 149 | using Raven.Client.Documents; 150 | ```` 151 | 152 | ### Step 2: Initialize the `DocumentStore` 153 | 154 | Again, let's do it using our good friend pattern `DocumentStoreHolder`. 155 | 156 | ````csharp 157 | public static class DocumentStoreHolder 158 | { 159 | private static readonly Lazy LazyStore = 160 | new Lazy(() => 161 | { 162 | var store = new DocumentStore 163 | { 164 | Urls = new[] { "http://localhost:8080" }, 165 | Database = "Northwind" 166 | }; 167 | 168 | return store.Initialize(); 169 | }); 170 | 171 | public static IDocumentStore Store => 172 | LazyStore.Value; 173 | } 174 | 175 | ```` 176 | 177 | ### Step 3: Write the model classes 178 | 179 | Let's define some very basic class models. Edit any Employee document in the RavenDB Studio and 180 | use the `Copy C#`. 181 | 182 | ![generating model](media/g82uh2jlj43rj3ndj3ndj2jd2.png) 183 | 184 | ````csharp 185 | public class Location 186 | { 187 | public float Latitude { get; set; } 188 | public float Longitude { get; set; } 189 | } 190 | 191 | public class Address 192 | { 193 | public string City { get; set; } 194 | public string Country { get; set; } 195 | public string Line1 { get; set; } 196 | public object Line2 { get; set; } 197 | public Location Location { get; set; } 198 | public string PostalCode { get; set; } 199 | public object Region { get; set; } 200 | } 201 | 202 | public class Employee 203 | { 204 | public Address Address { get; set; } 205 | public DateTimeOffset Birthday { get; set; } 206 | public int Extension { get; set; } 207 | public string FirstName { get; set; } 208 | public DateTimeOffset HiredAt { get; set; } 209 | public string HomePhone { get; set; } 210 | public string LastName { get; set; } 211 | public List Notes { get; set; } 212 | public string ReportsTo { get; set; } 213 | public List Territories { get; set; } 214 | public string Title { get; set; } 215 | } 216 | ```` 217 | 218 | By default, the `Copy C#` does not include the `Id` property. Here we will not use it. If you want this 219 | property, add it. 220 | 221 | ### Step 4: Write the `index definition` class 222 | 223 | ````csharp 224 | public class Employees_ByFirstAndLastName : AbstractIndexCreationTask 225 | { 226 | public Employees_ByFirstAndLastName() 227 | { 228 | Map = (employees) => 229 | from employee in employees 230 | select new 231 | { 232 | FirstName = employee.FirstName, 233 | LastName = employee.LastName 234 | }; 235 | } 236 | } 237 | ```` 238 | 239 | The class name `Employees_ByFirstAndLastName`, by convention, will generate 240 | an index named `Employees/ByFirstAndLastName`. 241 | 242 | The class inherits from `AbstractIndexCreationTask`, which indicates 243 | this class as an index that operates on the `Employees` collection. 244 | 245 | ### Step 5: Sending the indexes to the server 246 | 247 | In the previous step you just wrote the index definition. In order to create 248 | the index you need to execute it: 249 | 250 | ````csharp 251 | class Program 252 | { 253 | static void Main() 254 | { 255 | var store = DocumentStoreHolder.Store; 256 | new Employees_ByFirstAndLastName().Execute(store); 257 | } 258 | } 259 | ```` 260 | 261 | If you create or modify an index, when you execute it RavenDB will create/modify 262 | the index on the server. If the server side index definition matches the index 263 | definition in the client, the operation has no effect. 264 | 265 | > If you want to learn other ways to deploy indexes, you can read more about this topic in the [official documentation](http://ravendb.net/docs/article-page/latest/csharp/indexes/creating-and-deploying). 266 | 267 | In one of next lessons you will learn how to send the indexes definitions 268 | during the `Document Store` initialization. 269 | 270 | 271 | ### Step 6: Run! 272 | 273 | Delete all indexes as you learned in the [previous lesson](../lesson2/README.md). Then, 274 | go ahead and execute your program. 275 | 276 | You will see a new index created for you. 277 | 278 | ## Querying 279 | 280 | RavenDB is smart enough to select the right index for you if it was created by the server. 281 | But if you want to use a static index, you can specify what index should be used by using an additional type parameter 282 | of the `Query` method. 283 | 284 | ````csharp 285 | using (var session = DocumentStoreHolder.Store.OpenSession()) 286 | { 287 | var results = session 288 | .Query() 289 | .Where(x => x.FirstName == "Robert") 290 | .ToList(); 291 | 292 | foreach (var employee in results) 293 | { 294 | Console.WriteLine($"{employee.LastName}, {employee.FirstName}"); 295 | } 296 | } 297 | ```` 298 | 299 | ## Great Job! 300 | 301 | Awesome! 302 | 303 | **Let's move onto [Lesson 3](../lesson3/README.md).** 304 | -------------------------------------------------------------------------------- /src/unit1/lesson7/README.md: -------------------------------------------------------------------------------- 1 | # Unit 1, Lesson 7 - Storing, Modifying, and Deleting Documents 2 | 3 | In this lesson, you will learn how to store, modify, and delete documents. 4 | 5 | ## Quick Start 6 | Storing, modifying, and deleting documents is extremely easy with RavenDB. 7 | 8 | Let me provide you a quick start demo. 9 | 10 | ````csharp 11 | // storing a new document 12 | string categoryId; 13 | using (var session = DocumentStoreHolder.Store.OpenSession()) 14 | { 15 | var newCategory = new Category 16 | { 17 | Name = "My New Category", 18 | Description = "Description of the new category" 19 | }; 20 | 21 | session.Store(newCategory); 22 | categoryId = newCategory.Id; 23 | session.SaveChanges(); 24 | } 25 | 26 | // loading and modifying 27 | using (var session = DocumentStoreHolder.Store.OpenSession()) 28 | { 29 | var storedCategory = session 30 | .Load(categoryId); 31 | 32 | storedCategory.Name = "abcd"; 33 | 34 | session.SaveChanges(); 35 | } 36 | 37 | // deleting 38 | using (var session = DocumentStoreHolder.Store.OpenSession()) 39 | { 40 | session.Delete(categoryId); 41 | session.SaveChanges(); 42 | } 43 | ```` 44 | 45 | Any .NET object can be stored by RavenDB. It only needs to be serializable to 46 | JSON. 47 | 48 | The `Store` method is responsible to register the "storing" intention in the session. 49 | You can access the document right after the `Store` call was made, even though the document 50 | was not saved to the database yet. The `SaveChanges` method applies the registered 51 | actions in the session to the database. 52 | 53 | When you change the state of an entity, the session is smart enough to detect it and 54 | update the matching document on the server side. The session keeps track of all the 55 | entities you have loaded (with `Load` or `Query` methods), and when you call `SaveChanges`, 56 | all changes to those entities are sent to the database in a *single remote call*. 57 | 58 | The `Delete` method, which we have used in the last part of the code will delete the 59 | matching document on the server side. You can provide the document ID or an 60 | entity instance. 61 | 62 | **All the changes are applied on the server side only after you call the `SaveChanges` method.** 63 | 64 | > The session implements the Unit of Work pattern. Learn more reading the [official documentation](http://ravendb.net/docs/article-page/latest/csharp/client-api/session/what-is-a-session-and-how-does-it-work). 65 | 66 | ## Modifying Documents with the `Store` Method 67 | 68 | Beyond saving a new entity, the `Store` method is also used to associate entities 69 | of existing documents within the session. This is common in web applications. You 70 | have one endpoint that sends the entity to the user, which modifies that entity and 71 | then sends it back to your web application. You have a live entity instance, but 72 | it is not loaded by the session or tracked by it. At that point, you have to 73 | call the `Store` method on that entity, and because it doesn't have a null document 74 | ID, it will be treated as an existing document and overwrite the previous version 75 | on the database side. 76 | 77 | ````csharp 78 | public class CategoryRepository 79 | { 80 | // ... 81 | public void Update(Category category) 82 | { 83 | var (session = DocumentStoreHolder.Store.OpenSession()) 84 | { 85 | session.Store(category); 86 | session.SaveChanges(); 87 | } 88 | } 89 | // ... 90 | } 91 | ```` 92 | 93 | `SaveChanges` should be called only once per session. 94 | 95 | ## Exercise: Creating a Basic Contacts CRUD 96 | 97 | In this exercise we will create a `Console Application` to manage a basic 98 | list of contacts. 99 | 100 | ### Step 1: Create a new project and install the latest `RavenDB.Client` package 101 | 102 | Start Visual Studio and create a new `Console Application Project` named 103 | Contacts Manager. Then, in the `Package Manager Console`, issue the following 104 | command: 105 | 106 | ```powershell 107 | Install-Package RavenDB.Client -Version 5.4.5 108 | ``` 109 | 110 | This will install RavenDB.Client binaries, which you will need in order 111 | to compile your code. 112 | 113 | Then you will need to add the `using` namespace at the top of `Program.cs`: 114 | 115 | ````csharp 116 | using Raven.Client; 117 | using Raven.Client.Documents.Linq; 118 | ```` 119 | 120 | ### Step 2: Initialize the `DocumentStore` 121 | 122 | Let's do it using our good friend pattern `DocumentStoreHolder`. You learned about it in 123 | [Lesson 4](../lesson4/README.md). 124 | 125 | Note that, if the database specified in the `Database` property does not exist, the code below will create automatically one for you. The next time that you'll run your application again, the `ContactsManager` database won't re-created anymore and your application will able to resuse this one. 126 | 127 | ````csharp 128 | using System; 129 | using Raven.Client.Documents; 130 | using Raven.Client.ServerWide; 131 | using Raven.Client.ServerWide.Operations; 132 | 133 | namespace ContactsManager 134 | { 135 | public static class DocumentStoreHolder 136 | { 137 | private static readonly Lazy LazyStore = 138 | new Lazy(() => 139 | { 140 | var store = new DocumentStore 141 | { 142 | Urls = new[] { "http://localhost:8080" }, 143 | Database = "ContactsManager" 144 | }; 145 | 146 | store.Initialize(); 147 | 148 | // Try to retrieve a record of this database 149 | var databaseRecord = store.Maintenance.Server.Send(new GetDatabaseRecordOperation(store.Database)); 150 | 151 | if (databaseRecord != null) 152 | return store; 153 | 154 | var createDatabaseOperation = 155 | new CreateDatabaseOperation(new DatabaseRecord(store.Database)); 156 | 157 | store.Maintenance.Server.Send(createDatabaseOperation); 158 | 159 | return store; 160 | }); 161 | 162 | public static IDocumentStore Store => 163 | LazyStore.Value; 164 | } 165 | } 166 | ```` 167 | 168 | ### Step 3: Instancing and running 169 | 170 | Now that we have a `DocumentStoreHolder` correctly implemented, let's write 171 | some application code. 172 | 173 | ````csharp 174 | using System; 175 | using System.Linq; 176 | using Raven.Client; 177 | using Raven.Client.Documents; 178 | 179 | namespace ContactsManager 180 | { 181 | class Program 182 | { 183 | static void Main(string[] args) 184 | { 185 | new Program().Run(); 186 | } 187 | 188 | private void Run() 189 | { 190 | } 191 | } 192 | } 193 | ```` 194 | 195 | I don't like the idea of having too many static methods. 196 | 197 | ### Step 4: Create the model class 198 | 199 | In this exercise we will use a very simple model class. 200 | 201 | ````csharp 202 | public class Contact 203 | { 204 | public string Id { get; set; } 205 | public string Name { get; set; } 206 | public string Email { get; set; } 207 | } 208 | ```` 209 | ### Step 5: Implementing a basic options menu 210 | 211 | Let's implement a basic console menu that permits the user to select 212 | which operation should be executed. 213 | 214 | ````csharp 215 | private void Run() 216 | { 217 | while (true) 218 | { 219 | Console.WriteLine("Please, press:"); 220 | Console.WriteLine("C - Create"); 221 | Console.WriteLine("R - Retrieve"); 222 | Console.WriteLine("U - Update"); 223 | Console.WriteLine("D - Delete"); 224 | Console.WriteLine("Q - Query all contacts (limit to 128 items)"); 225 | 226 | var input = Console.ReadKey(); 227 | 228 | Console.WriteLine("\n------------"); 229 | 230 | switch (input.Key) 231 | { 232 | case ConsoleKey.C: 233 | CreateContact(); 234 | break; 235 | case ConsoleKey.R: 236 | RetrieveContact(); 237 | break; 238 | case ConsoleKey.U: 239 | UpdateContact(); 240 | break; 241 | case ConsoleKey.D: 242 | DeleteContact(); 243 | break; 244 | case ConsoleKey.Q: 245 | QueryAllContacts(); 246 | break; 247 | default: 248 | return; 249 | } 250 | 251 | Console.WriteLine("------------"); 252 | } 253 | } 254 | ```` 255 | ### Step 6: Implementing the logic to create a new contact 256 | 257 | ````csharp 258 | private void CreateContact() 259 | { 260 | using (var session = DocumentStoreHolder.Store.OpenSession()) 261 | { 262 | Console.WriteLine("Name: "); 263 | var name = Console.ReadLine(); 264 | 265 | Console.WriteLine("Email: "); 266 | var email = Console.ReadLine(); 267 | 268 | var contact = new Contact 269 | { 270 | Name = name, 271 | Email = email 272 | }; 273 | 274 | session.Store(contact); 275 | 276 | Console.WriteLine($"New Contact ID {contact.Id}"); 277 | 278 | session.SaveChanges(); 279 | } 280 | } 281 | ```` 282 | 283 | ### Step 7: Retrieving information 284 | 285 | ````csharp 286 | private void RetrieveContact() 287 | { 288 | Console.WriteLine("Enter the contact id: "); 289 | var id = Console.ReadLine(); 290 | var contactsReference = $"contacts/{id}-A"; 291 | 292 | using (var session = DocumentStoreHolder.Store.OpenSession()) 293 | { 294 | var contact = session.Load(contactsReference); 295 | 296 | if (contact == null) 297 | { 298 | Console.WriteLine("Contact not found."); 299 | return; 300 | } 301 | 302 | Console.WriteLine($"Name: {contact.Name}"); 303 | Console.WriteLine($"Email: {contact.Email}"); 304 | } 305 | } 306 | ```` 307 | 308 | 309 | ### Step 8: Updating a contact 310 | 311 | ````csharp 312 | private void UpdateContact() 313 | { 314 | Console.WriteLine("Enter the contact id: "); 315 | var id = Console.ReadLine(); 316 | var contactsReference = $"contacts/{id}-A"; 317 | 318 | using (var session = DocumentStoreHolder.Store.OpenSession()) 319 | { 320 | var contact = session.Load(contactsReference); 321 | 322 | if (contact == null) 323 | { 324 | Console.WriteLine("Contact not found."); 325 | return; 326 | } 327 | 328 | Console.WriteLine($"Actual name: {contact.Name}"); 329 | Console.WriteLine("New name: "); 330 | contact.Name = Console.ReadLine(); 331 | 332 | Console.WriteLine($"Actual email: {contact.Email}"); 333 | Console.WriteLine("New email address: "); 334 | contact.Email = Console.ReadLine(); 335 | 336 | session.SaveChanges(); 337 | } 338 | } 339 | ```` 340 | 341 | ### Step 9: Deleting a contact 342 | 343 | ````csharp 344 | private void DeleteContact() 345 | { 346 | Console.WriteLine("Enter the contact id: "); 347 | var id = Console.ReadLine(); 348 | var contactsReference = $"contacts/{id}-A"; 349 | 350 | using (var session = DocumentStoreHolder.Store.OpenSession()) 351 | { 352 | var contact = session.Load(contactsReference); 353 | 354 | if (contact == null) 355 | { 356 | Console.WriteLine("Contact not found."); 357 | return; 358 | } 359 | 360 | session.Delete(contact); 361 | session.SaveChanges(); 362 | } 363 | } 364 | ```` 365 | 366 | ### Step 10: List all contacts 367 | 368 | ````csharp 369 | private void QueryAllContacts() 370 | { 371 | using (var session = DocumentStoreHolder.Store.OpenSession()) 372 | { 373 | var contacts = session.Query().ToList(); 374 | 375 | foreach (var contact in contacts) 376 | { 377 | Console.WriteLine($"{contact.Id} - {contact.Name} - {contact.Email}"); 378 | } 379 | 380 | Console.WriteLine($"{contacts.Count} contacts found."); 381 | } 382 | } 383 | ```` 384 | 385 | It is important to say that all contacts will be returned. 386 | 387 | ## Transactions! Transactions! 388 | 389 | In RavenDB all actions performed on documents are fully ACID (Atomicity, 390 | Consistency, Isolation, and Durability). All these constraints are ensured 391 | when you use a session and call the `SaveChanges` method. 392 | 393 | This is a great thing! 394 | 395 | Unlike many other NoSQL databases, RavenDB is an ACID 396 | database. We were the first to become ACID over 10 years ago. 397 | 398 | More information is available on the [official documentation](https://ravendb.net/docs/article-page/4.0/csharp/client-api/faq/transaction-support) 399 | 400 | ## Great Job! 401 | 402 | **Congratulations! You know the basics of RavenDB.** 403 | --------------------------------------------------------------------------------