2010-02-13 4 views
0

Мне нужно оставить в DataTable только записи с датами, которые в настоящее время нет в базе данных.Удалите дубликаты по полю из одной таблицы, используя другую, используя LINQ

Так что я прочитал все существующие даты с помощью хранимой процедуры (это правильно?):

SELECT DISTINCT CAST(S.[date] AS DATE) -- original date is DATETIME2(0) 
FROM ... 
WHERE ... 

и загрузить его в DataTable:

var tableDate = new DataTable(); 
new SqlDataAdapter(command).Fill(tableDate); 

Как удалить теперь от другой таблицы все ненужные строки? Я думаю, что LINQ может помочь, но я не знаю, как ..

+0

Я не уверен, что понимаю требования. Вы имеете в виду, что вам нужно найти все значения даты, которые не существуют в данной таблице в базе данных, или вы имеете в виду, что вам нужно удалить строки из таблицы A в базе данных, где значение даты не существует в таблице B? – Thomas

+0

@ Томас: Позвольте мне описать ситуацию. Мне нужно добавить новые данные в базу данных, используя SqlBulCopy. Но сначала мне нужно его очистить - я не добавляю данные, уже существующие в базе данных. Критерии - дата. Например, сначала я добавил данные за январь, 1,2,3. А после этого - за 2,3,4. Мне нужно удалить Jan, 2 из второго набора. – abatishchev

ответ

2

Я смотрю на ваш ответ, который вы говорите, работает, и вы просто хотите знать, как сделать это в «одном запросе LINQ.» Имейте в виду, что эти запросы все отложили выполнение, поэтому следующие два запроса функционально эквивалентны:

var q = 
    from d in dates 
    select d.Field<DateTime>("date"); 
return 
    (from r in records 
    where !q.Contains(r.Field<DateTime>("date")) 
    select r).CopyToDataTable(); 

И:

return 
    (from r in records 
    where !dates 
     .Select(d => d.Field<DateTime>("date")) 
     .Contains(r.Field<DateTime>("date")) 
    select r).CopyToDataTable(); 

Второй вариант намного сложнее читать, но, тем не менее, это это «один запрос».


Сказав это, ни один из этих примеров, на самом деле, кажется, не соответствуют вашим заголовок вопроса, что говорит о том, что вы пытаетесь удалить дубликаты строк. Если это действительно то, что вы пытаетесь сделать, вот метод, который будет делать это:

static DataTable RemoveDuplicates(DataTable dt) 
{ 
    return 
     (from row in dt.Rows.OfType<DataRow>() 
     group row by row.Field<string>("date") into g 
     select g 
      .OrderBy(r => r.Field<int>("ID")) 
      .First()).CopyToDataTable(); 
} 

Если вы не заботитесь о , какие дубликаты удаляются, то вы можете просто удалить OrderBy линию. Вы можете проверить это следующим образом:

static void Main(string[] args) 
{ 
    using (DataTable original = CreateSampleTable()) 
    using (DataTable filtered = RemoveDuplicates(original)) 
    { 
     DumpTable(filtered); 
    } 
    Console.ReadKey(); 
} 

static DataTable CreateSampleTable() 
{ 
    DataTable dt = new DataTable(); 
    dt.Columns.Add("ID", typeof(int)); 
    dt.Columns.Add("Code", typeof(string)); 
    dt.Columns.Add("Name", typeof(string)); 
    dt.Rows.Add(1, "123", "Alice"); 
    dt.Rows.Add(2, "456", "Bob"); 
    dt.Rows.Add(3, "456", "Chris"); 
    dt.Rows.Add(4, "789", "Dave"); 
    dt.Rows.Add(5, "123", "Elen"); 
    dt.Rows.Add(6, "123", "Frank"); 
    return dt; 
} 

static void DumpTable(DataTable dt) 
{ 
    foreach (DataRow row in dt.Rows) 
    { 
     Console.WriteLine("{0},{1},{2}", 
      row.Field<int>("ID"), 
      row.Field<string>("Code"), 
      row.Field<string>("Name")); 
    } 
} 

(только заменить «дату» с «Кодекс» в методе RemoveDuplicates для данного примера)

Надеюсь, в один из этих ответов на ваш вопрос. В противном случае, я думаю, вам нужно будет более четко понять ваши требования.

1

Вы можете использовать Except()

return records.Except(dates);

ОБНОВЛЕНЫ: Если ваш DataTable напечатал полей, то она должна быть следующим :

var excluded = arbDates.Rows.OfType<System.Data.DataRow>().Select(a => a[0]) .Except(excDates.Rows.OfType<System.Data.DataRow>().Select(e => e[0]));

иначе вы могли бы бросить его:

var excluded = arbDates.Rows.OfType<System.Data.DataRow>() .Select(a => Convert.ToDateTime(a[0].ToString())) .Except( excDates.Rows.OfType<System.Data.DataRow>() .Select(e => Convert.ToDateTime(e[0].ToString())));

+0

