Как насчет метода SingleOrNew() вместо SingleOrDefault() в LINQ?
Метод SingleOrDefault()
велик, потому что он не генерирует исключение, если коллекция, с которой вы его вызываете, пуста. Однако иногда я хочу получить новый объект какого-либо типа, если ничего не существует. Например, было бы здорово, если бы я мог сделать следующее:
var client = db.Clients
.Where(c => c.Name == "Some Client")
.SingleOrNew<Client>();
Таким образом, мне не нужно проверять, если он null
, и если он создает новый, я всегда знаю, что мой объект client
будет тем, что я могу использовать сразу.
Любые идеи?
Ответы
Ответ 1
Действительно, вы просто хотите использовать оператор null coalescing. Пример:
var client = db.Clients
.Where(c => c.Name == "Some Client")
.SingleOrDefault() ?? new Client();
Это вернет то, что возвращает SingleOrDefault, за исключением того, что если SingleOrDefault возвращает значение null, выражение возвращает вместо него новый Client().
Изменить: Как отметил Джон Скит, это решение не проводит различия между ситуацией, когда нет совпадения, и найден нулевой элемент, хотя это явно не обязательно является проблемой во многих случаях, Альтернативой является создание метода расширения следующим образом.
public static T SingleOrNew<T>(this IEnumerable<T> query) where T : new()
{
try
{
return query.Single();
}
catch (InvalidOperationException)
{
return new T();
}
}
Я бы сказал, что это, наверное, самое элегантное решение, которое работает в общем случае.
Ответ 2
Если все, что вы хотите выполнить, - это переопределить значение по умолчанию (и вернуть новый объект), вы можете сделать это, используя DefaultIfEmpty ( ) Метод перед вызовом SingleOrDefault(). Что-то вроде:
var client = db.Clients
.Where(c => c.Name == name)
.DefaultIfEmpty(new Client { Name = name })
.SingleOrDefault();
Ответ 3
Будет ли это делать? EDIT Оказывается, что??? не будет работать с общим типом.
public static class IEnumerableExtensions
{
public static T SingleOrNew<T>( this IEnumerable<T> query ) where T : new()
{
var value = query.SingleOrDefault();
if (value == null)
{
value = new T();
}
return value;
}
}
Ответ 4
Похоже, это можно сделать, да. Не могу сказать, что я помню, что был в ситуации, когда я буду использовать ее сам, но ее достаточно просто реализовать. Что-то вроде этого:
public static T SingleOrNew<T>(this IEnumerable<T> source) where T : new()
{
if (source == null)
{
throw new ArgumentNullException("source");
}
using (IEnumerator<T> iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
{
return new T();
}
T first = iterator.Current;
if (iterator.MoveNext())
{
throw new InvalidOperationException();
}
return first;
}
}
Я добавлю его в список операторов, которые будут включены в MoreLinq...
Другим, более общим подходом было бы предоставление делегата, который был бы оценен только при необходимости (мне нужно, чтобы это имя было лучше):
public static T SingleOrSpecifiedDefault<T>(this IEnumerable<T> source,
Func<T> defaultSelector)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (defaultSelector == null)
{
throw new ArgumentNullException("defaultSelector");
}
using (IEnumerator<T> iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
{
return defaultSelector();
}
T first = iterator.Current;
if (iterator.MoveNext())
{
throw new InvalidOperationException();
}
return first;
}
}
Ответ 5
Почему бы вам не добавить его с помощью метода расширения? Похоже, это было бы удобно.
Ответ 6
var theClient =
Repository<Client>
.Entities()
.Where(t => t.ShouldBeHere())
.SingleOrDefault()
?? new Client { Name = "Howdy!" }
;