LINQ 数据过滤仅在调试期间有效
LINQ filtering on data only works during debug
我正在使用围绕 Entity Framework Code First 和 ASP.NET MVC 5 构建的 N 层解决方案。
我的 Artist Controller 中有以下 ActionResult。
public ActionResult Paintings(string urlfriendly)
{
var artist = _artistService.GetArtistByUrlFriendly(urlfriendly);
ArtistPaintingsVM vm = Mapper.Map<ArtistPaintingsVM>(artist);
return View(vm);
}
该服务最终到达这里:
public Artist GetArtistByUrlFriendly(string urlFriendly)
{
var artist = _context.Artists.FirstOrDefault(a => a.UrlFriendly == urlFriendly && a.Verified);
if (artist != null)
{
artist.Paintings = _context.Paintings.Where(p => p.ArtistId == artist.Id && p.Verified).ToList();
}
return artist;
}
当我单步执行该代码时,我得到了想要的结果。我得到了那位艺术家的两幅经过验证的画作。然而,当我 运行 应用程序没有调试时,我得到了艺术家的所有三幅画,即使其中一幅未经验证!
我上面的代码,应该只return画过验证,在debug下确实如此,但在正常运行ning模式下不行。
这里是数据播种代码:
public class DXIntializer : DropCreateDatabaseAlways<DXContext>
{
protected override void Seed(DXContext context)
{
try
{
var artists = new List<Artist>
{
new Artist { FName = "Salvador", LName = "Dali", ImgURL = "http://i62.tinypic.com/ss8txxn.jpg", UrlFriendly = "salvador-dali", Verified = true }
};
artists.ForEach(a => context.Artists.Add(a));
context.SaveChanges();
var paintings = new List<Painting>
{
new Painting { Title = "The Persistence of Memory", ImgUrl = "http://i62.tinypic.com/xx8tssn.jpg", ArtistId = 1, Verified = true },
new Painting { Title = "Swans Reflecting Elephants", ImgUrl = "http://i62.tinypic.com/aa9tssn.jpg", ArtistId = 1, Verified = true },
new Painting { Title = "Crucifiction", ImgUrl = "http://i62.tinypic.com/qq0tssn.jpg", ArtistId = 1, Verified = false }
};
paintings.ForEach(p => context.paintings.Add(p));
context.SaveChanges();
}
catch (DbEntityValidationException ex)
{
foreach (var validationErrors in ex.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
Trace.TraceInformation("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
}
}
}
}
}
我做错了什么?
您不应该这样设置艺术家的 属性。由于这是一个 EF 导航 属性。调试时,一旦您的代码命中服务(因为您的调试器读取 属性),它就会被加载,然后被您的 linq 语句的值覆盖。当 运行 没有调试时,更有可能的是 属性 实际上首先在映射器中读取(延迟加载)并且执行的查询是 EF 定义的查询。
如果你只在一个地方需要它,你应该像
一样只写一次
artist.Paintings.Where(p => p.Verified)
如果您在多个地方需要它,您可以为它制作一个 属性。
一个正确的方法是为您的艺术家定义一个额外的 属性。
ICollection<Painting> VerifiedPaintings
{
get
{
return this.Paintings.Where(p => p.Verified);
}
}
当您希望您的服务重新运行一个完全加载的对象时(例如,如果您在离开服务时处置您的 DbContext)。您应该将其加载到单独的列表中。
return new artistDto
{
Artist = artist ,
VerifiedPaintings = artist.Paintings.Where(p => p.Verified)
}
如果 Paintings
属性 的 Artist
class 是虚拟的,例如
public virtual List<Painting> Paintings { get; set; }
使其成为非虚拟的 (turn off lazy loading),像这样:
public List<Painting> Paintings { get; set; }
此修复应该可以让您的代码正常运行。如果它已经是非虚拟的,请公开这些实体的源代码以帮助我们理解问题。
解释 + 一些建议
使用 Entity Framework 构建的 N 层解决方案很可能涉及实体序列化。在您的情况下,用于检索数据的服务(我想是 wcf 或 web api)将序列化 Artist 对象并将其传递到 UI 层。现在,lazy loading and serialization don't mix well. 当序列化程序访问 Paintings
属性 时,由于延迟加载,它最终会加载属于艺术家的所有画作。调试时,试试这个:根本不要检查 \ 悬停在艺术家对象上,也不要将其从手表中删除,在 return artist;
语句上设置断点。按 F5,当执行暂停在 return 语句时,探索艺术家对象,您应该看到所有三幅画。在为 Paintings
属性 赋值之前,请确保没有任何东西会触发 Paintings
属性 的计算。
像这样声明导航属性是一个很好的做法:public List<Painting> Paintings { get; private set; }
这会强制使用推荐的导航属性方式。也就是说,您可以从集合中添加或删除项目,但是,您不能分配新的集合。在默认构造函数中初始化此类属性很重要,如下所示:
public class Artist
{
public List Paintings { get; private set; }
public Artist()
{
this.Paintings = new List();
}
}
- 关闭延迟加载时,必须explicitly load related entities。
在您的情况下,与其直接为 Paintings
属性 赋值,不如执行以下操作:
db.Entry<Artist>(artist)
.Collection(a => a.Paintings)
.Query()
.Where(p => p.Verified)
.Load();
它只会加载指定艺术家的经过验证的画作(延迟加载应该关闭)。
我正在使用围绕 Entity Framework Code First 和 ASP.NET MVC 5 构建的 N 层解决方案。
我的 Artist Controller 中有以下 ActionResult。
public ActionResult Paintings(string urlfriendly)
{
var artist = _artistService.GetArtistByUrlFriendly(urlfriendly);
ArtistPaintingsVM vm = Mapper.Map<ArtistPaintingsVM>(artist);
return View(vm);
}
该服务最终到达这里:
public Artist GetArtistByUrlFriendly(string urlFriendly)
{
var artist = _context.Artists.FirstOrDefault(a => a.UrlFriendly == urlFriendly && a.Verified);
if (artist != null)
{
artist.Paintings = _context.Paintings.Where(p => p.ArtistId == artist.Id && p.Verified).ToList();
}
return artist;
}
当我单步执行该代码时,我得到了想要的结果。我得到了那位艺术家的两幅经过验证的画作。然而,当我 运行 应用程序没有调试时,我得到了艺术家的所有三幅画,即使其中一幅未经验证!
我上面的代码,应该只return画过验证,在debug下确实如此,但在正常运行ning模式下不行。
这里是数据播种代码:
public class DXIntializer : DropCreateDatabaseAlways<DXContext>
{
protected override void Seed(DXContext context)
{
try
{
var artists = new List<Artist>
{
new Artist { FName = "Salvador", LName = "Dali", ImgURL = "http://i62.tinypic.com/ss8txxn.jpg", UrlFriendly = "salvador-dali", Verified = true }
};
artists.ForEach(a => context.Artists.Add(a));
context.SaveChanges();
var paintings = new List<Painting>
{
new Painting { Title = "The Persistence of Memory", ImgUrl = "http://i62.tinypic.com/xx8tssn.jpg", ArtistId = 1, Verified = true },
new Painting { Title = "Swans Reflecting Elephants", ImgUrl = "http://i62.tinypic.com/aa9tssn.jpg", ArtistId = 1, Verified = true },
new Painting { Title = "Crucifiction", ImgUrl = "http://i62.tinypic.com/qq0tssn.jpg", ArtistId = 1, Verified = false }
};
paintings.ForEach(p => context.paintings.Add(p));
context.SaveChanges();
}
catch (DbEntityValidationException ex)
{
foreach (var validationErrors in ex.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
Trace.TraceInformation("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
}
}
}
}
}
我做错了什么?
您不应该这样设置艺术家的 属性。由于这是一个 EF 导航 属性。调试时,一旦您的代码命中服务(因为您的调试器读取 属性),它就会被加载,然后被您的 linq 语句的值覆盖。当 运行 没有调试时,更有可能的是 属性 实际上首先在映射器中读取(延迟加载)并且执行的查询是 EF 定义的查询。
如果你只在一个地方需要它,你应该像
一样只写一次artist.Paintings.Where(p => p.Verified)
如果您在多个地方需要它,您可以为它制作一个 属性。 一个正确的方法是为您的艺术家定义一个额外的 属性。
ICollection<Painting> VerifiedPaintings
{
get
{
return this.Paintings.Where(p => p.Verified);
}
}
当您希望您的服务重新运行一个完全加载的对象时(例如,如果您在离开服务时处置您的 DbContext)。您应该将其加载到单独的列表中。
return new artistDto
{
Artist = artist ,
VerifiedPaintings = artist.Paintings.Where(p => p.Verified)
}
如果 Paintings
属性 的 Artist
class 是虚拟的,例如
public virtual List<Painting> Paintings { get; set; }
使其成为非虚拟的 (turn off lazy loading),像这样:
public List<Painting> Paintings { get; set; }
此修复应该可以让您的代码正常运行。如果它已经是非虚拟的,请公开这些实体的源代码以帮助我们理解问题。
解释 + 一些建议
使用 Entity Framework 构建的 N 层解决方案很可能涉及实体序列化。在您的情况下,用于检索数据的服务(我想是 wcf 或 web api)将序列化 Artist 对象并将其传递到 UI 层。现在,lazy loading and serialization don't mix well. 当序列化程序访问
Paintings
属性 时,由于延迟加载,它最终会加载属于艺术家的所有画作。调试时,试试这个:根本不要检查 \ 悬停在艺术家对象上,也不要将其从手表中删除,在return artist;
语句上设置断点。按 F5,当执行暂停在 return 语句时,探索艺术家对象,您应该看到所有三幅画。在为Paintings
属性 赋值之前,请确保没有任何东西会触发Paintings
属性 的计算。像这样声明导航属性是一个很好的做法:
public List<Painting> Paintings { get; private set; }
这会强制使用推荐的导航属性方式。也就是说,您可以从集合中添加或删除项目,但是,您不能分配新的集合。在默认构造函数中初始化此类属性很重要,如下所示:
public class Artist { public List Paintings { get; private set; } public Artist() { this.Paintings = new List(); } }
- 关闭延迟加载时,必须explicitly load related entities。
在您的情况下,与其直接为 Paintings
属性 赋值,不如执行以下操作:
db.Entry<Artist>(artist)
.Collection(a => a.Paintings)
.Query()
.Where(p => p.Verified)
.Load();
它只会加载指定艺术家的经过验证的画作(延迟加载应该关闭)。