Skip to content

DbFunctions: self-bootstrapping functions #9810

@divega

Description

@divega

TL;DR

We can enable instance functions and extension methods defined on the DbContext to be called directly outside of queries, since access to "this" DbContext instance can infuse the ability to bootstrap server LINQ queries without having to go through a DbSet<TEntity>:

  var count = db.PostReadCount(p.Id);

This applies to scalar functions as well as TVFs (which would look like any other self-bootstrapping function but would return IQueryable<T>).

Details

(originally from comment at #9213 (comment))

One of the reasons to support instance functions based on DbContext (#9213) is to be able to pass the DbContext to the implementation of the function, which may involve querying for data that exists in the database.

There is another click-stop beyond that which is to be able to use the function directly. E.g. instead of always having to call the function like this:

foreach(var p in posts.Where(p => db.PostReadCount(p.Id) > 5))
{
  // do something
}

We can enable calling the function directly in code:

foreach(var p in posts)
{
  ...
  if (db.PostReadCount(p.Id) > 5)
  {
    // do something
  }
}

(I understand the scenario above is a bit contrived, because in theory calling the function in the query may avoid additional round-trips, but I don't think this example negates the value of self-bootstrapping functions in general)

Since in the body of the method we have access to "this" DbContext, we can use it to execute a query in which the method is invoked:

public int PostReadCount(int postId)
{
  return Posts.Where(p => p.Id == postId).Select(p => db.PostReadCount(p.Id)).FirstOrDefault();  
}

Although ideally the user should be able to do something like this some day:

public int PostReadCount(int postId)
{
  return Execute(db => db.PostReadCount(postId));  
}

Where assuming there is a PostReadCount() scalar function in the database would translate to something like this:

SELECT dbo.PostReadCount(@postId);

We actually had this kind of capability in EF6 and even previous versions, and it was useful for mapping scalar functions as well as TVFs, although it wasn't at all easy to setup.

Impact of client evaluation

Note that it was also safer in EF6 to invoke the method through the LINQ provider as part of the implementation of the method, because there was no automatic client evaluation. In EF Core we should probably have a way to fork re-entrant calls to either evaluate in memory or throw if there is no reasonable in-memory implementation. Otherwise there are scenarios that would result into infinite recursion.

Metadata

Metadata

Assignees

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions