The AnyCPU Illusion: Native Dependencies in .NET Applications

The AnyCPU Illusion: Native Dependencies in .NET Applications

Introduction

In the .NET ecosystem, “AnyCPU” is often considered a silver bullet for cross-platform deployment. However, this assumption can lead to significant problems when your application depends on native assemblies. In this post, I want to share a personal story that highlights how I discovered these limitations and how native dependencies affect the true portability of AnyCPU applications, especially for database access through ADO.NET and popular ORMs.

My Journey to Understanding AnyCPU’s Limitations

Every year, around Thanksgiving or Christmas, I visit my friend, brother, and business partner Javier. Two years ago, during one of these visits, I made a decision that would lead me to a pivotal realization about AnyCPU architecture.

At the time, I was tired of traveling with my bulky MSI GE72 Apache Pro-24 gaming laptop. According to MSI’s official specifications, it weighed 5.95 pounds—but that number didn’t include the hefty charger, which brought the total to around 12 pounds. Later, I upgraded to an MSI GF63 Thin, which was lighter at 4.10 pounds—but with the charger, it was still around 7.5 pounds. Lugging these laptops through airports felt like a workout.

Determined to travel lighter, I purchased a MacBook Air with the M2 chip. At just 2.7 pounds, including the charger, the MacBook Air felt like a breath of fresh air. The Apple Silicon chip was incredibly fast, and I immediately fell in love with the machine.

Having used a MacBook Pro with Bootcamp and Windows 7 years ago, I thought I could recreate that experience by running a Windows virtual machine on my MacBook Air to check projects and do some light development while traveling.

The Virtualization Experiment

As someone who loves virtualization, I eagerly set up a Windows virtual machine on my MacBook Air. I grabbed my trusty Windows x64 ISO, set up the virtual machine, and attempted to boot it—but it failed. I quickly realized the issue was related to CPU architecture. My x64 ISO wasn’t compatible with the ARM-based M2 chip.

Undeterred, I downloaded a Windows 11 ISO for ARM architecture and created the VM. Success! Windows was up and running, and I installed Visual Studio along with my essential development tools, including DevExpress XPO (my favorite ORM).

The Demo Disaster

The real test came during a trip to Dubai, where I was scheduled to give a live demo showcasing how quickly you can develop Line-of-Business (LOB) apps with XAF. Everything started smoothly until I tried to connect my XAF app to the database. Despite my best efforts, the connection failed.

In the middle of the demo, I switched to an in-memory data provider to salvage the presentation. After the demo, I dug into the issue and realized the root cause was related to the CPU architecture. The native database drivers I was using weren’t compatible with the ARM architecture.

A Familiar Problem

This situation reminded me of the transition from x86 to x64 years ago. Back then, I encountered similar issues where native drivers wouldn’t load unless they matched the process architecture.

The Native Dependency Challenge

Platform-Specific Loading Requirements

Native DLLs must exactly match the CPU architecture of your application:

  • If your app runs as x86, it can only load x86 native DLLs.
  • If running as x64, it requires x64 native DLLs.
  • ARM requires ARM-specific binaries.
  • ARM64 requires ARM64-specific binaries.

There is no flexibility—attempting to load a DLL compiled for a different architecture results in an immediate failure.

How Native Libraries are Loaded

When your application loads a native DLL, the operating system follows a specific search pattern:

  1. The application’s directory
  2. System directories (System32/SysWOW64)
  3. Directories listed in the PATH environment variable

Crucially, these native libraries must match the exact architecture of the running process.

// This seemingly simple code
[DllImport("native.dll")]
static extern void NativeMethod();

// Actually requires:
// - native.dll compiled for x86 when running as 32-bit
// - native.dll compiled for x64 when running as 64-bit
// - native.dll compiled for ARM64 when running on ARM64

The SQL Server Example

Let’s look at SQL Server connectivity, a common scenario where the AnyCPU illusion breaks down:

// Traditional ADO.NET connection
using (var connection = new SqlConnection(connectionString))
{
    // This requires SQL Native Client
    // Which must match the process architecture
    await connection.OpenAsync();
}

