Access to data has always been one of the most critical bottlenecks in software development. For decades, developers have written thousands of lines of repetitive SQL code, managed database connections manually, and painstakingly mapped tabular results to complex objects. A process that not only slowed down development but introduced countless opportunities for error, from SQL injection to inconsistencies in data types. Entity Framework Core represents the modern answer to this age-old challenge, transforming the way in which .NET applications interact with databases.

But why should we still care about how we access data in the era of cloud computing and microservices? The answer is simple: despite all the architectural innovations, data remains the beating heart of every application. A perfect architecture with inefficient access to data is like a Ferrari with flat tires. Entity Framework Core isn't just a tool for simplifying code writing; it's a strategic component that can determine the performance, scalability, and maintainability of the entire application.
The evolution from Entity Framework 6 to Core was not a simple technological migration. It was a complete reengineering that embraced the principles of cloud-native computing, distributed architectures and performance optimized for containers. Today, EF Core is the bridge that allows .NET developers to build data-intensive applications without losing the productivity that characterizes the Microsoft ecosystem.
Entity Framework Core is an open source, lightweight and extensible Object-Relational Mapper (ORM) that allows .NET developers to work with databases using native .NET objects, eliminating the need to write most of the traditional data access code. Unlike its predecessor Entity Framework 6, EF Core has been completely rewritten to be cross-platform, supporting not only Windows but also Linux and MacOS, making it perfect for modern cloud-native applications.
EF Core's revolution lies in its pragmatic and flexible approach to database abstraction. It's not about completely hiding the underlying database, but about providing an elegant interface that allows developers to take advantage of the power of LINQ to query data, while maintaining the ability to go down to the SQL level when necessary for specific optimizations. This balance between abstraction and control has made EF Core the de facto standard for accessing data in the .NET ecosystem.
The real power of EF Core emerges when we consider the full development lifecycle. It's not just a matter of executing queries; EF Core manages the tracking of changes, the automatic generation of optimized SQL, the management of transactions, first and second level caching, and above all database migrations, allowing the schema to be evolved in a controlled and versioned way together with the application code.
The operation of EF Core revolves around the concept of DbContext, a class that represents a session with the database and coordinates all data access operations. The DbContext maintains a local cache of the loaded entities, tracks the changes made, and orchestrates the saving of these changes to the underlying database. This Unit of Work pattern ensures that all changes are applied atomically and consistently.
The model in EF Core is the conceptual representation of the database in the application. It consists of entity classes that map database tables and configurations that define how these entities relate to each other and to the database schema. EF Core supports three approaches to defining the model: Code First where you start from the .NET code to generate the database, Database First where you generate the model from an existing database, and Model First which starts from a conceptual model. The flexibility to choose the approach that best suits the specific context is one of EF Core's strengths.
Translating LINQ queries into SQL is where the magic of EF Core becomes apparent. When we write a LINQ query such as context.customers.where (c => c.City == “Milan”) .orderby (c => c.Name), EF Core analyzes the expression tree, optimizes the query based on the database structure and available metadata, and generates SQL specific to the database provider used. This translation process is incredibly sophisticated, supporting complex joins, aggregations, projections, and even window function operations when supported by the underlying database.

