Skip to content

11_FlattenAttribute

github-actions[bot] edited this page Jan 12, 2026 · 2 revisions

Flatten Attribute Reference

The [Flatten] attribute automatically generates a flattened projection of a source type, expanding all nested properties into top-level properties. This is particularly useful for API responses, reporting, and scenarios where you need a denormalized view of your data.

What is Flattening?

Flattening transforms a hierarchical object structure into a flat structure with all nested properties as top-level properties. Instead of manually writing DTOs with properties like AddressStreet, AddressCity, etc., Facet generates them automatically by traversing your domain model.

Example

Before (Domain Model):

public class Person
{
    public string FirstName { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
}

After (Flattened DTO):

[Flatten(typeof(Person))]
public partial class PersonFlatDto
{
    // Auto-generated:
    // public string FirstName { get; set; }
    // public string AddressStreet { get; set; }
    // public string AddressCity { get; set; }
}

Key Features

  • Automatic Property Discovery: Recursively traverses nested objects
  • Null-Safe Access: Uses null-conditional operators (?.) in constructors
  • LINQ Projection: Generates Projection expression for Entity Framework
  • Depth Control: Configurable maximum traversal depth
  • Exclusion Paths: Exclude specific nested properties
  • ID Filtering: Optional IgnoreNestedIds to exclude foreign keys and nested IDs
  • FK Clash Detection: Optional IgnoreForeignKeyClashes to eliminate duplicate foreign key data
  • Naming Strategies: Prefix or leaf-only naming
  • One-Way Operation: Flattening is intentionally one-way (no ToSource method)

Usage

Basic Flattening

[Flatten(typeof(Person))]
public partial class PersonFlatDto
{
    // All properties auto-generated
}

// Usage
var person = new Person
{
    FirstName = "John",
    Address = new Address { Street = "123 Main St", City = "Springfield" }
};

var dto = new PersonFlatDto(person);
// dto.FirstName = "John"
// dto.AddressStreet = "123 Main St"
// dto.AddressCity = "Springfield"

LINQ Projection for Entity Framework

// Efficient database projection
var dtos = await dbContext.People
    .Where(p => p.IsActive)
    .Select(PersonFlatDto.Projection)
    .ToListAsync();

Multi-Level Nesting

public class Person
{
    public string FirstName { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public Country Country { get; set; }
}

public class Country
{
    public string Name { get; set; }
    public string Code { get; set; }
}

[Flatten(typeof(Person))]
public partial class PersonFlatDto
{
    // Auto-generated:
    // public string FirstName { get; set; }
    // public string AddressStreet { get; set; }
    // public string AddressCountryName { get; set; }
    // public string AddressCountryCode { get; set; }
}

Parameters

Parameter Type Default Description
sourceType Type (required) The source type to flatten from.
exclude string[] null Property paths to exclude from flattening (e.g., "Address.Country", "Password").
Exclude string[] null Same as exclude (named property).
MaxDepth int 3 Maximum depth to traverse when flattening nested objects. Set to 0 for unlimited (not recommended).
NamingStrategy FlattenNamingStrategy Prefix Naming strategy for flattened properties.
IncludeFields bool false Include public fields in addition to properties.
IncludeCollections bool false Include collection properties as-is without flattening their contents.
GenerateParameterlessConstructor bool true Generate a parameterless constructor for object initialization.
GenerateProjection bool true Generate a LINQ projection expression for database queries.
UseFullName bool false Use fully qualified type name in generated file names to avoid collisions.
IgnoreNestedIds bool false When true, only keeps the root-level Id property and excludes all foreign key IDs and nested IDs.
IgnoreForeignKeyClashes bool false When true, automatically skips nested ID and FK properties that would duplicate foreign key data.

Naming Strategies

Prefix Strategy (Default)

Concatenates the full path to create the property name:

[Flatten(typeof(Person), NamingStrategy = FlattenNamingStrategy.Prefix)]
public partial class PersonFlatDto { }

// Generated properties:
// - FirstName
// - AddressStreet
// - AddressCity
// - AddressCountryName

LeafOnly Strategy

Uses only the leaf property name:

[Flatten(typeof(Person), NamingStrategy = FlattenNamingStrategy.LeafOnly)]
public partial class PersonFlatDto { }

// Generated properties:
// - FirstName
// - Street
// - City
// - Name // Warning: Can cause name collisions!

Warning: LeafOnly can cause name collisions if multiple nested objects have properties with the same name. Facet will automatically add numeric suffixes (e.g., Name2, Name3) to resolve collisions.

Excluding Properties

Exclude Top-Level Properties

[Flatten(typeof(Person), exclude: "DateOfBirth", "InternalNotes")]
public partial class PersonFlatDto { }

Exclude Nested Properties

Use dot notation to exclude specific nested paths:

[Flatten(typeof(Person), exclude: "Address.Country")]
public partial class PersonFlatDto
{
    // Generated:
    // - FirstName
    // - AddressStreet
    // - AddressCity
    // (Country properties excluded)
}

Exclude Entire Nested Objects

[Flatten(typeof(Person), exclude: "ContactInfo")]
public partial class PersonFlatDto
{
    // ContactInfo and all its nested properties excluded
}

Ignoring Nested IDs

The IgnoreNestedIds parameter provides a convenient way to exclude all ID properties except the root-level Id. This is particularly useful for API responses where you want to display data but don't need all the foreign key IDs and nested entity IDs.

Without IgnoreNestedIds (Default)

public class Order
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
    public Customer Customer { get; set; }
    public DateTime OrderDate { get; set; }
}

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

[Flatten(typeof(Order))]
public partial class OrderFlatDto
{
    // Generated:
    // public int Id { get; set; }
    // public int CustomerId { get; set; }        // Foreign key from root
    // public int CustomerId2 { get; set; }       // Customer.Id (name collision, gets suffix)
    // public string CustomerName { get; set; }
    // public DateTime OrderDate { get; set; }
}

With IgnoreNestedIds

[Flatten(typeof(Order), IgnoreNestedIds = true)]
public partial class OrderFlatDto
{
    // Generated:
    // public int Id { get; set; }                // Root-level Id preserved
    // public string CustomerName { get; set; }
    // public DateTime OrderDate { get; set; }
    // (CustomerId and CustomerId2/Customer.Id excluded)
}

Behavior Rules