Even though your application is compiled as AnyCPU, the SQL Native Client must match the process architecture. This becomes particularly problematic on newer architectures like ARM64, where native drivers may not be available.

Impact on ORMs

Entity Framework Core

Entity Framework Core, despite its modern design, still relies on database providers that may have native dependencies:

public class MyDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        // This configuration depends on:
        // 1. SQL Native Client
        // 2. Microsoft.Data.SqlClient native components
        optionsBuilder.UseSqlServer(connectionString);
    }
}

DevExpress XPO

DevExpress XPO faces similar challenges:

// XPO configuration
string connectionString = MSSqlConnectionProvider.GetConnectionString("server", "database");
XpoDefault.DataLayer = XpoDefault.GetDataLayer(connectionString, AutoCreateOption.DatabaseAndSchema);

// The MSSqlConnectionProvider relies on the same native SQL Server components

Solutions and Best Practices

1. Architecture-Specific Deployment

Instead of relying on AnyCPU, consider creating architecture-specific builds:

<PropertyGroup>
    <Platforms>x86;x64;arm64</Platforms>
    <RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
</PropertyGroup>

2. Runtime Provider Selection

Implement smart provider selection based on the current architecture:

public static class DatabaseProviderFactory
{
    public static IDbConnection GetProvider()
    {
        return RuntimeInformation.ProcessArchitecture switch
        {
            Architecture.X86 => new SqlConnection(), // x86 native provider
            Architecture.X64 => new SqlConnection(), // x64 native provider
            Architecture.Arm64 => new Microsoft.Data.SqlClient.SqlConnection(), // ARM64 support
            _ => throw new PlatformNotSupportedException()
        };
    }
}

3. Managed Fallbacks

Implement fallback strategies when native providers aren’t available:

public class DatabaseConnection
{
    public async Task<IDbConnection> CreateConnectionAsync()
    {
        try
        {
            var connection = new SqlConnection(_connectionString);
            await connection.OpenAsync();
            return connection;
        }
        catch (DllNotFoundException)
        {
            var managedConnection = new Microsoft.Data.SqlClient.SqlConnection(_connectionString);
            await managedConnection.OpenAsync();
            return managedConnection;
        }
    }
}

4. Deployment Considerations

  • Include all necessary native dependencies for each target architecture.
  • Use architecture-specific directories in your deployment.
  • Consider self-contained deployment to include the correct runtime.

Real-World Implications

This experience taught me that while AnyCPU provides excellent flexibility for managed code, it has limitations when dealing with native dependencies. These limitations become more apparent in scenarios like cloud deployments, ARM64 devices, and live demos.

Conclusion

The transition to ARM architecture is accelerating, and understanding the nuances of AnyCPU and native dependencies is more important than ever. By planning for architecture-specific deployments and implementing fallback strategies, you can build more resilient applications that can thrive in a multi-architecture world.

The Shift Towards Object Identifiers (OIDs):Why Compound Keys in Database Tables Are No Longer Valid

The Shift Towards Object Identifiers (OIDs):Why Compound Keys in Database Tables Are No Longer Valid

Why Compound Keys in Database Tables Are No Longer Valid

 

Introduction

 

In the realm of database design, compound keys were once a staple, largely driven by the need to adhere to normalization forms. However, the evolving landscape of technology and data management calls into question the continued relevance of these multi-attribute keys. This article explores the reasons why compound keys may no longer be the best choice and suggests a shift towards simpler, more maintainable alternatives like object identifiers (OIDs).

 

The Case Against Compound Keys

 

Complexity in Database Design

 

  • Normalization Overhead: Historically, compound keys were used to satisfy normalization requirements, ensuring minimal redundancy and dependency. While normalization is still important, the rigidity it imposes can lead to overly complex database schemas.
  • Business Logic Encapsulation: When compound keys include business logic, they can create dependencies that complicate data integrity and maintenance. Changes in business rules often necessitate schema alterations, which can be cumbersome.