One of EF Core's most powerful features is its pluggable provider system that allows the same code to be used with different databases. Microsoft maintains providers for SQL Server, SQLite, and Azure Cosmos DB, while the community has developed providers for PostgreSQL, MySQL, Oracle, and many others. Each provider not only translates queries into the specific SQL dialect of the database, but also optimizes operations based on the unique characteristics of each system.
This abstraction goes far beyond simple code portability. EF Core providers can take advantage of specific database functionality while maintaining a consistent API. For example, the PostgreSQL provider natively supports JSON and array data types, while the SQL Server provider automatically optimizes queries to use columnstore indexes when available. This combination of abstraction and specialization allows you to write portable code without sacrificing performance or advanced functionality.
EF Core's built-in logging and diagnostics system provides complete visibility into what's happening under the hood. Every SQL query generated, every connection opened, every transaction initiated can be monitored and analyzed. This is essential not only for debugging but also for optimizing performance in production, where tools such as Application Insights can aggregate these metrics to identify slow queries or inefficient access patterns.
We develop solutions based on artificial intelligence, with particular attention to modern technologies for information management. We work on projects that apply RAG, Machine Learning and natural language processing to improve productivity, customer experience and data analysis in any sector.
Our services include migrating legacy databases to modern architectures with EF Core, performance optimization for data-intensive applications, implementation of Repository patterns and Unit of Work enterprise, consulting on testing strategies for data layers, design of complex models with inheritance and owned types, integration of EF Core with microservices architectures and event sourcing.
Find out how we worked to create this project with .NET Core.
Migrations to EF Core represent one of the most revolutionary and practical features for managing database schema evolution. Instead of manually managing SQL scripts for each change, EF Core automatically generates migrations that capture the differences between the current model and the snapshot of the previous model. These migrations are versionable C# code that can be modified, tested, and deployed along with the rest of the application.
The process of creating a migration begins with the Add-Migration command that analyzes the changes to the model and generates two files: one containing the operations to apply the migration (Up) and one to cancel it (Down). This bidirectional approach allows not only to evolve the scheme but also to roll back in case of problems, a critical capability in production environments where service continuity is essential.
The application of migrations can take place in different ways, each suitable for specific scenarios. During development, migrations can be applied automatically when the application is launched, accelerating the development cycle. In production, however, it is advisable to generate SQL scripts from migrations that can be reviewed, optimized and applied through controlled deployment pipelines. EF Core 8 introduced migration bundles, standalone executables that can apply migrations without requiring the source code or the .NET SDK, further simplifying deployment.
Managing migrations in distributed teams presents unique challenges that EF Core elegantly addresses. When different developers create migrations in parallel, EF Core can detect and manage these conflicts, allowing changes to be merged in a controlled manner. The system also maintains a history table in the database that tracks which migrations have been applied, ensuring that each migration is performed only once and in the correct order.
We have created the Infrastructure & Security team, focused on the Azure cloud, to better respond to the needs of our customers who involve us in technical and strategic decisions. In addition to configuring and managing the tenant, we also take care of:
With Dev4Side, you have a reliable partner that supports you across the entire Microsoft application ecosystem.
Performance optimization in EF Core goes far beyond simply generating efficient SQL. The framework implements sophisticated caching, batching, and lazy loading strategies that, when used correctly, can dramatically improve application performance. The first level of caching operates at the DbContext level, keeping the entities already loaded in memory to avoid duplicate queries. This identity mapping also ensures that there is always only one instance of each entity in the context, preventing inconsistencies.
Automatic task batching is one of the most impactful optimizations introduced in EF Core. When you save multiple changes, instead of running a query for each operation, EF Core automatically groups the operations into batches optimized for the specific database. For SQL Server, for example, EF Core can combine up to 42 statements in a single round-trip, dramatically reducing network latency. This optimization is completely transparent to the developer but can reduce saving times by orders of magnitude.
Query splitting and AsSplitQuery are advanced techniques for dealing with the Cartesian explosion problem when loading entities with multiple related collections. Instead of generating a single joined query that can produce redundant results, EF Core can divide the query into multiple optimized queries that run in parallel. This approach can significantly reduce data transfer and entity materialization time, especially for graphs of complex objects.
The introduction of ExecuteUpdate and ExecuteDelete in EF Core 7 has revolutionized bulk operations scenarios. These APIs allow you to perform updates and deletes directly on the database without loading the entities into memory, completely bypassing the change tracker.

