linq2db / linq2db.EntityFrameworkCore

Bring power of Linq To DB to Entity Framework Core projects
MIT License
462 stars 38 forks source link

Set() a database generated field on query? #155

Closed Evengard closed 2 weeks ago

Evengard commented 3 years ago

Hello!

I have a following data model (using PgSql array data type):

public class Material
{
    public Guid MaterialId {get; set;}
    public Guid[] Linked {get; set;}
    [NotMapped]
    public Guid[] LinkedFrom {get; set;}
}

Now, let's assume we have 2 materials with ID 1 and 2. The Material 1 have Material 2 in his array Linked. Now, I need to have the Material 2 populate the LinkedFrom array (database-generated) with the ID 1 (inferred from the fact that the Material 1 have Material 2 in his Linked array). Now, EF Core does have a .HasComputedColumnSql("someSql", stored: false); for a similar case, but I want to avoid using plain SQL at all costs and instead was hoping to use Linq2Db on query-time to populate it somehow. Now, probably I could do it with something like this (tentative code):

(from m in _context.Materials
from m2 in _context.Materials.DefaultIfEmpty()
where m.MaterialId==id && Sql.Ext.PostgreSQL().ValueIsEqualToAny(m.MaterialId, m2.Linked)
group m2 by m into grp
select new Material
{
    MaterialId = grp.Key.MaterialId,
    Linked = grp.Key.Linked,
    LinkedFrom = grp.ArrayAggregate(m => m.MaterialId, Sql.AggregateModifier.Distinct).ToValue()
})

But the actual Material object is actually a lot bigger than theese 3 fields, and recreating it all by hand seems to be way too cumbersome.

I probably could just return it as new { Material = grp.Key, LinkedFrom = grp.ArrayAggregate(m => m.MaterialId, Sql.AggregateModifier.Distinct).ToValue() } and then just patching it up with a simple foreach after the query as well, but that also seems a bit of a workaround than a solution. Also I am applying a bunch of Includes on it, so that makes that cumbersome too.

Is there a way to somehow specify only one column to "patch" (a bit like .Set() for updating multiple queries, but at query-time only)?

sdanyliv commented 3 years ago

No way. You have to project all fields. With custom projection (as in your case) you can remove all Includes, they will be ignored.

Evengard commented 3 years ago

Ok, let me rephrase the question - is there a way to define a database-generated field with linq syntax with help of linq2db?

MaceWindu commented 2 weeks ago

To define computed column, ExpressionMethodAttribute attribute could be used. E.g. using fluent mapping:

var builder = new FluentMappingBuilder();

builder.Entity<Material>()
    .Property(e => e.LinkedFrom)
    .HasAttribute(new ExpressionMethodAttribute((IDataContext db, Material e)
            => db.GetTable<Material>()
                      .Where(r => Sql.Ext.PostgreSQL().ValueIsEqualToAny(e.MaterialId, r.Linked))
                      .ArrayAggregate(r => r.MaterialId, Sql.AggregateModifier.Distinct).ToValue()));

db.AddMappingSchema(builder.Build().MappingSchema);

but it is not possible to attach it only to specific queries - all non-projected entity queries will select this column. Only option to use it with specific queries is to add mapping schema configuration to contexts that execute those queries.