Maintenance Challenges

 

  • Data Integrity Issues: Compound keys can introduce challenges in maintaining data integrity, especially in large and complex databases. Ensuring the uniqueness and consistency of multi-attribute keys can be error-prone.
  • Performance Concerns: Queries involving compound keys can become less efficient, as indexing and searching across multiple columns can be more resource-intensive compared to single-column keys.

 

The Shift Towards Object Identifiers (OIDs)

 

Simplified Design

 

  • Single Attribute Keys: Using OIDs as primary keys simplifies the schema. Each row can be uniquely identified by a single attribute, making the design more straightforward and easier to understand.
  • Decoupling Business Logic: OIDs help in decoupling the business logic from the database schema. Changes in business rules do not necessitate changes in the primary key structure, enhancing flexibility.

 

Easier Maintenance

 

  • Improved Data Integrity: With a single attribute as the primary key, maintaining data integrity becomes more manageable. The likelihood of key conflicts is reduced, simplifying the validation process.
  • Performance Optimization: OIDs allow for more efficient indexing and query performance. Searching and sorting operations are faster and less resource-intensive, improving overall database performance.

 

Revisiting Normalization

 

Historical Context

 

  • Storage Constraints: Normalization rules were developed when data storage was expensive and limited. Reducing redundancy and optimizing storage was paramount.
  • Modern Storage Solutions: Today, storage is relatively cheap and abundant. The strict adherence to normalization may not be as critical as it once was.

Balancing Act

 

  • De-normalization for Performance: In modern databases, a balance between normalization and de-normalization can be beneficial. De-normalization can improve performance and simplify query design without significantly increasing storage costs.
  • Practical Normalization: Applying normalization principles should be driven by practical needs rather than strict adherence to theoretical models. The goal is to achieve a design that is both efficient and maintainable.

ORM Design Preferences

 

Object-Relational Mappers (ORMs)

 

  • Design with OIDs in Mind: Many ORMs, such as XPO from DevExpress, were originally designed to work with OIDs rather than compound keys. This preference simplifies database interaction and enhances compatibility with object-oriented programming paradigms.
  • Support for Compound Keys: Although these ORMs support compound keys, their architecture and default behavior often favor the use of single-column OIDs, highlighting the practical advantages of simpler key structures in modern application development.

Conclusion

 

The use of compound keys in database tables, driven by the need to fulfill normalization forms, may no longer be the best practice in modern database design. Simplifying schemas with object identifiers can enhance maintainability, improve performance, and decouple business logic from the database structure. As storage becomes less of a constraint, a pragmatic approach to normalization, balancing performance and data integrity, becomes increasingly important. Embracing these changes, along with leveraging ORM tools designed with OIDs in mind, can lead to more robust, flexible, and efficient database systems.

Unlocking the Power of Augmented Data Models: Enhance Analytics and AI Integration for Better Insights

Unlocking the Power of Augmented Data Models: Enhance Analytics and AI Integration for Better Insights

In today’s data-driven world, the need for more sophisticated and insightful data models has never been greater. Traditional database models, while powerful, often fall short of delivering the depth and breadth of insights required by modern organizations. Enter the augmented data model, a revolutionary approach that extends beyond the limitations of traditional models by integrating additional data sources, enhanced data features, advanced analytical capabilities, and AI-driven techniques. This blog post explores the key components, applications, and benefits of augmented data models.

Key Components of an Augmented Data Model

1. Integration of Diverse Data Sources

An augmented data model combines structured, semi-structured, and unstructured data from various sources such as databases, data lakes, social media, IoT devices, and external data feeds. This integration enables a holistic view of data across the organization, breaking down silos and fostering a more interconnected understanding of the data landscape.

2. Enhanced Data Features

Beyond raw data, augmented data models include derived attributes, calculated fields, and metadata to enrich the data. Machine learning and artificial intelligence are employed to create predictive and prescriptive data features, transforming raw data into actionable insights.

3. Advanced Analytics

Augmented data models incorporate advanced analytical models, including machine learning, statistical models, and data mining techniques. These models support real-time analytics and streaming data processing, enabling organizations to make faster, data-driven decisions.

4. AI-Driven Embeddings