  1. Root-level Id is always kept: The top-level Id property of the source type is included
  2. Foreign key IDs are excluded: Properties like CustomerId, ProductId are excluded at the root level
  3. All nested IDs are excluded: Any Id property from nested objects is excluded

Use Cases

  • API responses where you don't want to expose database keys
  • Reports where IDs clutter the output
  • Search results where you only need the primary ID for navigation
  • Export files where human-readable data is preferred over foreign keys

Example: Clean API Response

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int CategoryId { get; set; }
    public Category Category { get; set; }
    public int ManufacturerId { get; set; }
    public Manufacturer Manufacturer { get; set; }
}

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Manufacturer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Country { get; set; }
}

[Flatten(typeof(Product), IgnoreNestedIds = true)]
public partial class ProductDisplayDto
{
    // Generated:
    // public int Id { get; set; }                    // Root ID kept
    // public string Name { get; set; }
    // public string CategoryName { get; set; }       // Category.Id excluded
    // public string ManufacturerName { get; set; }   // Manufacturer.Id excluded
    // public string ManufacturerCountry { get; set; }
    // (CategoryId and ManufacturerId excluded at root)
}

// Clean API response without exposing internal IDs
[HttpGet("products/{id}")]
public async Task<IActionResult> GetProduct(int id)
{
    var product = await dbContext.Products
        .Where(p => p.Id == id)
        .Select(ProductDisplayDto.Projection)
        .FirstOrDefaultAsync();

    return Ok(product);
    // Response: { "id": 1, "name": "Widget", "categoryName": "Tools",
    //             "manufacturerName": "ACME", "manufacturerCountry": "USA" }
}

Ignoring Foreign Key Clashes

