Working with data is essential in contemporary online applications, and Entity Framework Core (EF Core) offers a productive method of interacting with databases through the use of Object-Relational Mapping (ORM). The Connected and Disconnected scenarios are the two main methods that EF Core provides for handling data in an application. Both situations differ in how data is recorded, accessed, and stored in the database, and they are crucial in distinct use cases. Both of these ideas are examined in this article along with workable solutions in a.NET Core MVC application.
What is a Connected Scenario?
In a Connected scenario, the DbContext is directly connected to the entity instances and actively tracks changes made to the data. This is the default behavior of EF Core when entities are retrieved using a context and modified within the scope of the context.
- Key Characteristics of a Connected Scenario
- Change Tracking: EF Core automatically tracks changes made to the entities. When SaveChanges() is called, it updates the database with those changes.
- Short-lived Context: The DbContext instance is created, used, and disposed of within a limited scope, often tied to a single HTTP request in a web application.
- Automatic State Management: EF Core keeps track of the state of entities, such as Added, Modified, Deleted, and Unchanged.
Practical Example
Let’s create a simple CRUD operation in a Connected scenario for managing a list of users.
Create the Model
namespace EFCoreConnectedDisconnectedDemo.Model
{
public class User
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Email { get; set; }
}
}
Set Up DbContext
using EFCoreConnectedDisconnectedDemo.Model;
using Microsoft.EntityFrameworkCore;
namespace EFCoreConnectedDisconnectedDemo.ApplicationContext
{
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
public DbSet<User> Users { get; set; }
}
}
Create the controller
using EFCoreConnectedDisconnectedDemo.ApplicationContext;
using EFCoreConnectedDisconnectedDemo.Model;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace EFCoreConnectedDisconnectedDemo.Controllers
{
public class UserController : Controller
{
private readonly ApplicationDbContext _context;
public UserController(ApplicationDbContext context)
{
_context = context;
}
// GET: User
public IActionResult Index()
{
var users = _context.Users.ToList();
return View(users);
}
// GET: User/Create
public IActionResult Create()
{
return View();
}
// POST: User/Create
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create([Bind("Id, Name, Email")] User user)
{
if (ModelState.IsValid)
{
_context.Add(user);
_context.SaveChanges(); // Save the changes in the connected scenario
return RedirectToAction(nameof(Index));
}
return View(user);
}
// GET: User/Edit/5
public IActionResult Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var user = _context.Users.Find(id);
if (user == null)
{
return NotFound();
}
return View(user);
}
// POST: User/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, [Bind("Id, Name, Email")] User user)
{
if (id != user.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(user);
_context.SaveChanges(); // EF Core automatically tracks changes and updates the database
}
catch (DbUpdateConcurrencyException)
{
if (!_context.Users.Any(e => e.Id == user.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(user);
}
// GET: User/Delete/5
public IActionResult Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var user = _context.Users
.FirstOrDefault(m => m.Id == id);
if (user == null)
{
return NotFound();
}
return View(user);
}
// POST: User/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public IActionResult DeleteConfirmed(int id)
{
var user = _context.Users.Find(id);
_context.Users.Remove(user);
_context.SaveChanges(); // EF Core will automatically track the deletion
return RedirectToAction(nameof(Index));
}
}
}
Create the Views
Index.cshtml, Create.cshtml, Edit.cshtml, and Delete.cshtml are Razor views for handling the respective CRUD actions.
How it Works in a Connected Scenario?
- Tracking Changes: EF Core tracks changes made to User entities in the database automatically.
- Saving Changes: Calling SaveChanges() saves the changes (whether added, updated, or deleted) to the database.
What is a Disconnected Scenario?
In a Disconnected scenario, the DbContext is not available to track changes once the data is retrieved from the database. This scenario is common in applications where the DbContext is disposed of after the entity data is fetched, such as in web APIs where entities are transferred over HTTP.
Key Characteristics of a Disconnected Scenario
- No Change Tracking: Entities are not tracked once the DbContext is disposed of, meaning changes must be manually managed.
- Manual State Management: In disconnected scenarios, the entity must be explicitly attached to the context and marked with its state (e.g., Modified) when changes are saved.
- Common in Web APIs: Commonly used in Web API scenarios where entities are transferred to and from clients and must be handled after detaching from the context.
Practical Example
Let’s handle a simple scenario where data is fetched in a disconnected manner, then updated and saved.
Create a Web API Controller for Disconnected Scenario
using EFCoreConnectedDisconnectedDemo.ApplicationContext;
using EFCoreConnectedDisconnectedDemo.Model;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace EFCoreConnectedDisconnectedDemo.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ApiUserController : ControllerBase
{
private readonly ApplicationDbContext _context;
public ApiUserController(ApplicationDbContext context)
{
_context = context;
}
// GET: api/ApiUser/5
[HttpGet("{id}")]
public ActionResult<User> GetUser(int id)
{
var user = _context.Users.Find(id);
if (user == null)
{
return NotFound();
}
return user;
}
// PUT: api/ApiUser/5
[HttpPut("{id}")]
public IActionResult PutUser(int id, User user)
{
if (id != user.Id)
{
return BadRequest();
}
// In disconnected scenario, we must attach the entity back to the context and mark it as modified
_context.Users.Attach(user);
_context.Entry(user).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
try
{
_context.SaveChanges(); // Save changes to the database manually
}
catch (DbUpdateConcurrencyException)
{
if (!_context.Users.Any(e => e.Id == user.Id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
}
}
How it Works in a Disconnected Scenario?
The data is fetched and passed to the client (e.g., as JSON).
When the client sends an updated version of the entity, the entity must be explicitly attached to the DbContext.
The entity is marked as modified, and the changes are saved by calling SaveChanges().
When to Use Each Scenario?
- Connected Scenario: Best suited for web applications or APIs where the DbContext remains active throughout the request lifecycle. It simplifies the development process since EF Core automatically handles change tracking.
- Disconnected Scenario: Ideal for web APIs and scenarios where the DbContext is not available after data is retrieved. It requires explicit management of entity states and is suitable for distributed systems or client-server models.
Conclusion
In this post, we looked at how to use Entity Framework Core in an ASP.NET Core MVC application to manage connected and disconnected circumstances. Knowing the distinctions between these two methods can help you decide whether your application needs manual state management (disconnected) or automatic change monitoring (connected) for data management. Both situations are necessary for developing effective and reliable data-driven applications and are crucial in various use cases.