One of the standout features of augmented data models is the creation of embeddings. These are dense vector representations of data (such as words, images, or user behaviors) that capture their semantic meaning. Embeddings enhance machine learning models, making them more effective at tasks such as recommendation, natural language processing, and image recognition.

5. Data Visualization and Reporting

To make complex data insights accessible, augmented data models facilitate advanced data visualization tools and dashboards. These tools allow users to interact with data dynamically through self-service analytics platforms, turning data into easily digestible visual stories.

6. Improved Data Quality and Governance

Ensuring data quality is paramount in augmented data models. Automated data cleansing, validation, and enrichment processes maintain high standards of data quality. Robust data governance policies manage data lineage, security, and compliance, ensuring that data is trustworthy and reliable.

7. Scalability and Performance

Designed to handle large volumes of data, augmented data models scale horizontally across distributed systems. They are optimized for high performance in data processing and querying, ensuring that insights are delivered swiftly and efficiently.

Applications and Benefits

Enhanced Decision Making

With deeper insights and predictive capabilities, augmented data models significantly improve decision-making processes. Organizations can move from reactive to proactive strategies, leveraging data to anticipate trends and identify opportunities.

Operational Efficiency

By streamlining data processing and integration, augmented data models reduce manual efforts and errors. This leads to more efficient operations and a greater focus on strategic initiatives.

Customer Insights

Augmented data models enable a 360-degree view of customers by integrating various touchpoints and interactions. This comprehensive view allows for more personalized and effective customer engagement strategies.

Innovation

Supporting advanced analytics and machine learning initiatives, augmented data models foster innovation within the organization. They provide the tools and insights needed to develop new products, services, and business models.

Real-World Examples

Customer 360 Platforms

By combining CRM data, social media interactions, and transactional data, augmented data models create a comprehensive view of customer behavior. This holistic approach enables personalized marketing and improved customer service.

IoT Analytics

Integrating sensor data, machine logs, and external environmental data, augmented data models optimize operations in manufacturing or smart cities. They enable real-time monitoring and predictive maintenance, reducing downtime and increasing efficiency.

Fraud Detection Systems

Using transactional data, user behavior analytics, and external threat intelligence, augmented data models detect and prevent fraudulent activities. Advanced machine learning models identify patterns and anomalies indicative of fraud, providing a proactive defense mechanism.

AI-Powered Recommendations

Embeddings created from user interactions, product descriptions, and historical purchase data power personalized recommendations in e-commerce. These AI-driven insights enhance customer experience and drive sales.

Conclusion

Augmented data models represent a significant advancement in the way organizations handle and analyze data. By leveraging modern technologies and methodologies, including the creation of embeddings for AI, these models provide a more comprehensive and actionable view of the data. The result is enhanced decision-making, improved operational efficiency, deeper customer insights, and a platform for innovation. As organizations continue to navigate the complexities of the data landscape, augmented data models will undoubtedly play a pivotal role in shaping the future of data analytics.

 

What is an O.R.M (Object-Relational Mapping)

What is an O.R.M (Object-Relational Mapping)

ORM is a technique for converting data between incompatible type systems using object-oriented programming languages. In the context of database management, an ORM provides a way to interact with a database using objects and methods, rather than writing raw SQL queries. This allows for a higher level of abstraction, reducing the amount of repetitive and error-prone code that needs to be written, and making it easier to maintain the application.

ORM and RDBMS

Object-Relational Mapping (ORM) tools and RDBMS have different approaches to querying data.

RDBMS uses SQL which is a low-level, language-agnostic query language that is used to communicate with relational databases. When using SQL, developers write raw SQL statements that are executed directly against the database. The results of the query are then returned in the form of a table or set of tables, which must be manually mapped to objects in code.

On the other hand, an ORM tool abstracts the underlying SQL code, allowing developers to query data using an object-oriented syntax that is similar to the programming language used in the application.

The ORM tool automatically generates the necessary SQL statements and executes them against the database, and then maps the resulting data to objects in code.

One of the main advantages of using an ORM tool is that it eliminates the need for manual data mapping, making it easier for developers to write data-access code and reducing the chances of errors. Additionally, because ORM tools use a higher-level syntax, they can also be more intuitive and easier to use than raw SQL.