Привет. Не могли бы вы помочь мне написать один запрос LINQ с q1.Except (q2)? Как объединить выбор в одном? – abatishchev

+0

К сожалению, это не сработало. table.AsEnumerable(). Except (Database.CreateDataTable (команда) .AsEnumerable()). ToArray() каждый раз возвращает те же записи, что и в таблице. Я попытаюсь использовать пользовательский сопоставитель и отчитаюсь. – abatishchev

+0

Мое существующее пользовательское сравнение также не помогло. Я разместил его в своем ответе ниже. Есть идеи? – abatishchev

1

Ваш SQL-оператор выглядит отлично. Насколько я понимаю, вы бросаете, чтобы получить значение по умолчанию, начинающееся с полуночи. Поэтому даты в другой сравниваемой таблице также должны соответствовать этому формату, чтобы сравнивать даты с нейтральными временами.Если это не так, вы все равно можете использовать код, который у меня есть, но вы должны добавить свойство .Date в любом месте, на которое ссылается поле строки tableResult. Также я использовал Field<DateTime>(0), но в зависимости от вашего запроса и на основе вашего предыдущего примера вам может потребоваться использовать Field<DateTime>("date").

Нет необходимости в пользовательском сопоставлении. Чтобы объединить запросы LINQ в один запрос, вы можете просто использовать ключевое слово let и нести промежуточный результат через запрос и ссылаться на него.

Дайте этому попытку:

var tableDate = new DataTable(); 
new SqlDataAdapter(command).Fill(tableDate); 

// this is the other table that has other dates, so populate as needed 
var tableResult = new DataTable(); 

var newTable = 
    (from row in tableResult.AsEnumerable() 
    let uniqueRows = tableResult.AsEnumerable().Select(r => r.Field<DateTime>(0)) 
           .Except(tableDate.AsEnumerable().Select(r => r.Field<DateTime>(0))) 
    where uniqueRows.Contains(row.Field<DateTime>(0)) 
    select row).CopyToDataTable(); 

В точечной нотации запрос будет:

var newTable = tableResult.AsEnumerable() 
    .Select(row => new 
    { 
     Row = row, 
     UniqueRows = tableResult.AsEnumerable() 
           .Select(r => r.Field<DateTime>(0)) 
           .Except(tableDate.AsEnumerable().Select(r => r.Field<DateTime>(0))) 
    }) 
    .Where(item => item.UniqueRows.Contains(item.Row.Field<DateTime>(0))) 
    .Select(item => item.Row) 
    .CopyToDataTable(); 

Вместо tableResult.AsEnumerable() вы могли бы использовать tableResult.Rows.Cast<DataRow>() или tableResult.Rows.OfType<DataRow>(). Результаты одинаковы для всех этих подходов.

Если вы хотите удалить дубликаты из существующей таблицы (а не копировать его в новую таблицу), можно удалить элементы, возвращенные Intersect method из таблицы:

var commonDates = tableDate.AsEnumerable().Select(row => row.Field<DateTime>(0)) 
          .Intersect(tableResult.AsEnumerable().Select(row => row.Field<DateTime>(0))); 

for (int index = tableResult.Rows.Count - 1; index >= 0; index--) 
{ 
    if (commonDates.Contains(tableResult.Rows[index].Field<DateTime>(0))) 
    { 
     tableResult.Rows.RemoveAt(index); 
    } 
} 
1

Как я понимаю проблему , вы пытаетесь удалить данные из некоторого импорта. Вам может не понадобиться делать это с помощью LINQ. Хотя в заголовке статьи предлагается LINQ, вы позже задаетесь вопросом, может ли LINQ быть лучшим решением, и, учитывая то, что мы знаем, я думаю, вы могли бы сделать это, используя один оператор Insert.

Во-первых, я хотел бы предложить насыпной копирование данных в временную папку в БД (если вы еще не сделали этого) так:

Create Table TempBulkCopyData 
(
    Id int not null identity(1,1) 
    , Date DateTime2 not null 
    , ... 
) 

Одним из преимуществ объемного копирования во временный местоположение заключается в том, что вы можете добавлять индексы и такие, чтобы ускорить процесс очистки. Для дублирующих данных, можно затем выполнить запрос следующим образом:

Insert DestinationData(...) 
Select ... 
From BulkCopyData As BCD 
Where Id = (
      Select Min(BCD2.[Id]) 
      From BulkCopyData As BCD2 
      Where Cast(BCD2.[Date] As Date) = Cast(BCD.[Date] As Date) 
      ) 

Или

Insert DestinationData(...) 
Select ... 
From BulkCopyData As BCD 
Where Id = (
      Select Min(BCD2.[Id]) 
      From BulkCopyData As BCD2 
      Where DateDiff(d, BCD.[Date], BCD2.[Date]) = 0 
      ) 

Это будет тянуть первую дату он находит (один с наименьшим идентификатором). Это, очевидно, несколько произвольно, но для того, чтобы получить более точные данные, нам нужно будет больше узнать о структуре данных и их требованиях.