The IgnoreForeignKeyClashes parameter helps eliminate duplicate ID data when flattening entities with foreign key relationships. When enabled, it automatically detects and skips properties that would represent the same data as a foreign key property.

The Problem: Foreign Key Duplication

In Entity Framework models with foreign keys and navigation properties, you often have both:

  1. A foreign key property (e.g., AddressId)
  2. A navigation property (e.g., Address)
  3. The referenced entity's ID (e.g., Address.Id)

When flattening, both AddressId and Address.Id become AddressId, causing naming collisions and representing the same data twice.

Without IgnoreForeignKeyClashes (Default)

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int? AddressId { get; set; }  // Foreign key
    public Address Address { get; set; }  // Navigation property
}

public class Address
{
    public int Id { get; set; }
    public string Line1 { get; set; }
    public string City { get; set; }
}

[Flatten(typeof(Person))]
public partial class PersonFlatDto
{
    // Generated:
    // public int Id { get; set; }
    // public string Name { get; set; }
    // public int? AddressId { get; set; }        // FK property
    // public int? AddressId2 { get; set; }       // Address.Id (collision!)
    // public string AddressLine1 { get; set; }
    // public string AddressCity { get; set; }
}

With IgnoreForeignKeyClashes

[Flatten(typeof(Person), IgnoreForeignKeyClashes = true)]
public partial class PersonFlatDto
{
    // Generated:
    // public int Id { get; set; }
    // public string Name { get; set; }
    // public int? AddressId { get; set; }     // FK property kept
    // public string AddressLine1 { get; set; }
    // public string AddressCity { get; set; }
    // (Address.Id is skipped - would be duplicate of AddressId)
}

Behavior Rules

  1. Detects FK patterns: Identifies properties ending with "Id" that have a matching navigation property
  2. Skips nested IDs: When a navigation property is flattened, its Id property is skipped if it would match a FK
  3. Skips nested FKs: Foreign keys within nested objects are also skipped to avoid deep duplicates
  4. Preserves root FKs: Foreign keys at the root level are always included
  5. Works at all depths: Handles complex scenarios like Customer.HomeAddressId and Customer.HomeAddress.Id

Example: Complex Nested Foreign Keys

public class Order
{
    public int Id { get; set; }
    public DateTime OrderDate { get; set; }
    public int CustomerId { get; set; }           // Root FK
    public Customer Customer { get; set; }
    public int? ShippingAddressId { get; set; }   // Root FK
    public Address ShippingAddress { get; set; }
}

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public int? HomeAddressId { get; set; }       // Nested FK
    public Address HomeAddress { get; set; }
}

public class Address
{
    public int Id { get; set; }
    public string Line1 { get; set; }
    public string City { get; set; }
}

[Flatten(typeof(Order), IgnoreForeignKeyClashes = true)]
public partial class OrderFlatDto
{
    // Generated:
    // public int Id { get; set; }
    // public DateTime OrderDate { get; set; }
    // public int CustomerId { get; set; }                 // Root FK included
    // public string CustomerName { get; set; }
    // public string CustomerEmail { get; set; }
    // public string CustomerHomeAddressLine1 { get; set; }
    // public string CustomerHomeAddressCity { get; set; }
    // public int? ShippingAddressId { get; set; }        // Root FK included
    // public string ShippingAddressLine1 { get; set; }
    // public string ShippingAddressCity { get; set; }
    //
    // Skipped (would be duplicates):
    // - Customer.Id (would clash with CustomerId)
    // - Customer.HomeAddressId (nested FK)
    // - Customer.HomeAddress.Id (would clash with CustomerHomeAddressId)
    // - ShippingAddress.Id (would clash with ShippingAddressId)
}

Use Cases

  • Entity Framework models with explicit foreign key properties
  • API responses that don't need duplicate ID data
  • Cleaner DTOs without naming collisions from IDs
  • Database-first models that follow FK conventions

Combining with IgnoreNestedIds

You can use both IgnoreNestedIds and IgnoreForeignKeyClashes together:

