使用Entity Framework时理解对象

3

我看到了一些奇怪的东西,它跟我的预期不太一样。

我们来看这段简单的代码,让你了解我做了什么:

MyRepository db = new MyRepository();

// let's get them all
IQueryable<TblCals> cals = db.FindAllCalendars();

// now that we have everything, let's split up
List<TblCals> masters = cals.Where(x => x.IsMaster).ToList();
List<TblCals> childs =  cals.Where(x => x.Parent > 0).ToList();

// Let's change the name of the masters
foreach(var c in masters) c.Name = String.Concat("Master: ", c.Name);

// Let's send everything to the View
return View(new model() { masters, childs });

现在,问题是:
为什么更改List masters中的名称也会更改childs中对象的名称?
当我执行List masters = cals.Where(x => x.IsMaster).ToList()时,它不应该从cals对象复制元素而不仅仅是引用它们吗?

我该怎么做来防止这种行为?

我可以简单地执行:
MyRepository db = new MyRepository();

// let's get them all
IQueryable<TblCals> cals = db.FindAllCalendars();

// now that we have everything, let's split up
List<TblCals> masters = db.FindAllCalendarsThatAreMasters().ToList();
List<TblCals> childs =  db.FindAllCalendarsThatHaveParent().ToList();

但是这将导致 3 次数据库查询,我可以先获取所有内容,然后再使用那个对象...但是如果我从中更改了任何子查询,“主”对象也会随之更改。


所有这背后的主要想法是看看是否安全进行以下操作:

public IEnumerable<JK_Users> ListAllUsers() {
    // Check / Add to memcache
    string key = "ListAllUsers";
    var users = MemcachedLayer.Get<IEnumerable<JK_Users>>(key);

    if(key == null)
        users = db.Tbl_Users.CachedQuery(key);

    return users;
}

public IEnumerable<JK_Users> FindUserById(decimal id) {
    // call ListAllUsers() instead Repository
    return this.ListAllUsers().FirstOrDefault(x => x.user_id == id);
}
public IEnumerable<JK_Users> FindUserByUsername(string username) {
    // call ListAllUsers() instead Repository
    return this.ListAllUsers().FirstOrDefault(x => x.username == username);
}

或者我必须调用

public JK_Users FindUserById(decimal id) {
    return db.Tbl_Users.FirstOrDefault(x => x.user_id == id);
}
public JK_Users FindUserByUsername(string username) {
    return db.Tbl_Users.FirstOrDefault(x => x.username == username);
}

这样我就可以更有效地使用缓存。


为什么不在视图中显示前缀“Master:”,对于IsMaster设置为true的日历呢?或者这只是一个例子? - J. Tihon
1个回答

3
不,集合仅包含引用类型的引用。这里没有任何特定于 EF 的内容:
List<StringBuilder> list1 = new List<StringBuilder>();
List<StringBuilder> list2 = new List<StringBuilder>();
StringBuilder builder = new StringBuilder();
list1.Add(builder);
list2.Add(builder);
list1[0].Append("Foo");
Console.WriteLine(list2[0]); // Still prints Foo...

实体框架确保每个“实体”仅有一个对象(在存储库对象的上下文中)。这意味着您不能进入状态,其中您已更改了表示相同实体(其中实体由其ID定义)的两个不同对象上的同一字段。这真正是合乎逻辑的方法-将涉及的值视为实体; 您的两个查询匹配某些结果的相同实体...为什么您希望这两个表示相同实体的结果成为独立对象?如果有人提到“Stack Overflow的Jon Skeet”和“英国雷丁Tilehurst的Jon Skeet”,那就是同一个人-通过一个结果更改年龄时应该在另一个结果中看到当。

那么我真的需要进行3次数据库调用才能得到我需要的东西吗?...有没有更有效率的方法呢?- 在我的情况下,这是关于获取一个既是“主”又是“父”的“Master”的想法,以极大地减少对数据库的调用。 - balexandre
@balexandre:首先,您需要知道创建IQueryable<T>并不会发出数据库调用,这与您在问题中的评论所暗示的相反。然后,您需要考虑是否真的想以您建议的方式违反实体模型。这里的实体名称是什么?听起来您应该退一步,仔细思考整体设计,而不是专注于这个特定问题。 - Jon Skeet
对不起,我忘了那个精彩的部分,IQueryable 只是一个查询,只有在这种情况下执行 ToList() 时,才是真正的数据库调用。 - balexandre
我刚刚追加了一些代码,目前正在使用。从“缓存”中填充是否安全,还是每次需要调用我的存储库?……这就是我提问时所考虑的。 - balexandre
@balexandre:我怀疑缓存不起作用,因为我希望每个实体都“知道”它从哪个上下文中加载。但是你可能可以将其与该上下文分离。说实话,这超出了我的Entity Framework知识范围 - 我建议你找一本好书或教程来学习。 - Jon Skeet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接