However, ORM tools can also introduce performance overhead and complexity, as they must manage the mapping between the database and objects in code. In some cases, raw SQL may still be necessary to achieve the desired performance or to perform complex data manipulation that is not supported by the ORM tool.

As a simple example we will use the database table “customer”. It has four columns:

  • id: A unique identifier for each customer, usually an auto-incrementing integer.
  • name: The name of the customer, represented as a string.
  • email: The email address of the customer, represented as a string.
  • address: The address of the customer, represented as a string.

Each row in the table represents a single customer, and each column represents a piece of information about that customer. The table is used to store the data for the customer objects created in the code. The ORM maps the table to a class, allowing you to interact with the data using objects and methods.

Here is an example of how an ORM can map a database table named “customer” to a class representation in C#:

class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public string Address { get; set; }
}

// The ORM maps the customer table in the database to the Customer class.
// The columns of the table become properties of the class, and each row in the table becomes an instance of the class.

In this example, the ORM abstracts away the underlying database and provides a high-level, object-oriented interface for working with the data.

The main advantage of using an ORM (Object-Relational Mapping) instead of using the direct results from the database in C# is increased abstraction and convenience. This provides several benefits:

  1. Improved readability and maintainability: Code written using an ORM is easier to understand and maintain, as it uses a higher-level, object-oriented interface, rather than raw SQL.
  2. Reduced code duplication: With an ORM, you can encapsulate database-related logic in reusable classes and methods, reducing the amount of repetitive code that needs to be written.
  3. Increased safety: An ORM can help prevent SQL injection attacks by automatically escaping user input, ensuring that data is safely inserted into the database.
  4. Increased flexibility: An ORM provides a layer of abstraction between the application code and the database, allowing you to switch between different database systems without changing the application code.

 

Direct database access

 

var connection = new SqlConnection("connection string");
connection. Open();
var command = new SqlCommand("SELECT * FROM customers", connection);
var reader = command.ExecuteReader();
List < Customer > customers = new List < Customer > ();
while (reader. Read()) {
  var customer = new Customer {
    Id = reader.GetInt32(0),
      Name = reader.GetString(1),
      Email = reader.GetString(2),
      Address = reader.GetString(3)
  };
  customers. Add(customer);
}
reader.Close();
connection. Close();

 

ORM access

 

var customers = ORM.Query<Customer>().ToList();

As you can see, using an ORM simplifies the code and makes it more readable, while also providing additional safety and flexibility benefits.

Different types of O.R.M 

 

Micro O.R.M

 

A micro ORM is a type of Object-Relational Mapping (ORM) library that is designed to be lightweight and simple, offering a minimalistic and easy-to-use interface for interacting with a relational database.

Micro ORMs are typically faster and require less configuration and setup than full-featured ORMs, but also have fewer features and less advanced capabilities.

Micro ORMs provide a higher-level, object-oriented interface for working with databases, allowing you to interact with the data using classes, properties, and methods, rather than writing raw SQL queries as well but they typically offer a smaller, more focused set of features than full-featured ORMs, including support for basic CRUD (Create, Read, Update, Delete) operations, and often provide basic querying and data mapping capabilities.

Micro ORMs are often used in small projects or in environments where performance is critical. They are also commonly used in microservices and other architectures that prioritize simplicity, scalability, and ease of deployment.

 

Full-fledged Object-Relational Mapping (ORM)

 

A full-fledged Object-Relational Mapping (ORM) is a software library that provides a convenient and efficient way of working with relational databases.

A full-fledged ORM typically has the following characteristics:

  1. Object Mapping: The ORM maps database tables to object-oriented classes, allowing developers to interact with the database using objects instead of tables.
  2. Query Generation: The ORM generates SQL statements based on the object-oriented query syntax, reducing the need for developers to write and manage raw SQL.
  3. Data Validation: The ORM provides built-in data validation and type checking, reducing the risk of incorrect data being written to the database.
  4. Caching: The ORM may provide caching of data to improve performance, reducing the number of databases round trips.
  5. Transactions: The ORM supports transactions, making it easier to ensure that database operations are atomic and consistent.
  6. Database Independence: The ORM is typically designed to be database agnostic, allowing developers to switch between different database platforms without changing the application code.
  7. Lazy Loading: The ORM supports lazy loading, which can improve performance by loading related objects only when they are needed.
  8. Performance: A comprehensive ORM is crafted to deliver satisfactory performance, and we can customize each aspect of the ORM to enhance its performance. However, it is important to note that a solid understanding of how the ORM operates is necessary to make these customizations effective.

