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

Set operations: allow operands with different entity types #16298

Open
Tracked by #24106
roji opened this issue Jun 27, 2019 · 12 comments
Open
Tracked by #24106

Set operations: allow operands with different entity types #16298

roji opened this issue Jun 27, 2019 · 12 comments

Comments

@roji
Copy link
Member

roji commented Jun 27, 2019

Our current set operation support requires that both siblings have the exact same entity type. Quite a lot of logic is already in place for different entity types (of the same hierarchy), but the feature is currently disabled.

The issue is that when performing a set operation over two different siblings, null projections have to be injected, as the two siblings don't have the same set of properties. In addition, since the entire set operation is cast up to return the closest common ancestor of the two siblings, we also need to inject nulls for properties of unrelated siblings, since the shaper expects them there.

We currently lack a way to represent these null constants in the projection mappings of the set operation. Project mappings typically contain ColumnExpressions, but in our case there is no subquery table alias (since union operands don't have an alias). This should no longer be an issue after #17340.

roji added a commit to roji/efcore that referenced this issue Jun 27, 2019
Disabled support for siblings of differing entity types, dotnet#16298
roji added a commit to roji/efcore that referenced this issue Jun 27, 2019
Disabled support for siblings of differing entity types, dotnet#16298
roji added a commit that referenced this issue Jun 27, 2019
Disabled support for siblings of differing entity types, #16298
roji added a commit to roji/efcore that referenced this issue Jun 27, 2019
Disabled support for siblings of differing entity types, dotnet#16298
roji added a commit that referenced this issue Jun 27, 2019
Disabled support for siblings of differing entity types, #16298
roji added a commit that referenced this issue Jun 28, 2019
Disabled support for siblings of differing entity types, #16298
@ajcvickers ajcvickers added this to the Backlog milestone Jun 28, 2019
@roji roji self-assigned this Aug 27, 2019
@snailcatcher
Copy link

@roji Will this be fixed in the 3.0 release?

@roji
Copy link
Member Author

roji commented Sep 11, 2019

@snailcatcher unfortunately no, this has been punted for post-3.0 work, but we'll definitely consider this as a high-priority item for the next release.

However, note that you can still perform any sort of set operation on the client via client evaluation - simply insert an AsEnumerable() before performing your set operation. This is how all set operations were handled in pre-3.0 versions, and depending on the exact use case client evaluation may perform just as well as server evaluation (e.g. if no filtering is applied afterwards and all results are sent to the client anyway).

roji added a commit that referenced this issue Nov 10, 2019
Added support for set operations over different entity types.

When performing a set operation, if two properties in the entity
inheritance hierarchy were mapped to the same database column, that
column was projected twice.

Closes #16298
Fixes #18832
@roji roji removed this from the Backlog milestone Nov 10, 2019
@roji roji added closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. and removed consider-for-current-release labels Nov 10, 2019
@ajcvickers ajcvickers added this to the 5.0.0 milestone Nov 11, 2019
roji added a commit to roji/efcore that referenced this issue Nov 26, 2019
Added support for set operations over different entity types.

When performing a set operation, if two properties in the entity
inheritance hierarchy were mapped to the same database column, that
column was projected twice.

Closes dotnet#16298
Fixes dotnet#18832
@roji roji removed the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Dec 2, 2019
roji added a commit to roji/efcore that referenced this issue Dec 10, 2019
Added support for set operations over different entity types.

When performing a set operation, if two properties in the entity
inheritance hierarchy were mapped to the same database column, that
column was projected twice.

Closes dotnet#16298
Fixes dotnet#18832
roji added a commit to roji/efcore that referenced this issue Dec 19, 2019
Added support for set operations over different entity types.

When performing a set operation, if two properties in the entity
inheritance hierarchy were mapped to the same database column, that
column was projected twice.

Closes dotnet#16298
Fixes dotnet#18832
@mhDuke
Copy link

mhDuke commented Mar 24, 2021

according to this issue, if i have this setup

public class Reservation
{
   public int Id {get; set;}
   public TimeSpan Length {get; set;}
}
public class Appointment : Reservation
{
   public Customer Customer {get; set;}
}

DbContext

public DbSet<Reservation> Reservations { get; set; }
public DbSet<Appointment> Appointments { get; set; }

I cannot perform a Union like this

var reservations = context.Appointments.Include(e => e.Customer)
                   .Union(context.Reservations)

except in memory via AsEnumerable()?

you might want to answer this question as well :)

