Closed xyh20180101 closed 2 hours ago
@xyh20180101 You should be able to manually track the entities that are returned from the query. For example, on your DbContext:
public T TrackEntity<T>(T entity)
{
Entry(entity!).State = EntityState.Unchanged;
return entity;
}
Then the query would be:
var blogs = await context.Blogs
.Where(blog => blog.Title != null)
.Select(blog => context.TrackEntity(new Blog { Id = blog.Id, Title = blog.Title }))
.ToListAsync();
Full POC:
using (var context = new AppDbContext())
{
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();
context.AddRange(
new Blog { Title = "One" },
new Blog { Title = "Two" });
await context.SaveChangesAsync();
}
using (var context = new AppDbContext())
{
var blogs = await context.Blogs
.Where(blog => blog.Title != null)
.Select(blog => context.TrackEntity(new Blog { Id = blog.Id, Title = blog.Title }))
.ToListAsync();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
}
public class AppDbContext : DbContext
{
public DbSet<Blog> Blogs => Set<Blog>();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer("Data Source=localhost;Database=BuildBlogs;Integrated Security=True;Trust Server Certificate=True;ConnectRetryCount=0")
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging();
public T TrackEntity<T>(T entity)
{
Entry(entity!).State = EntityState.Unchanged;
return entity;
}
}
public class Blog
{
public int Id { get; set; }
public string? Title { get; set; }
}
@ajcvickers Thank you very much, your code runs fine, but there's still an issue. When replacing ToListAsync()
with SingleAsync(p=>p.Title == "One")
, it throws an exception:
System.InvalidOperationException:“The LINQ expression 'DbSet<Blog>()
.Where(b => b.Title != null)
.Sum(b => __context_0.TrackEntity<Blog>(new Blog{
Id = b.Id,
Title = b.Title
}
).Id)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.”
After testing, not only the Single
method, but also Where
, OrderBy
, Sum
, and so on, all lead to exceptions. It seems to be because of calling custom methods in the expression, and I'm not sure how to proceed.
@xyh20180101 You can only track after the entity instance has been created and returned. Do, for Single
, etc. that would be after Single()
has returned. Also, make sure that entities really should be tracked--for example, performing a Sum on the database does not usually return entities to track.
(Please note that the following content is generated by a translator and may contain inaccuracies.)
Background
I am developing a multi-tenant data permission framework. By inheriting from QueryCompiler and overriding the Execute/ExecuteAsync methods, I insert Where and Select methods into the expression (for filtering data rows and columns). For example:
Expression written by the user:
Expression after being processed by the framework:
Problem
The framework works well in query scenarios, but there are issues in modification scenarios. Because I inserted the Select method, the returned entities will lose tracking. It looks like this:
Currently, I bypass the framework through extension methods (like
context.Blogs.AsNoDataPermission()
), but this requires users to write additional code. I hope to provide a non-intrusive framework as much as possible. Is there any way to manually add tracking for new entity objects in an expression? Any suggestions would be greatly appreciated.