Implementing EF Core in enterprise settings requires mastery of sophisticated architectural patterns that go beyond the basic use of the framework. The Repository pattern, often debated in the community, still finds its place when used correctly to abstract complex queries or to facilitate testing. The modern implementation avoids generic repositories that simply wrap DbSet, instead focusing on specific repositories for aggregates that encapsulate complex business logic and optimized queries.
Managing optimistic competition through row versioning is essential in multi-user applications. EF Core natively supports several mechanisms, from SQL Server timestamp/rowversion to ETags for Cosmos DB. The configuration of competition tokens makes it possible to detect and manage conflicts when multiple sessions attempt to modify the same data, implementing resolution strategies that can range from simple “last wins” to sophisticated merge logic based on the business context.
Implementing multi-tenancy with EF Core presents unique challenges that the framework elegantly addresses. Whether they are separate databases for tenants, separate schemas, or row-level discriminators, EF Core provides the necessary hooks to implement these strategies transparently. The global query filters allow you to automatically apply filters per tenant to all queries, while the interceptor API allows you to dynamically modify the connection string or schema based on the context of the current tenant.
Managing complex domains with Value Objects and Owned Types demonstrates EF Core's maturity in supporting Domain-Driven Design. Owned types allow business concepts such as Address or Money to be modeled as complex types that are stored inline with the owner entity, maintaining encapsulation and type safety without sacrificing performance. The ability to configure these types with custom conversions allows you to maintain a rich domain model while mapping to optimized database structures.
The evolution of EF Core is following the broader trends of the .NET ecosystem and cloud computing. The integration with Azure and artificial intelligence is opening up completely new scenarios for accessing data. The compiled models introduced in EF Core 6 and improved in EF Core 9 drastically reduce startup times for models with hundreds or thousands of entities, making EF Core viable even for serverless functions where cold start is critical.
The optimization for Cosmos DB is transforming EF Core into a true multi-paradigm ORM that can manage not only relational databases but also document stores. The support for hierarchical partition keys, vector similarity search and the native integration with Azure Cosmos DB for PostgreSQL demonstrate how EF Core is evolving to support AI and machine learning workloads where semantic search and the processing of embeddings are fundamental.
Integration with .NET Aspire and cloud-native applications is redefining how EF Core is used in distributed architectures. The improved support for resilience through Polly, the native integration with health checks and readiness probes, and the support for distributed tracing through OpenTelemetry make EF Core a first-class citizen in the world of containers and Kubernetes.
The immediate future of EF Core includes significant improvements in complex query performance, extended support for bulk operations, and deeper integration with the new AOT (Ahead-Of-Time) compilation from .NET. The roadmap also includes improved support for temporal tables, graph databases through specialized providers, and native integration with event sourcing patterns for event-driven applications.
Entity Framework Core has transcended the role of a simple ORM to become the strategic foundation of the data layer in modern .NET applications. Its evolution from a productivity tool to a complete platform for accessing data reflects the broader transformation of the .NET ecosystem towards cloud-native, cross-platform and open source.
The maturity achieved by EF Core makes it suitable not only for simple CRUD applications but for complex enterprise scenarios that require high performance, horizontal scalability and integration with distributed architectures. The ability to balance abstraction and control, allowing developers to work at a high level with LINQ when possible and descend to SQL when necessary, represents the pragmatism that characterizes modern .NET development.
Looking to the future, EF Core will continue to evolve to address emerging data management challenges: from managing semi-structured data for AI workloads to real-time synchronization for collaborative applications, from support for distributed databases to integration with event-driven patterns. The ambitious roadmap and active community support ensure that EF Core will remain relevant and competitive for years to come.
For organizations that build on .NET, mastering Entity Framework Core is no longer optional but essential. It's not just about writing less boilerplate code, but about building data-driven applications that are maintainable, performing, and ready for the challenges of modern cloud computing. EF Core isn't just a bridge between objects and tables; it's the enabler that allows .NET developers to focus on business logic while the framework manages the complexities of accessing data.
Entity Framework Core is a completely rewritten, cross-platform, and open-source ORM for .NET. Unlike EF6, which runs only on Windows with the .NET Framework, EF Core supports .NET Core/.NET 5+ and runs on Linux, macOS, and Windows. EF Core is lighter, more performing and more modular, with a provider-based architecture that allows it to support non-relational databases such as Cosmos DB. However, some EF6 features such as automatic lazy loading proxies and EDMX designers are not available in Core.
Code First is ideal for new projects where the domain model guides the database design, for teams that prefer to work in code, and for scenarios where versioning and migrations are critical. Database First is preferred when working with existing legacy databases, when the database is managed by DBAs who control the schema, or when they integrate with systems that already use complex stored procedures. Model First with scaffolding from an existing database offers a compromise allowing you to start from the database but continue with Code First.
Optimizing performance requires attention at different levels: using AsnoTracking () for read-only queries, implementing projections with Select () to load only the necessary data, using Include () sparingly avoiding Cartesian explosion, exploiting compiled queries for frequent queries, configuring the batch size appropriately, using ExecuteUpdate/ExecuteDelete for bulk operations, and implementing strategic caching. Monitoring through logging and interceptors is essential to identify problematic queries.
Migrations capture model changes as versionable C# code. In development they can be applied automatically, but in production it is advisable to generate SQL scripts to be reviewed. Migration bundles in EF Core 8+ allow deployment without source code. It's important to always test migrations on a copy of the production database and have rollback strategies. For databases with a lot of traffic, consider blue-green deployments or online migrations that minimize downtime.
Yes, EF Core supports stored procedures in a number of ways. You can map entities to stored procedures for CRUD operations, execute stored procedures that return result sets with fromSQLraw (), call stored procedures that do not return data with executeSQLraw (), and use table-valued functions as if they were tables. However, the extensive use of stored procedures reduces portability and some of EF Core's advantages such as LINQ queries and change tracking.
The main strategies are: database per tenant (maximum isolation but high cost), schema per tenant (good compromise for SQL Server/PostgreSQL), and row-level security with discriminators (cheaper but requires careful design). EF Core supports all of these strategies through global query filters, interceptors to change connection strings dynamically, and the possibility of having multiple DbContext. The choice depends on isolation, scalability, and compliance requirements.
EF Core supports SQL Server, Azure SQL, SQLite, PostgreSQL, MySQL/MariaDB, Cosmos DB, Oracle, DB2, Firebird, and many others through providers maintained by Microsoft or the community. Each provider may have specific limitations, and not all EF Core functionality is available on all databases. It is important to verify the provider's compatibility with the version of EF Core used and to thoroughly test critical functionality.
EF Core supports optimistic competition through concurrency tokens (timestamp/rowversion in SQL Server, xmin in PostgreSQL). When a conflict is detected, a DBUpdateConcurrencyException is thrown, which can be managed to implement resolution strategies: database wins (reload and retry), client wins (force update), or custom merge. For complex scenarios, consider event sourcing or CQRS to eliminate root conflicts.
The debate is heated in the community. DbContext and DbSet already implement Repository and Unit of Work patterns, so generic wrappers only add complexity. Specific repositories make sense for: encapsulating complex reusable queries, facilitating unit testing with mocking, implementing data layer-specific business logic, or when you plan to change ORM in the future (rare). Most modern applications prefer to use DbContext directly with CQRS or feature folders.
For unit tests, use the in-memory provider or SQLite in-memory for quick but limited tests. For integration tests, prefer a real database in Docker containers through Testcontainers to ensure that the queries work with the production provider. Use TransactionScope or database recreation for isolation between tests. Respawn library can effectively reset the database between tests. Avoid mocking DbContext directly; if necessary, abstract behind specific interfaces or use pattern repositories.
The Infra & Security team focuses on the management and evolution of our customers' Microsoft Azure tenants. Besides configuring and managing these tenants, the team is responsible for creating application deployments through DevOps pipelines. It also monitors and manages all security aspects of the tenants and supports Security Operations Centers (SOC).