@SoftCircuits
Copy link

Why has this not been considered important enough to implement before 7.0? It really makes it difficult to work with EF when this is needed. Is there a workaround that can be offered? Any comments on using Convert.ToString() to convert columns so they match string literals? That seems to work but appears it may have memory concerns.

@ajcvickers
Copy link
Contributor

This issue has 9 votes (👍). This puts it in 164th position in the list of most requested features. See the planning process for more information on how we decide what to work on. Make sure to vote for this issue if it is important to you.

@roji
Copy link
Member Author

roji commented Sep 11, 2022

@SoftCircuits

Is there a workaround that can be offered? Any comments on using Convert.ToString() to convert columns so they match string literals?

Using Convert.ToString() should be a good workaround.

That seems to work but appears it may have memory concerns.

What makes you think that, can you provide more details?

@mhDuke
Copy link

mhDuke commented Sep 11, 2022

@roji @SoftCircuits

Using Convert.ToString() should be a good workaround.

I am not sure how Convert.ToString() is linked to working with sets of different types? could you explain!

@roji
Copy link
Member Author

roji commented Sep 11, 2022

@mhDuke see #29027, which is what @SoftCircuits was referring to above.

@SoftCircuits
Copy link

@mhDuke In some cases, wrapping the column with Convert.ToString() will allow it to be unioned with a string literal. That discussion is here, although it doesn't provide many details.

@SoftCircuits
Copy link

@roji I responded to that same question here.

@PavelFischerCoupa
Copy link

PavelFischerCoupa commented Mar 20, 2023

Got same issue with owned types like:
AuditDetail owned type, Entity1 and Entity2 own it

class Entity1{
   int Id, string Name, AuditDetail AuditDetail
}

class Entity2{
   int Id, string Name, AuditDetail AuditDetail
}

class EntityView{
   int Id, string Name, AuditDetail AuditDetail
}

class AuditDetail{
  int UserId, string UserName, DateTime Timestamp
}

and the query

var entityOnes= Entities1.AsNoTracking()
        .Select(entity1 => new EntityView
        {
            Id = entity1 .Id,
            Name = entity1.Name,
            AuditDetail = entity1.AuditDetail
        });

    var entityTwoes= Entities2.AsNoTracking()
        .Select(entity2=> new EntityView
        {
            Id = entity2.PortfolioId,
            Name = entity2.Name,
            AuditDetail = entity2.AuditDetail
        });

    return entityOnes.Concat(portfolios).OrderBy(x => x.Name).Skip(100).Take(100).ToList();

@Suchiman
Copy link
Contributor

Also just run into this while migrating from EF6 to EFC, similiar construct as Pavel:
System.InvalidOperationException: Unable to translate set operation when matching columns on both sides have different store types.

var entityOnes= Entities1
        .Select(entity1 => new EntityView
        {
            Id = entity1 .Id,
            Description = entity1.Name + " / " + entity1.Street
        });

    var entityTwoes= Entities2
        .Select(entity2=> new EntityView
        {
            Id = entity2.PortfolioId,
            Description = entity2.Risk
        });

    return entityOnes.Concat(entityTwoes).ToList();

Using Convert.ToString helps to workaround that, as does adding (string)(object), however both workarounds make EF6 unhappy LINQ to Entities does not recognize the method 'System.String ToString(System.String)' method, and this method cannot be translated into a store expression. and Unable to cast the type 'System.String' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.. The reason this matters for me is that i have EF sufficiently abstracted away that i can dynamically switch between EF6 and EFC to evaluate performance, correctness and functionality. So far it looks like the only way to support both is to duplicate the queries and switch between them depending on whether EF6 or EFC is currently in use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment