Guest Rishit Bhatia Posted October 26, 2024 Posted October 26, 2024 This is a guest post by Rishit Bhatia and Luce Carter. Rishit is a Senior Product Manager at MongoDB focusing on the .NET Developer Experience and has been working with C# since many years hands on before moving into Product Management. Luce is a Developer Advocate at MongoDB, Microsoft MVP and lover of code, sunshine and learning. This blog was reviewed by the Microsoft .NET team for EF Core. The EF Core provider for MongoDB went GA in May 2024. We’ve come a long way since we initially released this package in preview six months ago. We wanted to share some interesting features that we’ve been working on which would not have been possible without the support of and collaboration with Microsoft’s .NET Data and Entity Framework team. In this post, we will be using the MongoDB EF Core provider with MongoDB Atlas to showcase the following: Adding a property to an entity and change tracking Leveraging the escape hatch to create an index Performing complex queries Transactions and optimistic concurrency The code related to this blog can be found on Github. The boilerplate code to get started is in the “start” branch. The full code with all the feature highlights mentioned below is in the “main” branch. [HEADING=1]Prerequisites[/HEADING] We will be using a sample dataset — specifically, the movies collection from the sample_mflix database available for MongoDB Atlas in this example. To set up an Atlas cluster with sample data, you can follow the steps in the docs. We’ll create a simple .NET Console App to get started with the MongoDB EF Core provider. For more details on how to do that, you can check the quickstart guide. At this point, you should be connected to Atlas and able to output the movie plot from the movie being read in the quickstart guide. [HEADING=1]Features highlight[/HEADING] [HEADING=2]Adding properties and change tracking[/HEADING] One of the advantages of MongoDB’s document model is that it supports a flexible schema. This, coupled with EF Core’s ability to support a Code First approach, lets you add properties to your entities on the fly. To show this, we are going to add a new nullable boolean property called [iCODE]adapted_from_book[/iCODE] to our model class. This will make our model class as seen below: public class Movie { public ObjectId Id { get; set; } [bsonElement("title")] public string Title { get; set; } [bsonElement("rated")] public string Rated { get; set; } [bsonElement("plot")] public string Plot { get; set; } [bsonElement("adaptedFromBook")] public bool? AdaptedFromBook { get; set; } } Now, we are going to set this newly added property for the movie entity we found and see EF Core’s Change Tracking in action after we save our changes. To do so, we’ll add the following lines of code after printing the movie plot: movie.AdaptedFromBook = false; await db.SaveChangesAsync(); Before we run our program, let’s go to our collection in Atlas and find this movie to make sure that this newly created field [iCODE]adapted_from_book[/iCODE] does not exist in our database. To do so, simply go to your cluster in the Atlas Web UI and select Browse Collections. [ATTACH type=full" alt="Browse Collections button showing in Atlas UI]6010[/ATTACH] Then, choose the movies collection from the sample_mflix database. In the filter tab, we can find our movie using the query below: [iCODE]{title: "Back to the Future"}[/iCODE] This should find our movie and we can confirm that the new field we intend to add is indeed not seen. [ATTACH type=full" alt="Example movie document]6011[/ATTACH] Next, let’s add a breakpoint to the two new lines we just added to make sure that we can track the changes live as we proceed. Select the Start Debugging button to run the app. When the first breakpoint is hit, we can see that the local field value has been assigned. [ATTACH type=full" alt="Contents of local movie field viewed from debugger]6012[/ATTACH] Let’s hit Continue and check the document in the database. We can see that the new field has not yet been added. Let’s step over the save changes call which will end the program. At this point, if we check our document in the database, we’ll notice that the new field has been added as seen below! [ATTACH type=full" alt="Previous document example with new field adapted from book added]6013[/ATTACH] [HEADING=2]Index management[/HEADING] The MongoDB EF Core provider is built on top of the existing .NET/C# driver. One advantage of this architecture is that we can reuse the [iCODE]MongoClient[/iCODE] already created for the [iCODE]DbContext[/iCODE] to leverage other capabilities exposed by MongoDB’s developer data platform. This includes but is not limited to features such as Index Management, Atlas Search, and Vector Search. We’ll see how we can create a new index using the driver in this same application. First, we’ll list the indexes in our collection to see which indexes already exist. MongoDB creates an index on the [iCODE]_id[/iCODE] field by default. We’re going to create a helper function to print the indexes: var moviesCollection = client.GetDatabase("sample_mflix").GetCollection<Movie>("movies"); Console.WriteLine("Before creating a new Index:"); PrintIndexes(); void PrintIndexes() { var indexes = moviesCollection.Indexes.List(); foreach (var index in indexes.ToList()) { Console.WriteLine(index); } } The expected output is as seen below: [iCODE]{ "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_" }[/iCODE] Now, we’ll create a compound index on the title and rated fields in our collection and print the indexes again. var moviesIndex = new CreateIndexModel<Movie>(Builders<Movie>.IndexKeys .Ascending(m => m.Title) .Ascending(x => x.Rated)); await moviesCollection.Indexes.CreateOneAsync(moviesIndex); Console.WriteLine("After creating a new Index:"); PrintIndexes(); We can see that a new index with the name [iCODE]title_1_rated_1[/iCODE] has been created. After creating a new Index: { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_" } { "v" : 2, "key" : { "title" : 1, "rated" : 1 }, "name" : "title_1_rated_1" } [HEADING=2]Querying data[/HEADING] Since EF Core already supports Language Integrated Query (LINQ) Syntax, it becomes easy to write strongly typed queries in C#. Based on the fields available in our model classes, we can try to find some interesting movies from our collection. Let’s say I wanted to find all movies that are rated “PG-13” with their plot containing the word “shark” but I wanted them ordered by their title field. I can do so easily with the following query: var myMovies = await db.Movies .Where(m => m.Rated == "PG-13" && m.Plot.Contains("shark")) .OrderBy(m => m.Title) .ToListAsync(); foreach (var m in myMovies) { Console.WriteLine(m.Title); } We can then print out the queries using the code above and run the program using [iCODE]dotnet run[/iCODE] to see the results. We should be able to see two movie names from the 20K+ movies in our collection printed in the console as seen below. Jaws: The Revenge Shark Night 3D If you would like to see the query that is sent to the server, which in this case is the MQL, then you can enable logging in the [iCODE]Create[/iCODE] function on the DbContext as seen below: public static MflixDbContext Create(IMongoDatabase database) => new(new DbContextOptionsBuilder<MflixDbContext>() .UseMongoDB(database.Client, database.DatabaseNamespace.DatabaseName) .LogTo(Console.WriteLine) .EnableSensitiveDataLogging() .Options); This way we can see the following as a part of our detailed logs when we run the program again: Executed MQL query sample_mflix.movies.aggregate([{ "$match" : { "rated" : "PG-13", "plot" : /shark/s } }, { "$sort" : { "title" : 1 } }]) [HEADING=2]Autotransactions and optimistic concurrency[/HEADING] Yes, you read that right! The MongoDB EF Core provider from its 8.1.0 release supports transactions and optimistic concurrency. What this means is that by default, [iCODE]SaveChanges[/iCODE] and [iCODE]SaveChangesAsync[/iCODE] are transactional. This will empower automatic rollback of operations in production grade workloads in case of any failures and ensure that all operations are fulfilled with optimistic concurrency. If you want to turn off transactions, you can do so during the initialization phase before calling any [iCODE]SaveChanges[/iCODE] operation. [iCODE]db.Database.AutoTransactionBehavior = AutoTransactionBehavior.Never;[/iCODE] The provider supports two methods of optimistic concurrency depending on your requirements which are through a concurrency check or row versions. You can read more about it in the docs. We’ll be using the RowVersion to demonstrate this use case. This leverages the [iCODE]Version[/iCODE] field in our model class which will be updated automatically by the MongoDB EF Provider. To add the version, we add the following to our model class. [Timestamp] public long? Version { get; set; } First, let’s create a new movie entity called [iCODE]myMovie[/iCODE] as seen below and add it to the [iCODE]DbSet[/iCODE], followed by [iCODE]SaveChangesAsync[/iCODE]. Movie myMovie1= new Movie { Title = "The Rise of EF Core 1", Plot = "Entity Framework (EF) Core is a lightweight, extensible, open source and cross-platform version of the popular Entity Framework data access technology.", Rated = "G" }; db.Movies.Add(myMovie1); await db.SaveChangesAsync(); Now, let’s create a new [iCODE]DbContext[/iCODE] similar to the one we created above. We can move the database creation into a variable so we don’t have to define the name of the database again. With this new context, let’s add a sequel for our movie and add it to the DbSet. We’ll also add a third part (yes, it’s a trilogy) but use the same ID as our second movie entity to this new context and then save our changes. var dbContext2 = MflixDbContext.Create(database); dbContext2.Database.AutoTransactionBehavior = AutoTransactionBehavior.Never; var myMovie2 = new Movie { title = "The Rise of EF Core 2" }; dbContext2.Movies.Add(myMovie2); var myMovie3 = new Movie { Id = myMovie2.Id,Title = "The Rise of EF Core 3" }; dbContext2.Movies.Add(myMovie3); await dbContext2.SaveChangesAsync(); With transactions now being supported, the second set of operations for our latter two movie entities should not go through since we are trying to add them with an already existing [iCODE]_id[/iCODE]. We should see an exception and the transaction should be rolled with only one movie being seen in our database. Let’s run and see if that is true. We rightfully see an exception and we can confirm that we have only one movie (the first part) inserted into the database. [ATTACH type=full" alt="Exception being thrown by the transaction as the document being added has the same id as an existing one]6014[/ATTACH] The following shows only a single document in the database as the transaction was rolled back. [ATTACH type=full" alt="Only one document in the database as the transaction was rolled back]6015[/ATTACH] Don’t worry, we will correctly add our trilogy in the database. Let’s remove the [iCODE]_id[/iCODE] assignment on our third entity and let MongoDB automatically insert it for us. [iCODE]var myMovie3 = new Movie { Title = "The Rise of EF Core 3" };[/iCODE] Once we re-run the program, we can see that all our entities have been added to the database. [ATTACH type=full" alt="All three movies in the trilogy in the database due to fixing the duplicate id issue]6016[/ATTACH] [HEADING=1]Summary[/HEADING] We were able to use the MongoDB EF Core provider with MongoDB Atlas to showcase different capabilities like adding a property to an entity on the fly, leveraging the Escape Hatch to create an index, performing complex queries via LINQ, and demonstrating the newly added transactions and optimistic concurrency support. [HEADING=1]Learn more[/HEADING] To learn more about EF Core and MongoDB: See the EF Core documentation to learn more about using EF Core to access all kinds of databases. See the MongoDB documentation to learn more about using MongoDB from any platform. See the MongoDB EF Core provider documentation for more information on how to get started. Watch the talk about on the Microsoft Youtube channel. The post MongoDB EF Core Provider: What’s New? appeared first on .NET Blog. Continue reading... Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.