这纯粹是为了提高我的技能。我的解决方案可以很好地完成主要任务,但并非“整洁”。我目前正在研究带有实体框架项目的.NET MVC。我只知道多年来已经足够的基本奇异LINQ函数。现在,我想学习如何看待。
所以我有两个模型
public class Server
{
[Key]
public int Id { get; set; }
public string InstanceCode { get; set; }
public string ServerName { get; set; }
}
public class Users
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int ServerId { get; set; } //foreign key relationship
}
在我的一个视图模型中,我被要求提供一个下拉列表,用于在创建新用户时选择服务器。下拉列表中填充文本和值ID作为IEnumerable 这是我的服务器下拉列表的原始属性
public IEnumerable<SelectListItem> ServerItems
{
get { Servers.ToList().Select(s => new selectListItem { Value = x.Id.ToString(), Text = $"{s.InstanceCode}@{s.ServerName}" }); }
}
有关需求的更新,现在我需要显示与每个服务器选择相关的用户数。好的,没问题。这就是我从头顶上写下的内容。
public IEnumerable<SelectListItem> ServerItems
{
get
{
var items = new List<SelectListItem>();
Servers.ToList().ForEach(x => {
var count = Users.ToList().Where(t => t.ServerId == x.Id).Count();
items.Add(new SelectListItem { Value = x.Id.ToString(), Text = $"{x.InstanceCode}@{x.ServerName} ({count} users on)" });
});
return items;
}
}
这使我的结果说“ localhost @ rvrmt1u(8个用户)”,仅此而已。 如果我想按用户数对该下拉列表进行排序怎么办。我要做的就是字符串中的另一个变量。
TLDR ...我确信某个地方的人可以教我一两个关于将其转换为LINQ查询并使它看起来更好的东西。另外,由于知道如何对列表进行排序以首先显示具有最多用户的服务器,所以可以得到加分。
答案 0 :(得分:16)
好的,我们有这样的麻烦:
var items = new List<SelectListItem>();
Servers.ToList().ForEach(x => {
var count = Users.ToList().Where(t => t.ServerId == x.Id).Count();
items.Add(new SelectListItem { Value = x.Id.ToString(), Text = $"{x.InstanceCode}@{x.ServerName} ({count} users on)" });
});
return items;
进行一系列小的,仔细的,显然正确的重构,以逐步改进代码。。
开始于:让我们将那些复杂的操作抽象为它们自己的方法。
请注意,我已将无用的x
替换为有帮助的server
。
int UserCount(Server server) =>
Users.ToList().Where(t => t.ServerId == server.Id).Count();
为什么地球上ToList
上有Users
?看起来不对。
int UserCount(Server server) =>
Users.Where(t => t.ServerId == server.Id).Count();
我们注意到有一个内置的方法可以同时执行这两个操作:
int UserCount(Server server) =>
Users.Count(t => t.ServerId == server.Id);
同样,用于创建项目:
SelectListItem CreateItem(Server server, int count) =>
new SelectListItem
{
Value = server.Id.ToString(),
Text = $"{server.InstanceCode}@{server.ServerName} ({count} users on)"
};
现在我们的财产为:
var items = new List<SelectListItem>();
Servers.ToList().ForEach(server =>
{
var count = UserCount(server);
items.Add(CreateItem(server, count);
});
return items;
好多了。
如果您只是要传递lambda正文,请不要使用ForEach
作为方法!语言中已经有一个内置的机制可以做得更好!您只需写items.Foreach(item => {...});
就没有理由写foreach(var item in items) { ... }
。它更易于理解和调试,并且编译器可以更好地对其进行优化。
var items = new List<SelectListItem>();
foreach (var server in Servers.ToList())
{
var count = UserCount(server);
items.Add(CreateItem(server, count);
}
return items;
好多了。
为什么ToList
上有Servers
?完全没有必要!
var items = new List<SelectListItem>();
foreach(var server in Servers)
{
var count = UserCount(server);
items.Add(CreateItem(server, count);
}
return items;
变得更好。我们可以消除不必要的变量。
var items = new List<SelectListItem>();
foreach(var server in Servers)
items.Add(CreateItem(server, UserCount(server));
return items;
嗯。这使我们了解到CreateItem
可以自己进行计数。让我们重写它。
SelectListItem CreateItem(Server server) =>
new SelectListItem
{
Value = server.Id.ToString(),
Text = $"{server.InstanceCode}@{server.ServerName} ({UserCount(server)} users on)"
};
现在我们的道具身是
var items = new List<SelectListItem>();
foreach(var server in Servers)
items.Add(CreateItem(server);
return items;
这应该看起来很熟悉。我们已经重新发明了Select
和ToList
:
var items = Servers.Select(server => CreateItem(server)).ToList();
现在,我们注意到可以用方法组替换lambda:
var items = Servers.Select(CreateItem).ToList();
并且我们已将整个混乱情况简化为清晰明确的样子的一行。它有什么作用?它为每个服务器创建一个项目,并将它们放在列表中。 代码应该读起来像它所做的一样,而不是它的执行方式。
仔细研究我在这里使用的技术。
ForEach
ToList
如果我想按用户数量对下拉列表进行排序怎么办?
然后按用户数量排序!我们将其抽象为一个辅助方法,因此可以使用它:
var items = Servers
.OrderBy(UserCount)
.Select(CreateItem)
.ToList();
我们现在注意到,我们叫UserCount
两次。我们在乎吗?也许。两次调用它可能是一个性能问题,或者令人恐惧,它可能不是幂等的!如果有任何一个问题,那么我们需要撤销之前做出的决定。在理解模式下而不是在流利模式下更容易处理这种情况,所以让我们将其重写为一个理解:
var query = from server in Servers
orderby UserCount(server)
select CreateItem(server);
var items = query.ToList();
现在我们回到之前的内容:
SelectListItem CreateItem(Server server, int count) => ...
现在我们可以说
var query = from server in Servers
let count = UserCount(server)
orderby count
select CreateItem(server, count);
var items = query.ToList();
,我们每个服务器只调用一次UserCount
。
为什么要回到理解模式?因为在流利模式下执行此操作会造成混乱:
var query = Servers
.Select(server => new { server, count = UserCount(server) })
.OrderBy(pair => pair.count)
.Select(pair => CreateItem(pair.server, pair.count))
.ToList();
看起来有点难看。 (在C#7中,您可以使用元组而不是匿名类型,但是想法是相同的。)
答案 1 :(得分:1)
LINQ的窍门就是键入return
并从那里开始。不要创建列表并向其中添加项目;通常有一种一次性选择所有内容的方法。
public IEnumerable<SelectListItem> ServerItems
{
get
{
return Servers.Select
(
server =>
new
{
Server = server,
UserCount = Users.Count( u => u.ServerId = server.Id )
}
)
.Select
(
item =>
new SelectListItem
{
Value = item.Server.Id.ToString(),
Text = string.Format
(
@"{0}{1} ({2} users on)" ,
item.Server.InstanceCode,
item.Server.ServerName,
item.UserCount
)
}
);
}
}
在此示例中,实际上有两个Select
语句-一个用于提取数据,另一个用于格式化。在理想情况下,将这两个任务的逻辑划分为不同的层,但这是可以的。