Пытается написать запрос LINQ, используя содержит и начинается с списка
Используя VS 2017 в C#, у меня есть рабочее решение с расширенной областью применения. Первоначально нужно было только вернуть набор данных, для которого goodRMA_flag был установлен в true, если line.RMANumber содержал или начинал с двух разных переменных.
Это оригинальный код, который отлично работает:
string mask1 = "/078";
string mask2 = "078";
//start with all of them, flag the good and bad
var RMA_stops_all = (from rma in rDb.DistributionStopInformations
join line in rDb.DistributionLineItems on rma.UniqueIdNo equals line.UniqueIdNo
where line.RmaNumber != null
&&
(line.DatetimeCreated > Convert.ToDateTime(dateToCheck_rma) &&
line.DatetimeCreated < Convert.ToDateTime(dateToCheck_rma).AddDays(7))
&& rma.CustomerNo == TNGCustNo
select new
{
DatetimeCreated = line.DatetimeCreated,
UniqueIdNo = rma.UniqueIdNo,
RmaNumber = line.RmaNumber,
RmaOriginalUniqueId = line.RmaOriginalUniqueId,
ItemSequenceNo = line.ItemSequenceNo,
ItemNumber = line.ItemNumber,
goodRMA_flag = (line.RmaNumber.Contains(mask1) || line.RmaNumber.StartsWith(mask2)),
RMA_cleanedUp = line.RmaNumber.Substring(line.RmaNumber.IndexOf("/") + 1)
}).ToArray();
Теперь они расширили требование, где может потребоваться любое количество масок. Я строю список всех необходимых совпадений, используя этот код:
int startingMask = 75;
int numberNumberNeeded = 10;
List<string> masksContains = new List<string>();
List<string> masksStartsWith = new List<string>();
while (numberNumberNeeded > 0)
{
string newMask = (++startingMask).ToString().PadLeft(3, '0');
masksStartsWith.Add(newMask);
newMask = newMask.PadLeft(4, '/');
masksContains.Add(newMask);
numberNumberNeeded--;
}
Теперь, когда у меня есть список, я хотел изменить строку для goodRMA_flag на что-то вроде:
goodRMA_flag = (line.RmaNumber.Contains(masksContains) || line.RmaNumber.StartsWith(masksStartsWith)),
Я предполагаю, что мне понадобится какая-то Lamda, но я не смог получить правильный синтаксис, несмотря на многочисленные колебания.
РЕДАКТИРОВАТЬ: Использование -
goodRMA_flag = (masksContains.Any(masks => line.RmaNumber.Contains(masks)) ||
masksStartsWith.Any(masks => line.RmaNumber.StartsWith(masks))),
Выдает ошибку времени выполнения: "System.NotSupportedException:" Локальная последовательность не может использоваться в реализации операторов запросов LINQ to SQL, кроме оператора Contains() ".
Решение - вот окончательный рабочий код для справки:
//a integer that we are starting with - this needs to come from
//a database at some point
int startingMask = 75;
//how far ahead to look
int qtyNeeded= 10;
//will hold the strings that RMA_Number needs to contain
List<string> masksContains = new List<string>();
//will hold the strings that RMA_Number needs to start with
List<string> masksStartsWith = new List<string>();
//Build the two lists
while (qtyNeeded> 0)
{
string newMask = (++startingMask).ToString().PadLeft(3, '0');
masksStartsWith.Add(newMask);
newMask = newMask.PadLeft(4, '/');
masksContains.Add(newMask);
qtyNeeded--;
}
//start with all of them, flag them all as bad -- will then step through and fix
var RMA_stops_all = (from rma in rDb.DistributionStopInformations
join line in rDb.DistributionLineItems on rma.UniqueIdNo equals line.UniqueIdNo
where line.RmaNumber != null
&&
(line.DatetimeCreated > Convert.ToDateTime(dateToCheck_rma) &&
line.DatetimeCreated < Convert.ToDateTime(dateToCheck_rma).AddDays(7))
&& rma.CustomerNo == TNGCustNo
select new RMA_Items
{
DatetimeCreated = Convert.ToDateTime(line.DatetimeCreated),
UniqueIdNo = rma.UniqueIdNo,
RmaNumber = line.RmaNumber,
RmaOriginalUniqueId = Convert.ToDecimal(line.RmaOriginalUniqueId),
ItemSequenceNo = Convert.ToDecimal(line.ItemSequenceNo),
ItemNumber = line.ItemNumber,
goodRMA_flag = false,
RMA_cleanedUp = line.RmaNumber.Substring(line.RmaNumber.IndexOf("/") + 1)
}).ToArray();
//convert it to a new list that we can step through
var rmaStopsAllList = RMA_stops_all.ToList();
//go through the new list, set the goodRMA_flag for the items that meet our criteria
rmaStopsAllList.ForEach(x => x.goodRMA_flag =
(masksContains.Any(masks => x.RmaNumber.Contains(masks))
|| masksStartsWith.Any(masks => x.RmaNumber.StartsWith(masks)))&&
x.RMA_cleanedUp.Length==10);
//flip it back into our original array
RMA_stops_all = rmaStopsAllList.ToArray();
//pull out the good ones
var RMA_Stops_GoodRMA = (from R in RMA_stops_all
where R.goodRMA_flag == true
select R).ToArray();
//pull out the bad ones
var RMA_Stops_BadRMA = (from B in RMA_stops_all
where B.goodRMA_flag == false
select B).ToArray();
Вместе с новым классом:
class RMA_Items
{
public DateTime DatetimeCreated { get; set; }
public decimal UniqueIdNo { get; set; }
public decimal ItemSequenceNo { get; set; }
public string RmaNumber { get; set; }
public decimal RmaOriginalUniqueId { get; set; }
public string ItemNumber { get; set; }
public bool goodRMA_flag { get; set; }
public string RMA_cleanedUp { get; set; }
}
3 ответа
Я предполагаю что line.RmaNumber
должен содержать любую из масок, доступных в masksContains
, Или, альтернативно line.RmaNumber
должен начинаться с одной из масок, определенных в masksStartsWith
, Если какое-либо из этих условий выполнено, то goodRMA_flag
будет установлен в true
, Надеюсь, это то, что вы ищете:
goodRMA_flag = (masksContains.Any(masks => line.RmaNumber.Contains(masks))
|| masksStartsWith.Any(masks => line.RmaNumber.StartsWith(masks)))
РЕДАКТИРОВАТЬ: Однако, поскольку LINQ to SQL не позволяет этого, потому что line.RmaNumber
идет от SQL напрямую, мы можем вместо этого обновить флаг на следующем шаге, так как RmaNumber
имеется в объекте.
var rmaStopsAllList = RMA_stops_all.ToList();
rmaStopsAllList.Foreach(x => x.goodRMA_flag =
(masksContains.Any(masks => x.RmaNumber.Contains(masks))
|| masksStartsWith.Any(masks => x.RmaNumber.StartsWith(masks))));
RMA_stops_all = rmaStopsAllList.ToArray();
РЕДАКТИРОВАТЬ 2: Также необходимо объявить фактический класс вместо использования анонимного типа, чтобы он мог быть назначен позже (как показано выше). goodRMA_flag
должно быть свойством в классе и может иметь любое значение на предыдущем шаге.
select new SomeClass
{
// Other initialization here
goodRMA_flag = false,
}).ToArray();
вместо
select new
{
// Other initialization here
goodRMA_flag = false,
}).ToArray();
и что наиболее важно, обязательно добавьте новое определение класса для этой цели с правильными типами данных:
public class SomeClass
{
public string DateTime DatetimeCreated {get; set;}
public string UniqueIdNo {get; set;}
public string RmaNumber {get; set;}
public int RmaOriginalUniqueId {get; set;}
public string ItemSequenceNo {get; set;}
public string ItemNumber {get; set;}
public bool goodRMA_flag {get; set;}
public string RMA_cleanedUp {get; set;}
}
РЕДАКТИРОВАТЬ: Основная проблема заключается в том, что это LinqToSql, и он ссылается на локальную последовательность в операторе выбора. Вам нужно будет получить результаты и создать новый объект с локальными результатами.
Надеюсь, я не правильно понял ваш вопрос.
var db = new string[]{ "1", "2", "41", "C", "ka"};
var masks = new string[] { "a", "1" };
var query = from n in db
select new
{
n = n,
t = masks.Where(x=> n.Contains(x)).Count() > 0, //works
t2 = masks.Any(x=> n.Contains(x)) //best approach
};
Как видите, просто нужно проверить на Содержит.
1, 41 и ка, сбывается.
Вы можете попытаться создать метод, который возвращает логическое значение. Он примет список масок. Если маска не совпадает, вы возвращаете false.
Когда у вас есть этот метод, вы можете передать его в лямбду.