Удалите повторяющиеся сущности на основе нескольких свойств с помощью SQL / LINQ - PullRequest
0 голосов
/ 18 июня 2020

У меня две сущности: Etf и DataPoint. Каждый Etf имеет несколько DataPoints. См. Структуру ниже.

public class Etf
{
    public Etf() 
    { 
        DataPoints = new HashSet<DataPoint>(); 
    }

    public string Id { get; set; }
    public ICollection<DataPoint> DataPoints { get; private set; }
}

public class DataPoint { 
    public string EtfId { get; set; }
    public Etf Etf { get; set; }
    public DateTime Date { get; set; }

}

Бывает, что несколько точек данных с одинаковыми EtfId и Date вставляются в базу данных. Я хотел бы удалить дубликаты на основе этих двух полей.

В SQL, я пробовал это:

WITH CTE AS 
    (SELECT Id, ROW_NUMBER() OVER 
       (PARTITION BY EtfId, Date ORDER BY EtfId, Date DESC) 
       AS ROW_NUMBER FROM DataPoints) 
DELETE FROM CTE WHERE ROW_NUMBER > 1;

Что дает мне ошибку, что CTE не является обновляемой таблицей (возможно, поскольку он также подключен к обоим Etf как DataPoint). Затем я попробовал, основываясь на этом ответе, следующее в LINQ:

// Get the duplicate indices
var idxs = await _context.DataPoints
                    .GroupBy(g => new {g.EtfId, g.Date})
                    .OrderBy(g => g.Key.EtfId)
                    .ThenByDescending(g => g.Key.Date)
                    .SelectMany(g =>
                        g.Select((i, idx) => new { i.Id, Idx = idx }))
                    .Where(g => g.Idx > 0)
                    .Select(g => g.Id)
                    .ToListAsync(cancellationToken);

// Get the duplicate entities from indices
var duplicates = await _context.DataPoints
                    .Where(x => idxs.Contains(x.Id))
                    .ToListAsync(cancellationToken);

// Remove them
_context.DataPoints.RemoveRange(duplicates);

Однако этот подход дает мне System.InvalidOperationException в заявлении Select((i, idx) => ..), говоря, что это может быть ошибка или ограничение от EF Core.

Есть ли лучший (или просто работающий) метод, который я могу использовать?

Ответы [ 2 ]

0 голосов
/ 18 июня 2020

... и это, возможно, худшее решение в c # / linq. Он удалит все, кроме одной из повторяющихся точек данных, точек данных с Id = 2 и 5.

void Main()
{
    var datapoints = new List<Datapoint>();
    datapoints.Add(new Datapoint() { Id = 1, EtfId = "A", EtfDate = new DateTime(2020, 6, 1) });
    datapoints.Add(new Datapoint() { Id = 2, EtfId = "A", EtfDate = new DateTime(2020, 6, 1) });
    datapoints.Add(new Datapoint() { Id = 3, EtfId = "A", EtfDate = new DateTime(2020, 6, 2) });
    datapoints.Add(new Datapoint() { Id = 4, EtfId = "B", EtfDate = new DateTime(2020, 6, 1) });
    datapoints.Add(new Datapoint() { Id = 5, EtfId = "B", EtfDate = new DateTime(2020, 6, 1) });
    var duplicates = datapoints
        .GroupBy(g => new { g.EtfId, g.EtfDate })
        .Select(g => new { g.Key.EtfId, g.Key.EtfDate, Count = g.Count() })
        .Where(g => g.Count > 1);
    var datapointsToremove = new List<int>();
    foreach (var d in duplicates)
    {
        var removeDataPoints = datapoints
            .Where(x => x.EtfId == d.EtfId && x.EtfDate == d.EtfDate).Skip(1)
            .Select(x => x.Id);
        foreach (var rd in removeDataPoints)
        {
            datapointsToremove.Add(rd);
        };
    };
    foreach (var dtr in datapointsToremove)
    {
        datapoints.Remove(datapoints.FirstOrDefault(d => d.Id == dtr));
    }
}
public class Datapoint
{
    public int Id { get; set; }
    public string EtfId { get; set; }
    public DateTime EtfDate { get; set; }
}

И еще это навеяно var ownItems = items.GroupBy (x => x.Id). Выберите (y => y.First ()); см. Удалите дубликаты в списке с помощью linq

void Main()
{
    var datapoints = new List<Datapoint>();
    datapoints.Add(new Datapoint() { Id = 1, EtfId = "A", EtfDate = new DateTime(2020, 6, 1) });
    datapoints.Add(new Datapoint() { Id = 2, EtfId = "A", EtfDate = new DateTime(2020, 6, 1) });
    datapoints.Add(new Datapoint() { Id = 3, EtfId = "A", EtfDate = new DateTime(2020, 6, 2) });
    datapoints.Add(new Datapoint() { Id = 4, EtfId = "B", EtfDate = new DateTime(2020, 6, 1) });
    datapoints.Add(new Datapoint() { Id = 5, EtfId = "B", EtfDate = new DateTime(2020, 6, 1) });
    datapoints.Dump();
    var distinctItems = datapoints.GroupBy(x => new { x.EtfId, x.EtfDate}).Select(y => y.First());
}
class Datapoint
{
    public int Id  {get;set;}
    public string EtfId {get;set;}
    public DateTime EtfDate  {get;set;} 
}

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

0 голосов
/ 18 июня 2020

Мне удалось найти - отвратительное - решение с помощью SQL:

await _context.Database
                .ExecuteSqlRawAsync("DELETE FROM DataPoints WHERE Id IN " +
                                    "(SELECT Id FROM " +
                                    "(WITH c AS (SELECT Id, ROW_NUMBER() " +
                                    "OVER (PARTITION BY EtfId, Date ORDER BY EtfId, Date DESC) AS rn FROM DataPoints)" +
                                    "SELECT * FROM c WHERE rn > 1) AS t);");

Пожалуйста, никому не говорите об этом посте.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...