我应该如何禁用每个对象的 Entity Framework table 引用(外部)列表?
How should I disable Entity Framework table reference(foreign) list from each objects?
我正在使用 Sqlite 数据库 和 System.Data.SQLite 1.0.92
这里有2个table:
Table 人:
PersonId
人名
Table学生:
学号
PersonId(参考table Person FK)
学生号
现在每次我在 EF5 中获取 Persons 集合时:
using (var ctx = new myEntities)
{
AllPersons = ctx.Persons.ToList();
}
还有AllPersons.student合集会包含在结果中;
但我不需要它。当然这只是一个例子,有很多大table有很多引用,因此这里总是有性能问题。
所以我尽量不让它出现在我的结果中。所以我改变它:
using (var ctx = new myEntities)
{
ctx.Configuration.ProxyCreationEnabled = false;
ctx.Configuration.LazyLoadingEnabled = false;
AllPersons= ctx.Persons.ToList();
}
现在很好,因为 AllPersons.student
集合将永远是 null
但现在我发现:如果我把 Person 和 Student 放在一起:
using (var ctx = new myEntities)
{
ctx.Configuration.ProxyCreationEnabled = false;
ctx.Configuration.LazyLoadingEnabled = false;
AllPersons= ctx.Persons.ToList();
AllStudents = ctx.Student.ToList();
}
现在引用仍然包含在。
所以在这种情况下,有没有什么办法不要让引用包含在中?
谢谢。
更新
有朋友要求,我说明一下为什么需要:
1:当我将它转换为json时,它将是一个死循环。即使我已经使用 Json.net ReferenceLoopHandling
,json 的大小也非常大,足以让服务器崩溃。(如果没有引用,它只是一个非常小的 json)
2:每次获取客户端数据需要保存时,都会显示模型状态异常,直到我设置为null
。
示例:
using (myEntities ctx = new myEntities())
{
ctx.Configuration.LazyLoadingEnabled = false;
ctx.Configuration.ProxyCreationEnabled = false;
Person model= ThisIsAModel();
model.students = null; // This is a key, I need set the students collection references to null , otherwise it will throw exception
ctx.Entry(model).State = EntityState.Modified;
ctx.SaveChanges();
}
3:这是更重要的问题。我已经在服务器上获取了所有数据和缓存。但是它会让服务器启动时加载时间很长。 (因为资料和参考文献太多,这是主要问题),不知道又会遇到什么样的问题....
public List<Person> PersonsCache; // global cache
public List<Student> StudentsCache; // global cache
using (myEntities ctx = new myEntities())
{
ctx.Configuration.LazyLoadingEnabled = false;
ctx.Configuration.ProxyCreationEnabled = false;
// There is so many references and data, will let it very slow , when I first time get the all cache. even I only get the Person model, not other , just because some Collection has some references problem. It will very slow....
PersonsCache = ctx.Persons.ToList();
StudentsCache= ctx.Student.ToList();
}
它们会自动链接到 ObjectContext
那里 EntityKey
。根据您想对 Persons
和 Students
做什么,您可以 Detach
它们来自 ObjectContext
:
using (var ctx = new myEntities)
{
ctx.Configuration.ProxyCreationEnabled = false;
ctx.Configuration.LazyLoadingEnabled = false;
AllPersons= ctx.Persons.ToList();
foreach(var c in AllPersons)
{
ctx.Detach(c);
}
AllStudents = ctx.Student.ToList();
foreach(var c in AllStudents )
{
ctx.Detach(c);
}
}
如果实体是自动生成的,则将其复制粘贴到自己的代码中,并删除生成的关系,如子集合和外键。或者你不需要所有这些功能可能是用户轻量级框架,如 dapper
通常情况下,您的学生集合不会从数据库中填充。当您到达 属性 时,它就会填满。此外,如果您使用 ToList() 方法,那么 Entity Framework 从数据中读取数据以填充您的集合。
请检查一下。
https://msdn.microsoft.com/en-us/data/jj574232.aspx#lazy
https://msdn.microsoft.com/en-us/library/vstudio/dd456846(v=vs.100).aspx
明确地 select 您想要从数据库中 return 的内容。
使用Select new
。使用 select new
子句,您可以创建匿名类型的新对象作为查询的结果,并且不要让引用包含在内。此语法允许您构造匿名数据结构。这些是在评估时创建的(懒惰地)。像这样:
using (var ctx = new myEntities())
{
var AllPersons = ctx.People.Select(c => new {c.PersonId, c.PersonName}).ToList();
}
甚至 您也不需要再禁用延迟加载.
在运行上面的查询之后:
此查询当前使用 select new { }
分配匿名类型,这需要您使用 var
。如果你想分配一个已知类型,将它添加到你的 select 子句中:
private IEnumerable<MyClass> AllPersons;//global variable
using (var ctx = new myEntities())
{
AllPersons = ctx.People
.Select(c => new MyClass { PersonId = c.PersonId, PersonName = c.PersonName }).ToList();
}
并且:
public class MyClass
{
public string PersonId { get; set; }
public string PersonName { get; set; }
}
Is there anyway to don't let the reference include in any time in this situation?
解决这个问题的方法似乎很简单:不要映射关联。删除 Student
集合。我不能再多说了。
如果您使用的是 4.5+
,请使用 [IgnoreDataMember]
修饰任何属性
听起来你也在尝试做 table 继承,这是 EF
的一个不同问题
http://www.entityframeworktutorial.net/code-first/inheritance-strategy-in-code-first.aspx
如果我的理解正确,你只是想确保你只得到你特别要求的东西,对吗?
这在上面提到了一点,但要正确地做到这一点,你只需要 select 一个匿名类型。
var students = from s in _context.Students
select new{
StudentId,
StudentNo};
然后,当您要更新此 collection/object 时,我建议您使用 GraphDiff。 GraphDiff 确实有助于解决断开连接的实体和更新问题 (https://github.com/refactorthis/GraphDiff)
所以你的方法看起来类似于:
void UpdateStudent(Student student){
_context.UpdateGraph(student, map =>
map
.AssociatedEntity(c => c.Person));
_context.SaveChanges();
}
这样,您就可以更新对象的任何属性,无论是否断开连接,而不必担心关联。
这是假设您正确映射了您的实体,老实说,我发现将对象声明为 属性,而不仅仅是 ID,并使用映射文件来正确映射它更容易。
所以:
class Person{
int Id{get;set;}
string Name{get;set}
}
class Student{
int Id{get;set;}
string StudentNo{get;set;}
Person Person{get;set;}
public class StudentMap : EntityTypeConfiguration<Student>
{
public StudentMap()
{
// Primary Key
HasKey(t => t.Id);
// Table & Column Mappings
ToTable("Students");
Property(t => t.Id).HasColumnName("StudentId");
// Relationships
HasRequired(t => t.Person)
.HasForeignKey(d => d.PersonId);
}
}
希望这是有道理的。您不需要创建视图模型,但您绝对可以。不过,这种方式确实可以更轻松地将断开连接的项目映射回数据库。
问题
正如您所说,当您同时加载 Parent 和 Child 列表时,即使禁用了 LazyLoading,然后查看 parent.Childs 您会看到 child 项也被加载了。
var db = new YourDbContext();
db.Configuration.LazyLoadingEnabled = false;
var parentList= db.YourParentSet.ToList();
var childList= db.YourChildSet.ToList();
发生了什么事?为什么 child 包含在 parent 中?
parent 实体下的 child 是您使用 db.YourChildSet.ToList();
加载的 ;事实上 Entity Framework 再也不会为 parent 加载 childs 但由于 edmx 中 parent 和 child 之间的关系,它们被列在那里。
这会影响性能吗?
根据child只加载一次的事实,加载数据对性能没有影响。
但是为了序列化或者别的什么,我怎样才能去掉它?
您可以使用这些解决方案:
方案一:
使用 2 个不同的 YourDbContext 实例:
var db1 = new YourDbContext();
db1.Configuration.LazyLoadingEnabled = false;
var parentList= db.YourParentSet.ToList();
var db2 = new YourDbContext();
db2.Configuration.LazyLoadingEnabled = false;
var childList= db.YourChildSet.ToList();
- 现在当你查看 parent.Childs 时,里面没有 Child。
方案二:
使用 Projection 并根据您的意愿调整输出并使用它们。
var db1 = new YourDbContext();
db1.Configuration.LazyLoadingEnabled = false;
var parentList= db.YourParentSet
.Select(x=>new /*Model()*/{
Property1=x.Property1,
Property2=x.Property2, ...
}).ToList();
- 这样在序列化时就没有什么烦人的了。
- 使用自定义模型 class 是可选的,在某些情况下建议使用。
其他资源
作为使用 Entity Framework 的开发人员,强烈建议阅读这些资源:
我将重点解决您的第三个问题,因为这似乎是您最紧迫的问题。然后我会尝试对其他两个问题给出一些提示。
您应该注意两个 Entity Framework 功能:
当您将数据加载到上下文中时,Entity Framework 将尝试在对象关联的任何地方连接它们。这称为关系修正。您无法阻止 EF 这样做。因此,如果您分别加载 Persons
和 Students
,Person
的 Students
集合 将 包含学生,即使您没有t Include()
他们
默认情况下,上下文会缓存它从数据库中获取的所有数据。此外,它在其更改跟踪器中存储有关对象的元数据:它们各自属性的副本 和 所有关联。因此,通过加载许多对象,内部缓存会增加,元数据的大小也会增加。而且永远 运行 关系修复 过程变得越来越慢(尽管它可能有助于通过关闭自动更改检测来推迟它)。总而言之,上下文变得臃肿和缓慢 就像一头松垮的犀牛。
我了解到您想将每个实体的数据缓存在单独的集合中。两个简单的修改将使这更快:
- 通过单独的上下文加载每个集合来避免不可避免的关系修复
- 停止缓存(在上下文中)并通过使用
AsNoTracking
获取数据来更改跟踪。
这样做,您的代码将如下所示:
public List<Person> PersonsCache;
public List<Student> StudentsCache;
using (myEntities ctx = new myEntities())
{
ctx.Configuration.ProxyCreationEnabled = false;
PersonsCache = ctx.Persons
.AsNoTracking()
.ToList();
}
using (myEntities ctx = new myEntities())
{
ctx.Configuration.ProxyCreationEnabled = false;
StudentsCache= ctx.Student
.AsNoTracking()
.ToList();
}
关闭ProxyCreationEnabled
的原因是你会得到轻对象并且你永远不会无意中触发延迟加载(抛出上下文不再可用的异常)。
现在您将拥有互不相关且获取速度与使用 EF 一样快的缓存对象。如果这不够快,您将不得不求助于其他工具,例如 Dapper。
顺便说一下,您的第一个代码片段和问题描述...
using (var ctx = new myEntities)
{
AllPersons = ctx.Persons.ToList();
}
There is also has AllPersons.student collection will include in the result;
...建议 Entity Framework 自发地执行预先加载(学生),而无需您 Include
-ing 他们。我必须假设您的代码片段不完整。 EF 永远不会自动执行预先加载。 (除非,也许,你有一些古怪和错误的查询提供者)。
至于第一个问题,连载。您应该能够以与上面所示类似的方式解决该问题。只需加载要隔离序列化的数据并禁用代理创建。或者,正如其他人所建议的那样,序列化视图模型或匿名类型,这些模型完全包含您在那里需要的内容。
至于第二个问题,校验异常。我只能想象如果您默认初始化一个学生集合,空的 Student
个对象,就会发生这种情况。这些注定是无效的。如果不是这种情况,我建议您提出一个关于这个特定问题的新问题,显示有关所涉及的 类 和映射的详细信息。这不应该在这个问题中处理。
我有完全相同的情况。
我所做的就是在请求 Persons.ToList() 之前请求 Student.ToList()
我不必禁用延迟加载。只需要先加载引用其他 table 的 table ,然后您可以加载其他 table 并且第一个 table 结果已经在内存中并且没有得到"fixed" 包含所有参考文献。
我正在使用 Sqlite 数据库 和 System.Data.SQLite 1.0.92 这里有2个table:
Table 人:
PersonId
人名
Table学生:
学号
PersonId(参考table Person FK)
学生号
现在每次我在 EF5 中获取 Persons 集合时:
using (var ctx = new myEntities)
{
AllPersons = ctx.Persons.ToList();
}
还有AllPersons.student合集会包含在结果中;
但我不需要它。当然这只是一个例子,有很多大table有很多引用,因此这里总是有性能问题。
所以我尽量不让它出现在我的结果中。所以我改变它:
using (var ctx = new myEntities)
{
ctx.Configuration.ProxyCreationEnabled = false;
ctx.Configuration.LazyLoadingEnabled = false;
AllPersons= ctx.Persons.ToList();
}
现在很好,因为 AllPersons.student
集合将永远是 null
但现在我发现:如果我把 Person 和 Student 放在一起:
using (var ctx = new myEntities)
{
ctx.Configuration.ProxyCreationEnabled = false;
ctx.Configuration.LazyLoadingEnabled = false;
AllPersons= ctx.Persons.ToList();
AllStudents = ctx.Student.ToList();
}
现在引用仍然包含在。
所以在这种情况下,有没有什么办法不要让引用包含在中? 谢谢。
更新
有朋友要求,我说明一下为什么需要:
1:当我将它转换为json时,它将是一个死循环。即使我已经使用 Json.net ReferenceLoopHandling
,json 的大小也非常大,足以让服务器崩溃。(如果没有引用,它只是一个非常小的 json)
2:每次获取客户端数据需要保存时,都会显示模型状态异常,直到我设置为null
。
示例:
using (myEntities ctx = new myEntities())
{
ctx.Configuration.LazyLoadingEnabled = false;
ctx.Configuration.ProxyCreationEnabled = false;
Person model= ThisIsAModel();
model.students = null; // This is a key, I need set the students collection references to null , otherwise it will throw exception
ctx.Entry(model).State = EntityState.Modified;
ctx.SaveChanges();
}
3:这是更重要的问题。我已经在服务器上获取了所有数据和缓存。但是它会让服务器启动时加载时间很长。 (因为资料和参考文献太多,这是主要问题),不知道又会遇到什么样的问题....
public List<Person> PersonsCache; // global cache
public List<Student> StudentsCache; // global cache
using (myEntities ctx = new myEntities())
{
ctx.Configuration.LazyLoadingEnabled = false;
ctx.Configuration.ProxyCreationEnabled = false;
// There is so many references and data, will let it very slow , when I first time get the all cache. even I only get the Person model, not other , just because some Collection has some references problem. It will very slow....
PersonsCache = ctx.Persons.ToList();
StudentsCache= ctx.Student.ToList();
}
它们会自动链接到 ObjectContext
那里 EntityKey
。根据您想对 Persons
和 Students
做什么,您可以 Detach
它们来自 ObjectContext
:
using (var ctx = new myEntities)
{
ctx.Configuration.ProxyCreationEnabled = false;
ctx.Configuration.LazyLoadingEnabled = false;
AllPersons= ctx.Persons.ToList();
foreach(var c in AllPersons)
{
ctx.Detach(c);
}
AllStudents = ctx.Student.ToList();
foreach(var c in AllStudents )
{
ctx.Detach(c);
}
}
如果实体是自动生成的,则将其复制粘贴到自己的代码中,并删除生成的关系,如子集合和外键。或者你不需要所有这些功能可能是用户轻量级框架,如 dapper
通常情况下,您的学生集合不会从数据库中填充。当您到达 属性 时,它就会填满。此外,如果您使用 ToList() 方法,那么 Entity Framework 从数据中读取数据以填充您的集合。
请检查一下。 https://msdn.microsoft.com/en-us/data/jj574232.aspx#lazy https://msdn.microsoft.com/en-us/library/vstudio/dd456846(v=vs.100).aspx
明确地 select 您想要从数据库中 return 的内容。
使用Select new
。使用 select new
子句,您可以创建匿名类型的新对象作为查询的结果,并且不要让引用包含在内。此语法允许您构造匿名数据结构。这些是在评估时创建的(懒惰地)。像这样:
using (var ctx = new myEntities())
{
var AllPersons = ctx.People.Select(c => new {c.PersonId, c.PersonName}).ToList();
}
甚至 您也不需要再禁用延迟加载.
在运行上面的查询之后:
此查询当前使用 select new { }
分配匿名类型,这需要您使用 var
。如果你想分配一个已知类型,将它添加到你的 select 子句中:
private IEnumerable<MyClass> AllPersons;//global variable
using (var ctx = new myEntities())
{
AllPersons = ctx.People
.Select(c => new MyClass { PersonId = c.PersonId, PersonName = c.PersonName }).ToList();
}
并且:
public class MyClass
{
public string PersonId { get; set; }
public string PersonName { get; set; }
}
Is there anyway to don't let the reference include in any time in this situation?
解决这个问题的方法似乎很简单:不要映射关联。删除 Student
集合。我不能再多说了。
如果您使用的是 4.5+
,请使用[IgnoreDataMember]
修饰任何属性
听起来你也在尝试做 table 继承,这是 EF
的一个不同问题http://www.entityframeworktutorial.net/code-first/inheritance-strategy-in-code-first.aspx
如果我的理解正确,你只是想确保你只得到你特别要求的东西,对吗?
这在上面提到了一点,但要正确地做到这一点,你只需要 select 一个匿名类型。
var students = from s in _context.Students
select new{
StudentId,
StudentNo};
然后,当您要更新此 collection/object 时,我建议您使用 GraphDiff。 GraphDiff 确实有助于解决断开连接的实体和更新问题 (https://github.com/refactorthis/GraphDiff)
所以你的方法看起来类似于:
void UpdateStudent(Student student){
_context.UpdateGraph(student, map =>
map
.AssociatedEntity(c => c.Person));
_context.SaveChanges();
}
这样,您就可以更新对象的任何属性,无论是否断开连接,而不必担心关联。
这是假设您正确映射了您的实体,老实说,我发现将对象声明为 属性,而不仅仅是 ID,并使用映射文件来正确映射它更容易。
所以:
class Person{
int Id{get;set;}
string Name{get;set}
}
class Student{
int Id{get;set;}
string StudentNo{get;set;}
Person Person{get;set;}
public class StudentMap : EntityTypeConfiguration<Student>
{
public StudentMap()
{
// Primary Key
HasKey(t => t.Id);
// Table & Column Mappings
ToTable("Students");
Property(t => t.Id).HasColumnName("StudentId");
// Relationships
HasRequired(t => t.Person)
.HasForeignKey(d => d.PersonId);
}
}
希望这是有道理的。您不需要创建视图模型,但您绝对可以。不过,这种方式确实可以更轻松地将断开连接的项目映射回数据库。
问题
正如您所说,当您同时加载 Parent 和 Child 列表时,即使禁用了 LazyLoading,然后查看 parent.Childs 您会看到 child 项也被加载了。
var db = new YourDbContext();
db.Configuration.LazyLoadingEnabled = false;
var parentList= db.YourParentSet.ToList();
var childList= db.YourChildSet.ToList();
发生了什么事?为什么 child 包含在 parent 中?
parent 实体下的 child 是您使用 db.YourChildSet.ToList();
加载的 ;事实上 Entity Framework 再也不会为 parent 加载 childs 但由于 edmx 中 parent 和 child 之间的关系,它们被列在那里。
这会影响性能吗?
根据child只加载一次的事实,加载数据对性能没有影响。
但是为了序列化或者别的什么,我怎样才能去掉它?
您可以使用这些解决方案:
方案一:
使用 2 个不同的 YourDbContext 实例:
var db1 = new YourDbContext();
db1.Configuration.LazyLoadingEnabled = false;
var parentList= db.YourParentSet.ToList();
var db2 = new YourDbContext();
db2.Configuration.LazyLoadingEnabled = false;
var childList= db.YourChildSet.ToList();
- 现在当你查看 parent.Childs 时,里面没有 Child。
方案二:
使用 Projection 并根据您的意愿调整输出并使用它们。
var db1 = new YourDbContext();
db1.Configuration.LazyLoadingEnabled = false;
var parentList= db.YourParentSet
.Select(x=>new /*Model()*/{
Property1=x.Property1,
Property2=x.Property2, ...
}).ToList();
- 这样在序列化时就没有什么烦人的了。
- 使用自定义模型 class 是可选的,在某些情况下建议使用。
其他资源
作为使用 Entity Framework 的开发人员,强烈建议阅读这些资源:
我将重点解决您的第三个问题,因为这似乎是您最紧迫的问题。然后我会尝试对其他两个问题给出一些提示。
您应该注意两个 Entity Framework 功能:
当您将数据加载到上下文中时,Entity Framework 将尝试在对象关联的任何地方连接它们。这称为关系修正。您无法阻止 EF 这样做。因此,如果您分别加载
Persons
和Students
,Person
的Students
集合 将 包含学生,即使您没有tInclude()
他们默认情况下,上下文会缓存它从数据库中获取的所有数据。此外,它在其更改跟踪器中存储有关对象的元数据:它们各自属性的副本 和 所有关联。因此,通过加载许多对象,内部缓存会增加,元数据的大小也会增加。而且永远 运行 关系修复 过程变得越来越慢(尽管它可能有助于通过关闭自动更改检测来推迟它)。总而言之,上下文变得臃肿和缓慢 就像一头松垮的犀牛。
我了解到您想将每个实体的数据缓存在单独的集合中。两个简单的修改将使这更快:
- 通过单独的上下文加载每个集合来避免不可避免的关系修复
- 停止缓存(在上下文中)并通过使用
AsNoTracking
获取数据来更改跟踪。
这样做,您的代码将如下所示:
public List<Person> PersonsCache;
public List<Student> StudentsCache;
using (myEntities ctx = new myEntities())
{
ctx.Configuration.ProxyCreationEnabled = false;
PersonsCache = ctx.Persons
.AsNoTracking()
.ToList();
}
using (myEntities ctx = new myEntities())
{
ctx.Configuration.ProxyCreationEnabled = false;
StudentsCache= ctx.Student
.AsNoTracking()
.ToList();
}
关闭ProxyCreationEnabled
的原因是你会得到轻对象并且你永远不会无意中触发延迟加载(抛出上下文不再可用的异常)。
现在您将拥有互不相关且获取速度与使用 EF 一样快的缓存对象。如果这不够快,您将不得不求助于其他工具,例如 Dapper。
顺便说一下,您的第一个代码片段和问题描述...
using (var ctx = new myEntities) { AllPersons = ctx.Persons.ToList(); }
There is also has AllPersons.student collection will include in the result;
...建议 Entity Framework 自发地执行预先加载(学生),而无需您 Include
-ing 他们。我必须假设您的代码片段不完整。 EF 永远不会自动执行预先加载。 (除非,也许,你有一些古怪和错误的查询提供者)。
至于第一个问题,连载。您应该能够以与上面所示类似的方式解决该问题。只需加载要隔离序列化的数据并禁用代理创建。或者,正如其他人所建议的那样,序列化视图模型或匿名类型,这些模型完全包含您在那里需要的内容。
至于第二个问题,校验异常。我只能想象如果您默认初始化一个学生集合,空的 Student
个对象,就会发生这种情况。这些注定是无效的。如果不是这种情况,我建议您提出一个关于这个特定问题的新问题,显示有关所涉及的 类 和映射的详细信息。这不应该在这个问题中处理。
我有完全相同的情况。 我所做的就是在请求 Persons.ToList() 之前请求 Student.ToList() 我不必禁用延迟加载。只需要先加载引用其他 table 的 table ,然后您可以加载其他 table 并且第一个 table 结果已经在内存中并且没有得到"fixed" 包含所有参考文献。