Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collection Navigation Property is not Loading #27226

Closed
JohnFasc opened this issue Jan 20, 2022 · 2 comments
Closed

Collection Navigation Property is not Loading #27226

JohnFasc opened this issue Jan 20, 2022 · 2 comments

Comments

@JohnFasc
Copy link

I have the problem, that my navigation property "PaymetnConditions" is always empty. I tried different solutions I also tried to solve the problem with the moduleBuilder but nothing seems to work.

Probably it has something to do with the table prefix we set on startup in the context class.

foreach (IMutableEntityType entity in modelBuilder.Model.GetEntityTypes()) { entity.SetTableName(TablePrefix + "_" + entity.GetTableName()); }

[Table("Receipt")]
public class ReceiptEntity : ReportEntity
{
    [Column(TypeName = "nvarchar(50)")]
    public List<PositionEntity> Positions { get; set; }
    public CustomerEntity Customer { get; set; }
    public List<AddressEntity> Addresses { get; set; }
    public string ExternalSystemId { get; set; }
    public string ExternalSystemDescription { get; set; }
    public string ReceiptNumber { get; set; }
    public Guid? ClerkId { get; set; }
    public Guid? ProjectManagerId { get; set; }
    public string Description { get; set; }
    public string Note { get; set; }
    public string DocumentGuid { get; set; }
    public string ObjectType { get; set; }
    public string ObjectRef { get; set; }
    public DateTime ReceiptDate { get; set; }
    public DateTime? DocumentDate { get; set; }
    public DateTime? OrderDate { get; set; }
    public Guid? CanceledReceiptGuid { get; set; }
    public Guid? FromReceiptGuid { get; set; }
    public DateTime? DeliveryDate { get; set; }
    public string DeliveryType { get; set; }
    [Column(TypeName = "nvarchar(50)")]
    public ReceiptTypeEnum ReceiptType { get; set; }
    [Column(TypeName = "nvarchar(50)")]
    public ReceiptStateEnum ReceiptState { get; set; }
    public bool Paid { get; set; }
    public string Period { get; set; }
    
    public List<PaymentConditionEntity> PaymetnConditions { get; set; }
}



[Table("PaymentCondition")]
public class PaymentConditionEntity :BaseEntity
{
    public double Days { get; set; }
    public decimal Value { get; set; }

    [Column(TypeName = "nvarchar(50)")]
    public PaymentConditionTypeEnum PaymentConditionType { get; set; }

    [Column(TypeName = "nvarchar(50)")]
    public PaymentConditionStateEnum PaymentConditionState { get; set; }

    [ForeignKey("ReceiptGuid")]
    public ReceiptEntity Receipt { get; set; }
    public Guid ReceiptGuid { get; set; }
}

modelBuilder.Entity()
.HasMany(nameof(ReceiptEntity.PaymetnConditions))
.WithOne();

The call for getting the data from the context

context.Receipts
.Include(receipt => receipt.PaymetnConditions)
.Where(receipt => receipt.ReceiptState != ReceiptStateEnum.Deleted)
.Where(receipt => receipt.Paid == false)
.Where(receipt => receipt.ReceiptType == ReceiptTypeEnum.Deposit ||
receipt.ReceiptType == ReceiptTypeEnum.PartialInvoice ||
receipt.ReceiptType == ReceiptTypeEnum.Invoice ||
receipt.ReceiptType == ReceiptTypeEnum.FinalInvoice)
.Where(receipt => receipt.ReceiptState == ReceiptStateEnum.Locked).ToList();

@roji
Copy link
Member

roji commented Jan 20, 2022

@JohnFasc please submit a minimal, runnable code sample - the above doesn't provide us enough to investigate. Assuming you have PaymetnConditions rows in your database corresponding to your ReceiptEntity, these should get loaded; the table prefix shouldn't have anything to do with it.

@JohnFasc
Copy link
Author

I tried now to create an Sample Project. I copied all files into a new project and and connect it to the same database. Now the PaymentConditions get loaded. I tried the same code in the other project, with no result.

I think I have to figure out, where the differences could be.

@JohnFasc JohnFasc reopened this Jan 21, 2022
smitpatel added a commit that referenced this issue May 2, 2022
Design:
- Introduce `SqlEnumerableExpression` - a holder class which indicates the `SqlExpression` is in form of a enumerable (or group) coming as a result of whole table selection or a grouping element. It also stores details about if `Distinct` is applied over grouping or if there are any orderings.
- Due to above `DistinctExpression` has been removed. The token while used to denote `Distinct` over grouping element were not valid in other parts of SQL tree hence it makes more sense to combine it with `SqlEnumerableExpression`.
- To support dual pass, `GroupByShaperExpression` contains 2 forms of grouping element. One element selector form which correlates directly with the parent grouped query, second subquery form which correlates to parent grouped query through a correlation predicate. Element selector is first used to translate aggregation. If that fails we use subquery form to translate as a subquery. Due to 2 forms of same component, GroupByShaperExpression disallows calling into VisitChildren method, any visitor which is visiting a tree containing GroupByShaperExpression (which appears only in `QueryExpression.ShaperExpression` or LINQ expression after remapping but before translation) must intercept the tree and either ignore or process it appropriately.
- An internal visitor (`GroupByAggregateChainProcessor`) inside SqlTranslator visits and process chain of queryable operations on a grouping element before aggregate is called and condense it into `SqlEnumerableExpression` which is then passed to method which translates aggregate. This visitor only processes Where/Distinct/Select for now. Future PR will add processing for OrderBy/ThenBy(Descending) operations to generate orderings.
- Side-effect above is that joins expanded over the grouping element (due to navigations used on aggregate chain), doesn't translate to aggregate anymore since we need to translate the join on parent query, remove the translated join if the chain didn't end in aggregate and also de-dupe same joins. Filing issue to improve this in future. Due to fragile nature of matching to lift the join, we shouldn't try to lift joins.
- To support custom aggregate operations, we will either reused `IMethodCallTranslator` or create a parallel structure for aggregate methods and call into it from SqlTranslator by passing translated SqlEnumerableExpression as appropriate.
- For complex grouping key, we cause a pushdown so that we can reference the grouping key through columns only. This allows us to reference the grouping key in correlation predicate for subquery without generating invalid SQL in many cases.
- With complex grouping key converting to columns, now we are able to correctly generate identifiers for grouping queries which makes more queries with correlated collections (where either parent or inner both queries can be groupby query) translatable.

Resolves #27132
Introduce a pass to translate grouping element chain ending in aggregate before translating it as a subquery.

Resolves #27226
Resolves #27433
Cause a pushdown when grouping key is complex so that SQL parser doesn't throw error that ungrouped columns are being referenced.

Relates to #22957
@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants