Sembra che sarebbe molto semplice, ma non riesco a risolverlo. Utilizzando EF Core, ho un DbSet<Rule> Rules
nel mio DbContext
.
public class Rule
{
public int Id { get; set; }
public string Raw { get; set; }
}
Sto provando a scrivere una query in cui, data una IEnumerable<string> lines
, mi dia tutte le Rule
s dal DbSet
dove il suo valore Raw
è un elemento in lines
(corrispondenza esatta, non una sottostringa di un valore).
Per qualche tempo, stavo usando qualcosa come:
private IQueryable<Rule> GetExistingRules() =>
dbContext.Rules.Where(r => lines.Contains(r.Raw));
Ma da allora ho scoperto che (penso) questo non stava facendo quello che mi aspettavo. (Questo metodo è immediatamente seguito dall'inserimento di nuove Rule
per tutti gli elementi di lines
che al momento non esistono. Stavo ottenendo Rule
duplicate con lo stesso valore Raw
...) Penso, invece, che devo usare .Intersect()
?
Ho provato a utilizzare un EqualityComparer personalizzato per questo , ma genera un'eccezione.
private IQueryable<Rule> GetExistingRules()
{
var lineRules = lines.Select(l => new Rule {Raw = l});
return dbContext.Rules.Intersect(lineRules, new RuleRawEqualityComparer());
}
private class RuleRawEqualityComparer : IEqualityComparer<Rule>
{
public bool Equals(Rule x, Rule y) => x?.Raw == y?.Raw;
...
}
Impossibile analizzare il valore dell'espressione '(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1 [FilterLists.Data.Entities.Rule]). Intersect (__ p_0, __p_1)': Questo overload del metodo 'System.Linq.Queryable.Intersect 'non è attualmente supportato.
Qual è il modo migliore per comporre questa query? Si noti che si trova in una catena di interazioni DbContext
, quindi preferirei mantenere il tipo restituito come IQueryable
per abilitare la composizione lazy di query di EF.
Aggiornamento: maggiori informazioni sul motivo per cui sospettavo che l'approccio Contains()
non funzionasse:
Questa è la classe in cui viene utilizzata la query. Sto vedendo eccezioni come di seguito perché la colonna Raw
nel database ha un vincolo univoco. Ho pensato che la mia logica (l'uso di Except()
in CreateNewRules()
) avrebbe impedito qualsiasi riga in Rules
con valori Raw
duplicati, ma forse il mio problema risiede altrove ...
public class SnapshotBatch
{
private readonly FilterListsDbContext dbContext;
private readonly IEnumerable<string> lines;
private readonly Data.Entities.Snapshot snapEntity;
public SnapshotBatch(FilterListsDbContext dbContext, IEnumerable<string> lines,
Data.Entities.Snapshot snapEntity)
{
this.dbContext = dbContext;
this.lines = lines;
this.snapEntity = snapEntity;
}
public async Task SaveAsync()
{
var existingRules = GetExistingRules();
var newRules = CreateNewRules(existingRules);
dbContext.Rules.AddRange(newRules);
var rules = existingRules.Concat(newRules);
AddSnapshotRules(rules);
await dbContext.SaveChangesAsync();
}
private IQueryable<Rule> GetExistingRules() =>
dbContext.Rules.Where(r => lines.Contains(r.Raw));
private List<Rule> CreateNewRules(IQueryable<Rule> existingRules) =>
lines.Except(existingRules.Select(r => r.Raw))
.Select(r => new Rule {Raw = r})
.ToList();
private void AddSnapshotRules(IQueryable<Rule> rules) =>
snapEntity.AddedSnapshotRules
.AddRange(rules.Select(r => new SnapshotRule {Rule = r}));
}
Snippet dall'eccezione StackTrace (dove '### Meebo: AdElement.Root' è un valore di esempio per Raw
nella tabella Rules
):
FilterLists.Services.Snapshot.Snapshot.TrySaveAsync () in /home/travis/build/collinbarrett/FilterLists/src/FilterLists.Services/Snapshot/Snapshot.cs:line 43 Duplica voce '### Meebo: AdElement.Root' per chiave "IX_rules_Raw" su MySql.Data.MySqlClient.MySqlDataReader.ActivateResultSet (ResultSet resultSet) in C: \ projects \ mysqlconnector \ src \ MySqlConnector \ MySql.Data.MySqlClient \ MySqlDataReader.cs: riga 93 a
Aggiornamento 2 : Sono abbastanza sicuro che il problema riscontrato con Contains()
sia dovuto a questo problema che ha un PR attivo . Dato che le mie stringhe hanno tutti i tipi di caratteri speciali, penso che non siano stati scappati correttamente quando sono passati a Contains()
, ma sembrano essere opportunamente scappati come parametri in un Join()
.
Non dimenticare che quando usi linQ con EFCore e IQueryable, traduce il codice c # nelle istruzioni Sql.
Hai provato questo?
var query = from rule in dbContext.Rules
join line in lines
on rule.Raw equals line
select rule;
Hai scritto:
Dammi tutte le regole dal DbSet dove il suo valore Raw è un elemento nelle linee (corrispondenza esatta ecc.)
La tua prima soluzione darà il risultato desiderato:
var requestedRules = dbContext.Rules
.Where(rule => lines.Contains(rule));
In parole: dalla raccolta di Rules
, selezionare solo quelle Rules
che hanno un valore Raw
uguale a uno dei valori nella sequenza di lines
.
Stavo ottenendo regole duplicate con lo stesso valore di Raw ...)
A quanto pare la tua raccolta di fonti ha regole con lo stesso valore di Raw!
Se vuoi solo valori Raw univoci devi decidere cosa fare con i duplicati:
Id Raw
1 "Hello"
2 "Hello"
Quale volete? Il primo? l'ultimo? Entrambi? Nessuna? Qualunque?
Prendiamo per Any: creeremo gruppi di regole con lo stesso valore di Raw, e da ogni gruppo prendiamo il primo (o se vogliamo l'ultimo, dopotutto non ci interessa, ma è un po 'meno efficiente.
var result = dbContext.Rules
.Where(rule => lines.Contains(rule))
.GroupBy(rule => rule.Raw)
.Select(group => group.FirstOrDefault());
In parole: dalla raccolta di Rules
, selezionare solo quelle Rules
che hanno un valore Raw
uguale a uno dei valori nella sequenza di lines
. Dagli elementi rimanenti si creano gruppi di regole con lo stesso valore Raw. Quindi da ogni gruppo prendi qualsiasi elemento, per esempio il primo.
Se vuoi tutto / solo il primo / solo l'ultimo, saprai cosa fare adesso.