efcore icon indicating copy to clipboard operation
efcore copied to clipboard

Set operations: allow operands with different entity types

Open roji opened this issue 6 years ago • 10 comments
trafficstars

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 avatar Jun 27 '19 15:06 roji

@roji Will this be fixed in the 3.0 release?

snailcatcher avatar Sep 11 '19 06:09 snailcatcher

@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 avatar Sep 11 '19 08:09 roji

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 :)

mhDuke avatar Mar 24 '21 20:03 mhDuke

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.

SoftCircuits avatar Sep 09 '22 21:09 SoftCircuits

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.

ajcvickers avatar Sep 10 '22 09:09 ajcvickers

@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?

roji avatar Sep 11 '22 10:09 roji

@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!

mhDuke avatar Sep 11 '22 10:09 mhDuke

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

roji avatar Sep 11 '22 10:09 roji

@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 avatar Sep 11 '22 15:09 SoftCircuits

@roji I responded to that same question here.

SoftCircuits avatar Sep 11 '22 15:09 SoftCircuits

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();

PavelFischerCoupa avatar Mar 20 '23 08:03 PavelFischerCoupa

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.

Suchiman avatar Mar 27 '23 14:03 Suchiman