[Flatten(typeof(Order), IgnoreNestedIds = true, IgnoreForeignKeyClashes = true)]
public partial class OrderDisplayDto
{
    // This combination:
    // 1. Ignores ALL ID properties except root (IgnoreNestedIds)
    // 2. No FK clashes to worry about since FKs are also IDs (both work together)
    //
    // Generated:
    // public int Id { get; set; }              // Root ID only
    // public DateTime OrderDate { get; set; }
    // public string CustomerName { get; set; }
    // public string CustomerEmail { get; set; }
    // (All foreign keys and IDs excluded)
}

Note: When using both together, IgnoreNestedIds takes precedence since it removes all ID properties (which includes FKs). However, IgnoreForeignKeyClashes provides more granular control if you want to keep root-level FKs while avoiding clash duplicates.

Controlling Depth

Default Depth (3 Levels)

[Flatten(typeof(Person))]
public partial class PersonFlatDto { }
// Traverses up to 3 levels deep

Custom Depth

[Flatten(typeof(Person), MaxDepth = 2)]
public partial class PersonFlatDto { }
// Only traverses 2 levels deep
// Person.Address.Country would NOT be included

Safety Limit

Even with MaxDepth = 0 (unlimited), Facet enforces a safety limit of 10 levels to prevent stack overflow from circular references or extremely deep hierarchies.

What Gets Flattened?

Facet automatically determines which types should be flattened as "leaf" properties and which should be recursed into:

Always Flattened (Leaf Properties)

  • Primitive types (int, bool, decimal, etc.)
  • string
  • Enums
  • DateTime, DateTimeOffset, TimeSpan
  • Guid
  • Value types with 0-2 properties

Recursed Into (Nested Objects)

  • Complex reference types with properties
  • Value types with 3+ properties

Completely Ignored (by default)

  • Collections (Lists, Arrays, IEnumerable, etc.) - These are skipped entirely and don't generate any flattened properties

Including Collections

By default, collections are excluded from flattened types. However, you can opt-in to include collection properties using the IncludeCollections parameter. When enabled, collection properties are "hoisted" as-is into the flattened type without attempting to flatten their contents.

Without IncludeCollections (Default)

public class ApiResponse
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Item> Items { get; set; }
    public string[] Tags { get; set; }
}

[Flatten(typeof(ApiResponse))]
public partial class ApiResponseFlatDto
{
    // Generated:
    // public int Id { get; set; }
    // public string Name { get; set; }
    // (Items and Tags are excluded)
}

With IncludeCollections

[Flatten(typeof(ApiResponse), IncludeCollections = true)]
public partial class ApiResponseFlatDto
{
    // Generated:
    // public int Id { get; set; }
    // public string Name { get; set; }
    // public List<Item> Items { get; set; }      // Collection included as-is
    // public string[] Tags { get; set; }         // Array included as-is
}

Behavior Rules

  1. Collections are not flattened: The collection type and its element type are preserved exactly as declared
  2. Nested objects are still flattened: Scalar properties from nested objects continue to be flattened
  3. Works with all collection types: List<T>, Array, IEnumerable<T>, ICollection<T>, IList<T>, HashSet<T>, etc.
  4. Naming strategies apply: Collection property names follow the same naming strategy as other properties

Use Cases

API Responses:

public class OrderResponse
{
    public int OrderId { get; set; }
    public Customer Customer { get; set; }
    public List<OrderLine> Lines { get; set; }
}

[Flatten(typeof(OrderResponse), IncludeCollections = true)]
public partial class OrderResponseFlat
{
    // Generated:
    // public int OrderId { get; set; }
    // public string CustomerName { get; set; }     // Flattened from Customer
    // public string CustomerEmail { get; set; }    // Flattened from Customer
    // public List<OrderLine> Lines { get; set; }   // Collection preserved
}

Export/Report Data:

public class ProductExport
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Category Category { get; set; }
    public string[] Tags { get; set; }
    public List<Image> Images { get; set; }
}

[Flatten(typeof(ProductExport), IncludeCollections = true)]
public partial class ProductExportFlat
{
    // Scalar properties flattened, collections preserved
}

Combining with Other Options

You can combine IncludeCollections with other Flatten options:

