Skip to content

10.1 release notes

InfoCarrier.Core 10.1 is about the store most application servers run: a relational database. The client now knows what a relational server can do, so queries that depend on that knowledge reach it instead of stopping at the boundary.

10.1.1 is the latest of this line and needs Entity Framework Core 10.0.1 or later. 10.1.0 works only against EF Core 10.0.0: on a later patch a query returning entities throws. Move a pinned 10.0.0 forward.

Upgrading from 10.0.x is a package version change. Read Queries that now throw first, because some queries that used to return an answer now raise an exception.

Raw SQL

FromSql and Database.SqlQuery<T> reach the server's database.

List<Order> heavy = await context.Orders
    .FromSql($"SELECT * FROM Orders WHERE Freight > {minimum}")
    .Where(o => o.Customer!.Country == "Germany")
    .ToListAsync();

List<decimal> freights = await context.Database
    .SqlQuery<decimal>($"SELECT Freight FROM Orders")
    .ToListAsync();

Both stay off until two grants line up: services.AddInfoCarrierArbitrarySqlExecution() on the server, and o.AllowArbitrarySqlExecution() on the client. Only the server's half is a boundary, and it denies by default.

Weigh what the grant is worth to whoever holds a client. One command text runs every statement in it, and an uncomposed FromSql reaches the database unchanged, so a caller who has the grant can run whatever the database allows, under the server's own rights. Security has the threat model.

A client without the grant is refused. Before this release the SQL was dropped and the query returned the whole table.

Functions your database defines

EF.Functions.Like, EF.Constant and EF.Parameter cross the wire. A store's own family, such as SqliteDbFunctionsExtensions or SqlServerDbFunctionsExtensions, is not in this package's vocabulary, so name it on both halves:

// The server, which references the provider that declares them.
builder.Services.AddInfoCarrierAllowedTypes(typeof(SqlServerDbFunctionsExtensions));

// The client, which does not.
optionsBuilder.UseInfoCarrier(client, o => o.AllowTypes(typeof(SqlServerDbFunctionsExtensions)));

Functions you map yourself with HasDbFunction work too, scalar and table valued, including one mapped as an instance method.

Inheritance and table mapping

Table per hierarchy, table per type and table per concrete type all round trip, and so do table splitting and entity splitting. The client keeps the mapping the server's model has, rather than the one core EF Core would infer on its own.

AsSplitQuery and AsSingleQuery now travel with the request, so the server decides how many statements to issue. UseRelationalNulls reaches the server as well.

Relational metadata reaches the client model

entityType.GetTableName() and Model.GetRelationalModel() answer on a client context, so tooling that reads them works against one. They come from your own [Table] attributes and DbSet names, which both halves compile, so the two models agree. Nothing the client computes from them crosses the wire.

Queries that now throw

Three shapes of query used to return an answer and now raise InvalidOperationException, with EF Core's own wording:

Query Why
An OrderBy key of a type the wire cannot carry The server sent the whole table and the client sorted it
new Layout() ?? fallback in a predicate or an ordering The same, and the coalesce did nothing: new never returns null
Distinct, Union, Concat, Except or Intersect over a projection that carries a collection Every other provider refuses it, because the columns that identify a row do not survive

The first two were correct answers at a price nothing reported. The third was an answer no other provider gives, so LINQ written against it ran nowhere else.

If your server's store is not relational, say so and the three go away:

optionsBuilder.UseInfoCarrier(client, o => o.UseNonRelationalServerStore());

You are telling the client something it cannot work out for itself, because it never sees the server's provider. Saying it about a relational server does not make the query work there. It only removes the refusal here.

Seeing what a request costs

The meter InfoCarrier.Core counts round trips and how long each took, so you can measure a screen in a running application. See Counting round trips.

InfoCarrierEventId.QuerySplit now names which operators stayed on the client, and says whether they only reshape rows or remove them. A Where, Skip or Take among them means the server sent more rows than your query asked for.

A server can send the log events it raises back with the result, so a client sees EF's warnings about its own query. The server has to grant that, because a forwarded event describes the server's store. See Sending the server's log to the client.

Fixes

A query parameter still reached the database as a literal in four shapes that 10.0.1 did not cover: a HashSet, ImmutableArray or ReadOnlyCollection in a Contains, an entity compared with ==, a key with a value converter, and a complex value. Each now travels as a parameter, so the database reuses one plan per query instead of one per value.

DbUpdateException.Entries names the row the store rejected, rather than every row in the batch.

How it is verified

The provider inherits Microsoft's own EF Core specification suite:

Total tests: 29516, Passed: 29259, Failed: 19, Skipped: 238

The suite is about seven thousand tests larger than at 10.0, which is where the higher failure count comes from. The nineteen are classified and gated in continuous integration, so the number cannot grow unnoticed, and Limitations states the ones a caller can observe. The 238 skips are EF Core's own, not suppressions added here.

Feedback

If you encounter a bug, have a question, or would like to request a feature, open an issue.