One of the disadvantages of using an Object-Relational Mapping (ORM) tool is that you may not be able to create specialized, fine-tuned queries with the same level of precision as you can with raw SQL. This is because ORMs typically abstract the underlying database and expose a higher-level, more object-oriented API for querying data.

For example, it may not be possible to use specific SQL constructs, such as subqueries or window functions, in your ORM queries. Additionally, some ORMs may not provide a way to optimize query performance, such as by specifying index hints or controlling the execution plan.

As a result, developers who need to write specialized or fine-tuned queries may find it easier and more efficient to write raw SQL statements. However, this comes at the cost of having to manually manage the mapping between the database and the application, which is one of the main benefits of using an ORM in the first place.

 

ORM and Querying data

 

Before the existence of LINQ, Object-Relational Mapping (ORM) tools used a proprietary query language to query data. This meant that each ORM had to invent its own criteria or query language, and developers had to learn the specific syntax of each ORM in order to write queries.

For example, in XPO, developers used the Criteria API to write queries. The Criteria API allowed developers to write queries in a type-safe and intuitive manner, but the syntax was specific to XPO and not easily transferable to other ORMs.

Similarly, in NHibernate, developers used the Hibernate Query Language (HQL) to write queries. Like the Criteria API in XPO, HQL was a proprietary query language specific to NHibernate, and developers had to learn the syntax in order to write queries.

This lack of a standardized query language was one of the main limitations of ORMs before the introduction of LINQ. LINQ provided a common, language-agnostic query language that could be used with a variety of data sources, including relational databases. This made it easier for developers to write data-access code, as they only had to learn one query language, regardless of the ORM or data source being used.

 

The introduction of LINQ, a new era for O.R.M in dot net

 

Language Integrated Query (LINQ) is a Microsoft .NET Framework component that was introduced in .NET Framework 3.5. It is a set of language and library features that enables developers to write querying and manipulation operations over data in a more intuitive and readable way.

LINQ was created to address the need for a more unified and flexible approach to querying and manipulating data in .NET applications. Prior to LINQ, developers had to write separate code to access and manipulate data in different data sources, such as databases, XML documents, and in-memory collections. This resulted in a fragmented and difficult-to-maintain codebase, with different syntax and approaches for different data sources.

With LINQ, developers can now write a single querying language that works with a wide range of data sources, including databases, XML, in-memory collections, and more.

In addition, LINQ also provides a number of other benefits, such as improved performance, better type safety, and integrated support for data processing operations, such as filtering, grouping, and aggregating.

 

using (var context = new DbContext())
{
   var selectedCustomers = context.Customers
                                   .Where(c => c.Address.Contains("San Salvador"))
                                   .Select(c => new 
                                   { 
                                       c.Id, 
                                       c.Name, 
                                       c.Email, 
                                       c.Address 
                                   })
                                   .ToList();

    foreach(var customer in selectedCustomers)

    foreach(var customer in selectedCustomers)
    {
        Console.WriteLine($"Id: {customer.Id}, Name: {customer.Name}, Email: {customer.Email}, Address: {customer. Address}");
    }
}

 

And that’s all for this post, until next time ))

We are excited to announce that we are currently in the process of writing a comprehensive book about DevExpress XPO. As we work on this project, we believe it is essential to involve our readers and gather their valuable feedback. Therefore, we have decided to share articles from the book as we complete them, giving you an opportunity to provide input and suggestions that we can consider for inclusion in the final release. Keep in mind that the content presented is subject to change. We greatly appreciate your participation in this collaborative effort.

Related Articles

ADO The origin of data access in .NET

Relational database systems: the holy grail of data