[Flatten(typeof(Order),
    IncludeCollections = true,
    IgnoreNestedIds = true,
    NamingStrategy = FlattenNamingStrategy.SmartLeaf)]
public partial class OrderFlatDto
{
    // Collections included
    // Nested IDs excluded
    // SmartLeaf naming for collisions
}

Complete Examples

API Response DTO

public class Order
{
    public int Id { get; set; }
    public DateTime OrderDate { get; set; }
    public Customer Customer { get; set; }
    public Address ShippingAddress { get; set; }
    public decimal Total { get; set; }
}

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

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string ZipCode { get; set; }
}

[Flatten(typeof(Order))]
public partial class OrderListDto
{
    // Auto-generated:
    // public int Id { get; set; }
    // public DateTime OrderDate { get; set; }
    // public int CustomerId { get; set; }
    // public string CustomerName { get; set; }
    // public string CustomerEmail { get; set; }
    // public string ShippingAddressStreet { get; set; }
    // public string ShippingAddressCity { get; set; }
    // public string ShippingAddressState { get; set; }
    // public string ShippingAddressZipCode { get; set; }
    // public decimal Total { get; set; }
}

// API Usage
[HttpGet("orders")]
public async Task<IActionResult> GetOrders()
{
    var orders = await dbContext.Orders
        .Where(o => o.Status == OrderStatus.Completed)
        .Select(OrderListDto.Projection)
        .ToListAsync();

    return Ok(orders);
}

Report Generation

public class Employee
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Department Department { get; set; }
    public Address HomeAddress { get; set; }
    public decimal Salary { get; set; }
}

public class Department
{
    public string Name { get; set; }
    public string Code { get; set; }
    public Manager Manager { get; set; }
}

public class Manager
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
}

[Flatten(typeof(Employee), exclude: "Salary")] // Exclude sensitive data
public partial class EmployeeReportDto { }

// Generate report
var report = await dbContext.Employees
    .Select(EmployeeReportDto.Projection)
    .ToListAsync();

await GenerateExcelReport(report);

Limiting Depth for Performance

// Deep hierarchy
public class Organization
{
    public string Name { get; set; }
    public Location Headquarters { get; set; }
}

public class Location
{
    public string Name { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public City City { get; set; }
}

public class City
{
    public string Name { get; set; }
    public State State { get; set; }
}

public class State
{
    public string Name { get; set; }
    public Country Country { get; set; }
}

public class Country
{
    public string Name { get; set; }
    public string Code { get; set; }
}

// Limit depth to 3 levels
[Flatten(typeof(Organization), MaxDepth = 3)]
public partial class OrganizationSummaryDto
{
    // Includes:
    // - Name
    // - HeadquartersName
    // - HeadquartersAddressStreet
    // Does NOT include City, State, Country (beyond depth 3)
}

Best Practices

