Мне нужен анализатор электронной почты, который прикрепляет входящие электронные письма к соответствующему билету.
Если тема электронного письма включает что-то в формате номера тикета, я ищу по нему, и все готово. Это работает годами.
Теперь мы хотим найти по теме соответствующий заголовок билета.
Я нашел Удаление «RE:» (и т.п.) из темы электронной почты с помощью класса RegEx, который отлично подходит для удаления re: и т. д. из входящей темы. Но у нас есть случаи, когда название билета включает RE:
. Я ожидаю, что он может включать FW:
и FWD:
, а также смешанные и строчные версии всех этих.
Какой самый чистый запрос Linq или SQL для поиска соответствия? Я знаком с Replace()
. Могу ли я связать их вместе? Ужасно некрасиво связывать воедино 16 замен, чтобы поймать все версии case, но я полагаю, что мог бы с этим смириться.
И как производительность при этом? Полагаю, я могу ToLower
обе стороны, и это должно работать? Это, вероятно, не повредило бы моему совпадению названий тем. Это до трех замен.
Я нашел одну страницу, на которой говорилось, что использование регулярных выражений в запросе приводит к извлечению всех данных, что может быть дорого. Это относится и к Remove
? Я уже фильтрую по входящему адресу электронной почты, чтобы получить одного клиента, поэтому я не собираюсь использовать их для всей базы данных. Итак, может быть, регулярное выражение жизнеспособно?
Есть ли что-то лучше? Напоминаем, мы используем Entity Framework.
Спасибо
PS: я знаю, что некоторые простые предметы могут получить несколько обращений. «Ошибка» или «Проблема с электронной почтой». Я закажу по дате убывания и надеюсь, что поймаю большинство из них.
----- Редактировать: -----
Простите меня, я думал, что мое описание было довольно подробным. Это довольно хорошо сдерживаемая проблема. Другими словами, у меня есть несколько общих посторонних текстовых фрагментов в поле VARCHAR, и я ищу лучший способ поиска по этому полю, который игнорирует эти несколько общих текстовых фрагментов.
Вот текущее состояние моего метода FindExistingTicket(). Я начал фильтрацию по клиентам, но не закончил ее, когда увидел эти комментарии. (Меня не было на рабочем месте пару часов.)
/// <summary>
/// Given a from address and a subject, see if we can find a ticket to match.
/// to match. Either the formatted ticket number is in the subject.
/// Or, the subject is identical to a ticket title.
/// </summary>
/// <param name = "from">The From address.</param>
/// <param name = "subject">The subject string.</param>
/// <returns>The ticket found, or null.</returns>
public Ticket FindExistingTicket(string from, string subject)
{
Ticket ticket = null;
// If the subject is null then there's not going to be a match.
if (subject == null)
{
return null;
}
// First look for a formatted ticket number.
Match m = TicketNumRegex.Match(subject);
if (m.Success)
{
// Make sure a ticket with that number actually exists.
ticket = db.Tickets.Where(a => a.TicketNumber == m.Value).FirstOrDefault();
}
// If we have a ticket, then we're done.
if (ticket != null) return ticket;
// Check to see if we know this email address
Contact contact = db.Contacts.Where(a => a.ContactStatu.Status.Equals("Active") && a.email.ToLower().Contains(from)).FirstOrDefault();
if (contact != null)
{
Client client = db.Clients.Where(a => a.id == contact.customer_id).FirstOrDefault();
// Search for a ticket where the Subject matches the Title.
// First, strip off any re: or Fwd:
// If we want multiple languages, per https://stackoverflow.com/questions/16395814/
// @"^([\[\(] *)?(RE?S?|FYI|RIF|I|FS|VB|RV|ENC|ODP|PD|YNT|ILT|SV|VS|VL|AW|WG|ΑΠ|ΣΧΕΤ|ΠΡΘ|תגובה|הועבר|主题|转发|FWD?) *([-:;)\]][ :;\])-]*|$)|\]+ *$", RegexOptions.IgnoreCase
Regex regex = new Regex(@"^(?>(?:re|fwd|fw) *: *)*", RegexOptions.IgnoreCase);
string strippedSubject = regex.Replace(subject, string.Empty);
// Among open tickets, if we find more than one ticket with this Title, we grab the last one.
List<Ticket> tickets = db.Tickets.Where(a => OpenIDs.Contains(a.Status_ID) && a.Title.Replace("re:", "") == strippedSubject).OrderByDescending(a => a.LastQueueDate).ToList();
if (tickets.Count() >= 1)
{
ticket = tickets.First();
}
}
// else we don't know the email address, so just fall through and return null
return ticket;
}
Итак, прямо сейчас, если в заголовке заявки в поле VARCHAR базы данных SQL есть RE:, я не нахожу совпадения. Примеры заголовков заявок, которые включают RE: и FW:, копируются и вставляются вручную из строк темы электронной почты, поэтому я не собираюсь удалять их до того, как они будут зафиксированы в базе данных.
После публикации этого, когда я выходил за дверь, мне пришло в голову, что как только я отфильтрую билеты по клиенту, у меня уже будет список в памяти. Итак, я должен иметь возможность дешево использовать регулярное выражение. Предполагая, что EF позволит мне это сделать.
Я рад услышать любые другие идеи.
Я не понимаю, где у тебя электронная почта? или вы работаете со списком строк?
Вам нужно поделиться с нами своим классом сущностей, какое хранилище данных вы используете, какой тип LINQ у вас уже есть... очень сложно сказать, что вы пытаетесь здесь сделать.
Если аргумент темы для FindExistingTicket не удаляется и тема в поле базы данных не удаляется, почему вы удаляете параметр темы только для того, чтобы применить ту же логику внутри вашего выражения/запроса LINQ? Почему бы просто не использовать необработанную тему в своем запросе? Затем, когда у вас есть соответствующий билет, удалите оскорбительные символы из ticket.Title
в C#.
Почему бы просто не использовать s.EndsWith(original)
. В любом случае производительность будет не очень хорошей в любом случае. База данных не может использовать здесь индексы.
Причина, по которой я их удаляю, заключается в том, что во многих случаях они будут меняться. По мере роста цепочки электронной почты некоторые почтовые программы будут видеть «re:», а затем добавят «RE:», и теперь у нас есть «RE: re:». И так далее. Множество способов, которыми они могут быть добавлены в строку темы. Другой способ сказать это: я не могу гарантировать, что они останутся последовательными. И если я сдираю их с одной стороны, мне придется сдирать их с другой. У меня нет хорошего способа выяснить, что оставить позади.
Я протестировал его с помощью EndsWith, и он работал в моих простых тестах. Но я могу представить ситуации, когда в названии билета есть RE:. Возможно, не часто, но я вижу, что это происходит. Таким образом, у субъекта будет удален этот внутренний RE:, и EndsWith больше не будет совпадать.
Поняв, что у меня есть список в памяти, я смог использовать одно и то же регулярное выражение как для темы, так и для заголовка в LINQ. Это хорошо, потому что они должны быть раздеты таким же образом. Вот соответствующая часть окончательного кода:
// Check to see if we know this email address
Contact contact = db.Contacts.Where(a => a.ContactStatu.Status.Equals("Active") && a.email.ToLower().Contains(from)).FirstOrDefault();
if (contact != null)
{
// Grab a list of only this client's open tickets.
List<Ticket> clientTickets = db.Tickets.Where(a => OpenIDs.Contains(a.Status_ID) && a.Company_ID == contact.customer_id).OrderByDescending(a => a.LastQueueDate).ToList(); // sort descending strip
// Search for a ticket where the Subject matches the Title.
// First, strip off any re: or Fwd:
// If we want multiple languages, per https://stackoverflow.com/questions/16395814/
// @"^([\[\(] *)?(RE?S?|FYI|RIF|I|FS|VB|RV|ENC|ODP|PD|YNT|ILT|SV|VS|VL|AW|WG|ΑΠ|ΣΧΕΤ|ΠΡΘ|תגובה|הועבר|主题|转发|FWD?) *([-:;)\]][ :;\])-]*|$)|\]+ *$", RegexOptions.IgnoreCase
Regex regex = new Regex(@"^(?>(?:re|fwd|fw) *: *)*", RegexOptions.IgnoreCase);
string strippedSubject = regex.Replace(subject, string.Empty);
// If we find more than one ticket with this Title, we grab the last one.
List<Ticket> tickets = clientTickets.Where(a => regex.Replace(a.Title, string.Empty)
== strippedSubject).OrderByDescending(a => a.LastQueueDate).ToList();
if (tickets.Count() >= 1)
{
ticket = tickets.First();
}
}
// else we don't know the email address, so just fall through and return null
Спасибо, что помогли мне обдумать это
Если вы ищете конкретную строку — «название билета» — то почему бы просто не искать по заголовкам и игнорировать все остальное? Вы можете использовать параметр табличного значения (если ваша версия EF поддерживает это)