【问题标题】:Get all entities from DbSet<TEntity> with a property value that is in an IEnumerable使用 IEnumerable 中的属性值从 DbSet<TEntity> 获取所有实体
【发布时间】:2019-01-26 14:04:58
【问题描述】:

这似乎很简单,但我似乎无法解决它。使用 EF Core,我的 DbContext 中有一个 DbSet&lt;Rule&gt; Rules

public class Rule
{
    public int Id { get; set; }
    public string Raw { get; set; }
}

我正在尝试编写一个查询,在给定IEnumerable&lt;string&gt; lines 的情况下,给我来自DbSet 的所有Rules,其中它的Raw 值是lines 中的一个元素(完全匹配,不是值的子字符串)。

有一段时间,我在使用类似的东西:

private IQueryable<Rule> GetExistingRules() =>
    dbContext.Rules.Where(r => lines.Contains(r.Raw));

但是,我后来发现(我认为)这并没有达到我的预期。 (此方法之后立即为当前不存在的lines 的所有元素插入新的Rules。我得到了具有相同Raw 值的重复Rules...)我认为,相反,我需要使用.Intersect()

我尝试根据 this 使用自定义 EqualityComparer,但它会引发异常。

    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;
        ...
    }

无法解析表达式 '值(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[FilterLists.Data.Entities.Rule]).Intersect(__p_0, __p_1)':目前不支持“System.Linq.Queryable.Intersect”方法的这种重载。

编写此查询的最佳方法是什么?请注意,它位于 DbContext 交互链中,因此我更愿意将返回类型保留为 IQueryable 以启用 EF 的惰性查询组合。

Context on GitHub

更新:更多关于我为什么怀疑Contains() 方法不起作用的信息:

这是使用查询的类。我看到如下异常,因为数据库中的 Raw 列具有唯一约束。我认为我的逻辑(在CreateNewRules() 中使用Except())会阻止Rules 中具有重复Raw 值的任何行,但也许我的问题出在其他地方......

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}));
}

来自异常 StackTrace 的代码段(其中 '###Meebo:AdElement.Root' 是 Rules 表中 Raw 的示例值):

FilterLists.Services.Snapshot.Snapshot.TrySaveAsync() 在 /home/travis/build/collinbarrett/FilterLists/src/FilterLists.Services/Snapshot/Snapshot.cs:line 43 键 'IX_rules_Raw' 的重复条目 '###Meebo:AdElement.Root' MySql.Data.MySqlClient.MySqlDataReader.ActivateResultSet(ResultSet 结果集)在 C:\projects\mysqlconnector\src\MySqlConnector\MySql.Data.MySqlClient\MySqlDataReader.cs:line 93 在

更新 2:我很确定我看到的 Contains() 问题是由于 this issue 具有 active PR。因为我的字符串有各种特殊字符,我认为它们在传递给Contains() 时没有被正确转义,但它们似乎作为Join() 中的参数被正确转义。

【问题讨论】:

  • 你能再解释一下Contains 方法有什么问题吗(因为它似乎是从 db 获取现有规则的正确方法)?
  • @IvanStoev 请参阅问题底部的更新。可能是我问了一个完全错误的问题……我不确定。
  • 异常似乎是由不同的代码 (Snapshot.TrySaveAsync) 生成的。问题很可能是由 snapEntity.AddedSnapshotRules.Add[Range] 引起的 - 如果 snapEntity 附加到不同的上下文,则该上下文的更改跟踪器可能会认为 Rule 对象是新的。
  • SnapshotBatch 使用与调用者 Snapshot.TrySaveAsync() 相同的 DbContext 实例。但是,由于Snapshot 启动了多个SnapshotBatch 实例,我想知道是否由于一切都是异步的而存在冲突......
  • 请注意,数据库中的比较可能不区分大小写。因此,如果在数据库中有规则 Alines 包含 aA(或仅 a),您将获得现有规则:A,除 A 之外的行是 a ,因为 C# 区分大小写,您将尝试插入 a,这是数据库的重复项。

标签: c# mysql entity-framework linq entity-framework-core


【解决方案1】:

不要忘记,当您将 linQ 与 EFCore 和 IQueryable 一起使用时,它会将 c# 代码转换为 Sql 语句。

你试过了吗?

var query = from rule in dbContext.Rules
            join line in lines
                on rule.Raw equals line
            select rule;

【讨论】:

  • 初步测试似乎表明这比.Contains() 工作得更好,将进行更全面的测试,这将需要一些时间。我使用的是 R# 转换的方法链语法dbContext.Rules.Join(lines, rule =&gt; rule.Raw, line =&gt; line, (rule, line) =&gt; rule),但应该是等价的。
  • 想知道Join() 是否会比Contains() 工作得更好,因为我正在处理每个this 的大型数据集...
  • 虽然Contains() 应该也可以工作,但至少在我的问题中Update 2 中提到的问题得到解决之前,将其标记为我正在使用的有效替代方案。谢谢!
【解决方案2】:

你写道:

给我 DbSet 中的所有规则,其中其原始值是行中的元素(精确匹配等)

您的第一个解决方案将提供所需的结果:

var requestedRules = dbContext.Rules
   .Where(rule => lines.Contains(rule));

换句话说:从Rules 的集合中,仅选择那些RulesRaw 值等于lines 序列中的值之一。

我得到了具有相同原始值的重复规则...)

显然,您的源集合具有相同原始值的规则!

如果您只想要唯一的原始值,则必须决定如何处理重复项:

Id  Raw
 1  "Hello"
 2  "Hello"

你想要哪个?首先?最后?两个都?没有?有吗?

让我们选择 Any:我们将创建具有相同 Raw 值的规则组,并从每个组中获取第一个(或者如果您想要最后一个,毕竟我们不在乎。但这有点效率较低。

var result = dbContext.Rules
   .Where(rule => lines.Contains(rule))
   .GroupBy(rule => rule.Raw)
   .Select(group => group.FirstOrDefault());

换句话说:从Rules 的集合中,只选择那些Raw 值等于lines 序列中的值之一的Rules。从其余元素中创建具有相同原始值的规则组。然后从每个组中取出任何元素,例如第一个。

如果你想要所有/只有第一个/只有最后一个,你现在就知道该怎么做了。

【讨论】:

  • 谢谢,哈拉德。在我的问题的“更新”下,我提到“数据库中的Raw 列具有唯一约束”。因此,数据库中已经没有重复的 Raw 值(正如我的问题底部的异常所证明的那样)。我怀疑我的问题与SnapshotBatch 共享dbContextasyncthis 中运行的多个实例有关。
猜你喜欢
  • 1970-01-01
  • 2023-04-05
  • 1970-01-01
  • 1970-01-01
  • 2011-10-31
  • 1970-01-01
  • 2012-02-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多