Reputation: 44600
I am trying to implement LINQ query to Entity Framework with union. Let's say I have 2 tables: Users and TempUsers. I try to:
var users = context.Users
.Select(u => new UserModel
{
Name = u.Name,
Roles = u.Roles.Select(r => r.Id)
})
.Union(context.TempUsers
.Select(u => new UserModel
{
Name = u.Name,
Roles = null
}))
.ToList();
Model class:
public class UserModel
{
public string Name { get; set; }
public IEnumerable<int> Roles { get; set; }
}
I am getting NotSupportedException
with message:
Unable to create a null constant value of type 'System.Collections.Generic.IEnumerable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]'. Only entity types, enumeration types or primitive types are supported in this context.
If I remove Roles = null
, I get NotSupportedException
with message:
The type 'UserModel' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.
An ideas how to fix it? Thanks!
Upvotes: 1
Views: 2229
Reputation: 205599
The first exception cannot be avoided, but the second can with the following trick.
Since EF complains that you initialize one and the same type differently, you can create and initialize a different type derived from the original one.
For instance:
public class TempUserModel : UserModel { }
and then
var users = context.Users
.Select(u => new UserModel
{
Name = u.Name,
Roles = u.Roles.Select(r => r.Id)
})
.Union(context.TempUsers
.Select(u => new TempUserModel
{
Name = u.Name
}))
.ToList();
UPDATE: The above trick works for single value properties but not for collection type properties. And more importantly, it turns out that Concat
/ Union
with nested subqueries are not supported at all. So the only way to do that in SQL is to perform the Concat
(which btw is the equivalent of the SQL UNION ALL
) master tables first, and then do conditional joins, but I'm not sure it does worth the effort. Better use some of the mixed approaches from the currently accepted answer.
Upvotes: 0
Reputation: 39326
To avoid those exceptions, you can do the union in memory calling AsEnumerable
extension method:
var users = context.Users
.Select(u => new UserModel
{
Name = u.Name,
Roles = u.Roles.Select(r => r.Id)
})
.AsEnumerable() //Add this
.Union(context.TempUsers
.Select(u => new UserModel
{
Name = u.Name,
}))
.ToList();
The problem is your are trying to project with a DTO that have a complex type as a property. That doesn't let you assign a default value because EF only supports entity types, enumeration types or primitive types when you try to project a query. Also not worth use an anonymous type because both projections must be based in an anonymous type with the same property names, so, at the end is the same problem.
Now that I saw a solution using Inheritance, this idea came to my mind.
public TempUser
{
public string Name { get; set; }
}
public UserModel:TempUser
{
public IEnumerable<int> Roles { get; set; }
}
Then to try execute union in your server side, you will also need to call OfType
extension method
var query=context.TempUsers
.Select(u => new TempUser
{
Name = u.Name,
})
.Union(context.Users
.Select(u => new UserModel
{
Name = u.Name,
Roles = u.Roles.Select(r => r.Id)
}).OfType<TempUser>())
.ToList();
The downside of this solution if the result will be a List<TempUser>
Upvotes: 2