问题描述
我正在使用一个使用实体框架的ASP.NET MVC项目。我需要将从数据库拉出的值设置为 PropertyValue
类型,如下所示:
public class PropertyValue {
public string StringValue {get;组; }
public bool? BoolValue {get;组; }
public int? IntValue {get;组; }
}
大多数情况下,这很简单。要使用First Name=John和Is Archived= true过滤所有用户,可以执行以下操作:
usersQuery
.Where(u =>
new PropertyValue {
StringValue = u.FirstName,
BoolValue = null,
IntValue = null
} .StringValue ==John)
.Where(u =>
new PropertyValue {
StringValue = null,
BoolValue = u.IsArchived,
IntValue = null
} .BoolValue == true);
显然这是一个荒谬的查询,但是我正在根据用户逐个构建这些查询输入。这些查询需要可组合,这就是为什么我必须将 PropertyValue
的所有未使用的属性显式设置为null,而且我必须以相同的顺序设置它们。如果我不这样做,我会从实体框架中收到一个错误,表示PropertyValue存在于查询中的两个结构不兼容的初始化中。
这不是问题,我可以确保所有属性都被显式设置为null。当我必须向我的 PropertyValue
类型添加另一个属性时,会出现此问题,以便能够检索ID列表(在我的情况下,检索用户的所有选定角色)即Admin,Guardian,Teacher,Student)
我修改了我的 PropertyValue
class可以存储一个Guid列表:
public class PropertyValue {
public string StringValue {get;组; }
public bool? BoolValue {get;组; }
public int? IntValue {get;组; }
public IEnumerable< Guid> GuidValues {get;组;
}
我现在可以查询如下所示的用户角色:
usersQuery
.Select(u => new PropertyValue {
StringValue = null,
BoolValue = null,
IntValue = null,
GuidValues = u.UserRoles.Select(ur => ur.Role_ID)
});
这很好。现在拉起名字现在如下所示:
usersQuery
.Select(u => new PropertyValue {
StringValue = u.FirstName,
BoolValue = null,
IntValue = null,
GuidValues = null
});
但是我收到以下错误:
所以我不能在EF中创建一个null枚举。但是我不能忽略该属性的初始化,否则我得到结构上不兼容的错误。我已经尝试过 GuidValues = Enumerable.Empty< Guid>()
,但是我也不能在EF上下文中执行。
如何解决这个问题,在EF投影中创建一个空或空的枚举?
p>我不太清楚用例。但是一般来说,我同意这是令人讨厌的EF限制,特别是结构不兼容的初始化要求背后的原因,这迫使我们使用一个丑陋的解决方法,我将提出你的建议。
很快,除了不包括分配到投影之外,没有办法解决 null枚举分配。下一步是试图解决结构不兼容的初始化问题。在大多数情况下,除了 Concat
/ Union
从定义一个子类开始:
public class StringPropertyValue: PropertyValue {}
public class BoolPropertyValue:PropertyValue {}
public class IntPropertyValue:PropertyValue {}
public class GuidsPropertyValue:PropertyValue {}
名称并不重要,因为这些类没有特殊的约束,唯一重要的是它们是不同的。我们将使用这些类投影而不是 PropertyValue
。
然后我们需要另一个简单的类:
public class Property
{
public PropertyValue Value {get;组;
}
需要解决另一个EF限制 - 不支持的 casts 。为了计划一个 PropertyValue
,而不是不支持
(PropertyValue)新的StringPropertyValue {StringValue = ..}
我们将使用丑陋但支持的
new Property {Value = new StringPropertyValue {StringValue = ...}} .Value
就是这样。
以下是您的示例的等效工作版本:
(1)
usersQuery
.Where(u => new Property {Value = new StringPropertyValue {
StringValue = u.FirstName
}}。Value.StringValue ==John)
.Where(u => new Property {Value = new BoolPropertyValue {
BoolValue = u.IsArchived
}} Value.BoolValue == true);
(2)
usersQuery
.Select(u => new Property {Value = new GuidsPropertyValue {
GuidValues = u.UserRoles.Select(ur => ur.Role_ID)
}}。值);
(3)
usersQuery
.Select(u => new Property {Value = new StringPropertyValue {
StringValue = u.FirstName
}}。
I am working on an ASP.NET MVC project which uses Entity Framework.
I need to project values pulled from the database into a PropertyValue
type, which looks like the following:
public class PropertyValue {
public string StringValue { get; set; }
public bool? BoolValue { get; set; }
public int? IntValue { get; set; }
}
Most of the time, this is easy enough. To filter all users with "First Name" = "John" and "Is Archived" = true, I could do:
usersQuery
.Where(u =>
new PropertyValue {
StringValue = u.FirstName,
BoolValue = null,
IntValue = null
}.StringValue == "John")
.Where(u =>
new PropertyValue {
StringValue = null,
BoolValue = u.IsArchived,
IntValue = null
}.BoolValue == true);
Obviously this is a ridiculous looking query, but I'm constructing these queries piece by piece based on user input. These queries need to be combinable, which is why I have to explicitly set all the unused properties of PropertyValue
to null, and I have to set them all in the same order. If I don't do this, I'll get an error from Entity Framework saying that PropertyValue exists in two structurally incompatible initializations within the query.
This isn't a problem, I can just make sure all the properties are explicitly set to null. The problem arises when I have to add another property to my PropertyValue
type, to be able to retrieve a list of IDs (in my case, retrieving all selected roles for the user, i.e. "Admin", "Guardian", "Teacher", "Student")
I've modified my PropertyValue
class to be able to store a list of Guids:
public class PropertyValue {
public string StringValue { get; set; }
public bool? BoolValue { get; set; }
public int? IntValue { get; set; }
public IEnumerable<Guid> GuidValues { get; set; }
}
I can now query for user roles like so:
usersQuery
.Select(u => new PropertyValue {
StringValue = null,
BoolValue = null,
IntValue = null,
GuidValues = u.UserRoles.Select(ur => ur.Role_ID)
});
That works great. Pulling first names would now look like this:
usersQuery
.Select(u => new PropertyValue {
StringValue = u.FirstName,
BoolValue = null,
IntValue = null,
GuidValues = null
});
But I get the following error:
So I can't create a null enumerable within EF. But I can't just ignore the initialization of the property, or else I get the structurally incompatible error. I've tried doing GuidValues = Enumerable.Empty<Guid>()
instead, but I can't do that in an EF context either.
How can I get around this issue and create a null or empty enumerable within an EF projection?
I'm not quite sure about the use case. But speaking generally, I agree that it's annoying EF limitation, especially the reasoning behind the structurally incompatible initializations requirement, which forces us to use an ugly workaround I'm going to propose you.
Shortly, there is no way to workaround the null enumerable assignment other than not including the assignment into projection. The next is an attempt to workaround the structurally incompatible initializations problem. It works in most cases except in Concat
/ Union
scenarios.
Start by defining a couple subclasses:
public class StringPropertyValue : PropertyValue { }
public class BoolPropertyValue : PropertyValue { }
public class IntPropertyValue : PropertyValue { }
public class GuidsPropertyValue : PropertyValue { }
The names does not really matter because no special constraints are put on those classes, the only important thing is they to be different. We'll use those classes for projection instead of PropertyValue
.
Then we'll need another simple class like this:
public class Property
{
public PropertyValue Value { get; set; }
}
It's needed to workaround another EF limitation - unsupported casts. In order to project a PropertyValue
, instead of unsupported
(PropertyValue)new StringPropertyValue { StringValue = .. }
we will perform the cast by using the ugly but supported
new Property { Value = new StringPropertyValue { StringValue = ... } }.Value
And that's it.
Here are the equivalent working versions of your examples:
(1)
usersQuery
.Where(u => new Property { Value = new StringPropertyValue {
StringValue = u.FirstName
}}.Value.StringValue == "John")
.Where(u => new Property { Value = new BoolPropertyValue {
BoolValue = u.IsArchived
}}.Value.BoolValue == true);
(2)
usersQuery
.Select(u => new Property { Value = new GuidsPropertyValue {
GuidValues = u.UserRoles.Select(ur => ur.Role_ID)
}}.Value);
(3)
usersQuery
.Select(u => new Property { Value = new StringPropertyValue {
StringValue = u.FirstName
}}.Value);
这篇关于在Linq to Entities查询中创建null枚举的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!