Open DG4ever opened 11 months ago
Having a projectable property on the concrete entity type as you have in QualityDataPart is supported. Your sample code is therefore supported and when I run this:
using System;
using EntityFrameworkCore.Projectables;
public class QualityDataPartInfo { public string Type => ""Test""; }
public interface IQualityDataPart { string Type {get;} }
public class QualityDataPart : IQualityDataPart
{
public virtual QualityDataPartInfo PartInfo { get; set; } = new QualityDataPartInfo();
[Projectable] public string Type => PartInfo.Type;
}
I get the following generated companion expression:
// <auto-generated/>
#nullable disable
using System;
using EntityFrameworkCore.Projectables;
namespace EntityFrameworkCore.Projectables.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
static class _QualityDataPart_Type
{
static global::System.Linq.Expressions.Expression<global::System.Func<global::QualityDataPart, string>> Expression()
{
return (global::QualityDataPart @this) => @this.PartInfo.Type;
}
}
}
Can you share the actual generated code that is failing?
Thanks for your response. I didn't know that there will be auto generated code. Where do I find it?
Ok I have found the generated sources (maybe you should add a hint to the readme that they are visible in Dependencies > Analyzers > EntityFrameworkCore.Projectables.Generator)
Here is the auto generated code:
// <auto-generated/>
#nullable disable
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using QualityData.Types;
using EntityFrameworkCore.Projectables;
using DataContext.Modules.EntityFramework.Database.Tables;
namespace EntityFrameworkCore.Projectables.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
static class DataContext_Modules_EntityFramework_Database_Tables_QualityDataPart_Type
{
static global::System.Linq.Expressions.Expression<global::System.Func<global::DataContext.Modules.EntityFramework.Database.Tables.QualityDataPart, string>> Expression()
{
return (global::DataContext.Modules.EntityFramework.Database.Tables.QualityDataPart @this) => @this.PartInfo.Type;
}
}
}
This seems correct to me. But I think the expession from the interface is just not translated to the concrete type.
When I build the expression with the concrete type everything works as expected. But when I use the interface the auto-generated expression is not used.
//working as expected
Expression<Func<QualityDataPart, bool>> test = p => p.Type == "Test";
var testResult = await qualityDataParts.Where(test).ToListAsync();
//generated expression not used -> "Translation of member 'Type' on entity type 'QualityDataPart' failed"
Expression<Func<IQualityDataPart, bool>> test2 = p => p.Type == "Test";
var testResult2 = await qualityDataParts.Where(test2).ToListAsync();
This seems to be an issue that will hard to solve.
Expression<Func<IQualityDataPart, bool>> test = p => p.Type == "Test";
doesn't inform this library what concrete type IQualityDataPart
actually is and therefore, this library would not know where to get the generated expression from. I can see some options here:
UseMemberBody
so that we can teach an interface where the expression of that interface lives.I have not tested this with your code, but we had a similar situation with a slightly stupid - but working - solution. Maybe this helps others:
Not working (from your code):
Expression<Func<IQualityDataPart, bool>> test2 = p => p.Type == "Test";
var testResult2 = await qualityDataParts.Where(test2).ToListAsync();
Working (in theory):
public string GetData(IQualityDataPart input)
{
return input.Type
}
Expression<Func<IQualityDataPart, bool>> test2 = p => GetData(p) == "Test";
var testResult2 = await qualityDataParts.Where(test2).ToListAsync();
My understanding of this solution: The main issue with Interfaces is, that the library tries to find a class when it only got information to find an interface - which is why it fails originally. What we do, is give it a method whithout having to touch the interface. The method is outside the generated expression. So it actually knows how to deal with the interface.
I do not have a 100% grasp of this problem, so our case might be different than this. If so, I apologize. But nevertheless, this issue helped solve our problem, so thank you :D
@JodliDev This will not work. In your example, The implementation of GetData
is still a blackbox for EF and since the method is not marked as a Projectable
, there are no companion expression trees that this library can take to tell EF how it is implemented. Even if that methods was marked as a Projectable, we would still run into this particular issue as we do not know in advance the concrete type of input and therefore we can't swap out a call to Type with a concrete expression.
Hm. But I think it works if GetData()
is static, no?
Here is some code that actually works for us (notice IDataContextForIdHelper
that is "translated" by GetTable
into an IQueryable
):
namespace BlueDanubeCrmModel
{
public interface IDataContextForIdHelper
{
IQueryable<T> GetIQueryable<T>() where T : class;
}
class SlModel {
public static string BuildIdHelperString(RobotModelIdHelperData robotData, string category, string slName, string flag, int p)
{
return RobotModel.BuildIdHelperString(robotData) + BuildSlModelString(category, slName, flag, p);
}
[Projectable]
public string IdHelperJoined(IDataContextForIdHelper dataContext) =>
BuildIdHelperString(
GetTable(dataContext)
.Where(x => x.Id == RobotModelId)
.Select(x => new RobotModelIdHelperData
{
RobMan = x.RobMan,
RobMod1 = x.RobMod1,
RobMod2 = x.RobMod2,
RobMod3 = x.RobMod3
})
.FirstOrDefault(),
Category
);
}
public static IQueryable<RobotModel> GetTable(IDataContextForIdHelper dataContext)
{
return dataContext.GetIQueryable<RobotModel>();
}
}
}
This generates the following code:
namespace EntityFrameworkCore.Projectables.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
static class BlueDanubeCrmModel_SlModel_IdHelperJoined
{
static global::System.Linq.Expressions.Expression<global::System.Func<global::BlueDanubeCrmModel.SlModel, global::DataAccessNfStandard.DataExtender.IDataContextForIdHelper, string>> Expression()
{
return (global::BlueDanubeCrmModel.SlModel @this, global::DataAccessNfStandard.DataExtender.IDataContextForIdHelper dataContext) => global::BlueDanubeCrmModel.SlModel.BuildIdHelperString(global::System.Linq.Queryable.FirstOrDefault(global::System.Linq.Queryable.Select(global::System.Linq.Queryable.Where(global::BlueDanubeCrmModel.SlModel.GetTable(dataContext), x => x.Id == @this.RobotModelId), x => new global::BlueDanubeCrmModel.RobotModel.RobotModelIdHelperData { RobMan = x.RobMan, RobMod1 = x.RobMod1, RobMod2 = x.RobMod2, RobMod3 = x.RobMod3 })), @this.Category, @this.SlName, @this.Flag, @this.P);
}
}
}
Not the most pretty, and we are still testing things, but so far it seems to work and seems to produce SQL queries that do what we want
My Entity
QualityDataPart
implements an interfaceIQualityDataPart
which has a propertyType
(string). In order to save storage this text is stored in a different EntityQualityDataPartInfo
Now I want to map the property from the different Entity:The user should be able to query an expression via the exposed interface
IQualityDataPart
Hovever this will not transalte the
Type
property toPartInfo.Type
.This won't work:
This is working as expected:
Might this be a bug or am I doing something wrong?