  1. Use for Read-Only Scenarios: Flattening is ideal for API responses, reports, and display models
  2. Set Appropriate MaxDepth: Don't flatten more than you need - it affects both performance and readability
  3. Exclude Sensitive Data: Use exclude parameter to omit passwords, salaries, or internal fields
  4. Consider IgnoreNestedIds: For public APIs and reports, use IgnoreNestedIds = true to avoid exposing database implementation details
  5. Prefer Prefix Naming: Avoid LeafOnly unless you're certain there won't be name collisions
  6. Combine with LINQ: Use the Projection property for efficient database queries
  7. Document Flattened Types: Add XML comments to explain what was flattened and why

Common Patterns

Search Results

[Flatten(typeof(SearchResult), MaxDepth = 2)]
public partial class SearchResultDto { }

Dashboard Widgets

[Flatten(typeof(DashboardMetrics))]
public partial class MetricsSummaryDto { }

Export/Import

[Flatten(typeof(Product), exclude: ["InternalNotes", "CostPrice"], IgnoreNestedIds = true)]
public partial class ProductExportDto { }

Troubleshooting

Name Collisions

Problem: Multiple properties end up with the same name when using LeafOnly strategy.

Solution: Use Prefix strategy (default) or manually rename properties in your domain model.

Properties Missing

Problem: Expected properties aren't included in the flattened DTO.

Solution: Check MaxDepth setting - you might need to increase it, or the property type might not be considered a "leaf" type.

Circular References

Problem: Worried about circular references causing infinite loops.

Solution: Facet has built-in protection - it tracks visited types and won't recurse infinitely, plus there's a safety limit of 10 levels.

Comparison: Flatten vs Facet with NestedFacets

Feature [Flatten] [Facet] with NestedFacets
Property Structure All properties at top level Preserves nested structure
Naming AddressStreet Address.Street
ToSource Method No (one-way only) Yes (bidirectional)
Use Case API responses, reports, exports Full CRUD, domain mapping
Setup Single attribute, automatic Requires defining each nested facet
Flexibility Less (automatic) More (explicit control)

FlattenTo Property (Unpack Collections to Rows)

While the [Flatten] attribute flattens nested objects into a single DTO, the FlattenTo property on the [Facet] attribute unpacks collection properties into multiple rows. This is useful for reports, exports, and scenarios where you need to denormalize a parent-child relationship.

What is FlattenTo?

FlattenTo transforms a facet with a collection property into multiple rows, combining the parent's properties with each collection item:

// Source entities
public class DataEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public ICollection<ExtendedEntity> Extended { get; set; }
}

public class ExtendedEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int DataValue { get; set; }
}

// Facet for the collection item
[Facet(typeof(ExtendedEntity))]
public partial class ExtendedFacet;

// Facet for the parent with FlattenTo
[Facet(typeof(DataEntity),
    NestedFacets = [typeof(ExtendedFacet)],
    FlattenTo = [typeof(DataFlattenedDto)])]
public partial class DataFacet;

// Flattened target (you define the properties you want)
public partial class DataFlattenedDto
{
    // Properties from parent (DataEntity)
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }

    // Properties from collection item (ExtendedEntity)
    // Use a prefix to avoid Name collision
    public string ExtendedName { get; set; }
    public int DataValue { get; set; }
}

Usage

var entity = new DataEntity
{
    Id = 1,
    Name = "Parent",
    Description = "Parent Description",
    Extended = new List<ExtendedEntity>
    {
        new() { Id = 10, Name = "Item 1", DataValue = 100 },
        new() { Id = 20, Name = "Item 2", DataValue = 200 }
    }
};

var facet = new DataFacet(entity);
var rows = facet.FlattenTo();

// Result: 2 rows
// Row 1: { Id: 1, Name: "Parent", Description: "Parent Description", ExtendedName: "Item 1", DataValue: 100 }
// Row 2: { Id: 1, Name: "Parent", Description: "Parent Description", ExtendedName: "Item 2", DataValue: 200 }

Key Features

  • Row-per-item output: Creates one output row for each collection item
  • Parent data replication: Parent properties are copied to each row
  • Type-safe: Target types are defined explicitly with the properties you need
  • Null-safe: Returns empty list when collection is null or empty

Handling Name Collisions

When both parent and child have properties with the same name, prefix the child property:

public class FlattenedOutput
{
    // From parent
    public int Id { get; set; }
    public string Name { get; set; }

    // From child - prefixed to avoid collision
    public int ItemId { get; set; }      // Maps from child's Id
    public string ItemName { get; set; } // Maps from child's Name
}

Use Cases

Report Generation:

var reportRows = invoices
    .Select(i => new InvoiceFacet(i))
    .SelectMany(f => f.FlattenTo())
    .ToList();

await GenerateExcelReport(reportRows);

CSV Export:

var exportRows = orderFacet.FlattenTo();
await csvWriter.WriteRecordsAsync(exportRows);

Comparison: FlattenTo vs Flatten Attribute

Feature FlattenTo property [Flatten] attribute
Purpose Unpack collections to rows Flatten nested objects to properties
Output Multiple rows (List) Single object
Input Facet with collection Entity with nested objects
Control You define target properties Properties auto-generated
Use case Reports, exports API responses, DTOs

See Also

Clone this wiki locally