【问题标题】:Dotnet EF Core Linq string contains in a list string split by commaDotnet EF Core Linq 字符串包含在以逗号分隔的列表字符串中
【发布时间】:2022-01-01 20:09:52
【问题描述】:

我在数据库中有这样的模型:

Post (PostId int, UserIds varchar(MAX)),示例 Post (12, "1,2,3,7,9,20")

我想通过 UserId 来查询,现在我用这个:

DBContext.Posts.Where(_ => _.UserIds.Contains(targetId)).ToList();

但问题是,如果 target 为 1,它也会返回 UserIds = "15,16" 的 Post 我尝试使用像 Regex.IsMatch(_.UserIds, $"\\b{targetId}\\b") 这样的正则表达式,但 SQL 无法翻译它。

有什么办法可以解决这个问题吗?

【问题讨论】:

  • 最好的解决方案是不要将您的数据作为逗号分隔的字符串存储在数据库中,这几乎总是一个坏主意。
  • @DavidG 所说的。但是,如果您坚持使用它,请在字符串上使用 .Split(',') 将其变成一个 ID 数组,然后您需要做什么。比如:_.UserIds.Split(',').Where(x => x == targetID).Any()
  • @Martin 我尝试过拆分,但 linq 不允许。这是一个旧系统,我暂时不想修改它的数据结构。
  • 如果您的数据库是 SQL Server 2016 或更高版本,则可以映射 the STRING_SPLIT function 以便您可以从 EF Core 调用它。

标签: sql-server entity-framework linq entity-framework-core


【解决方案1】:

所以你的数据库有一个用Posts 填充的表。每个Post 似乎都由零个或多个(可能是一个或多个)用户发布。在我看来,您还有一张Users 的表格。每个User 都发布了零个或多个Posts

在我看来UsersPosts 之间存在多对多关系:每个用户都发布了零个或多个帖子;每个帖子都由零个(一个?)或多个用户发布。

通常在数据库中,您会使用特殊表实现多对多关系:联结表。

您不使用联结表。您的数据库未标准化。 也许您当前的问题可以在不更改数据库的情况下解决,但是我看到您必须解决很多问题,可能不是现在,而是在不久的将来:如果您想删除用户,您需要做哪些巨大的工作?您如何获得所有“用户 [10] 已发布的帖子”以及如果用户 [10] 不想在帖子 [23] 的发布列表中被提及怎么办?如何防止在 Post[23] 中两次提到 User [10]:

UserIds = 10, 3, 5, 10, 7, 10

规范化数据库

考虑用联结表更新数据库并去掉字符串列Post.UserIds。这将一次性解决所有这些问题。

class User
{
    public int Id {get; set;}
    public string Name {get; set;}
    ...

    // every user has posted zero or more Posts:
    public virtual ICollection<Post> Posts {get; set;}
}

class Post
{
    public int Id {get; set;}
    public string Title {get; set;}
    public Datetime PublicationDate {get; set;}
    ...

    // every Post has been posted by zero or more Users:
    public virtual ICollection<User> Users {get; set;}
}

还有联结表:

public UsersPost
{
    public int UserId {get; set;}
    public int PostId {get; set;}
}

注意:[UserId, PostId] 是唯一的。使用这个作为主键

在实体框架中,表的列由非虚拟属性表示。虚拟属性反映了表之间的关系(一对多、多对多)

注意:外键是表中的真实列,因此外键是非虚拟的。

要配置多对多,可以使用 Fluent API:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // User - Post: many-to-many
    modelBuilder.Entity<User>()
            .HasMany<Post>(user => user.Posts)
            .WithMany(post => post.Users)
            .Map(userpost =>
                    {
                        userpost.MapLeftKey(nameof(UserPost.UserId));
                        userpost.MapRightKey(nameof(UserPost.PostId));
                        userpost.ToTable(nameof(UserPost));
                    });

    // primary key of UserPost is a composite key:
    modelBuilder.Entity<UserPost>()
        .HasKey(userpost => new {userpost.UserId, userpost.PostId});
}

回到你的问题

一旦你实现了联结表,你的数据请求就会很容易:

int userId = ...

// get this User with all his Posts:
var userWithPosts= dbContext.Users
    .Where(user => user.Id == userId)
    .Select(user => new
    {
         // Select only the user properties that you plan to use
         Name = user.Name,
         ...

         Posts = user.Posts.Select(post => new
         {
             // Select only the Post properties that you plan to use
             Id = post.Id
             PublicationDate = post.PublicationDate,
             ...
         })
         .ToList(),
    });

或者,如果您不想要任何用户数据,请从帖子开始:

var postsOfUser = dbContext.Posts
    .Where(post => post.Users.Any(user => user.Id == userId))
    .Select(post => new {...});

有些人不喜欢使用虚拟 ICollections,或者他们使用不支持此功能的实体框架版本。在这种情况下,您必须自己加入:

int userId = ...
var postsOfThisUser = dbContext.UserPosts

    // keep only the UserPosts of this user:
    .Where(userPost => post.UserId == userId)

    // join the remaining UserPosts with Posts
    .Join(dbContext.Posts,

    userpost => userpost.PostId,    // from every UserPost get the foreign key to Post
    post => post.Id,                // from every Post, get the primary key

    // parameter resultSelector: from every UserPost with matching Post make one new
    (userPost, post) => new
    {
        Title = post.Title,
        PublicationDate = post.PublicationDate,
        ...
    }
}

没有规范化数据库的解决方案

如果您真的无法让项目负责人相信一个合适的数据库将在未来避免很多问题,请考虑创建一个 SQL 文本来为您获取合适的帖子。

您的 DbContext 代表您的数据库的当前实现。它描述了表格和表格之间的关系。在我看来,添加一个获取用户帖子的方法似乎是 DbContext 的合法方法。

我的 SQL 有点生疏了,你会比我更了解如何在 SQL 中执行此操作。我想你会明白要点的:

public IEnumerable<Post> GetPostsOfUser(int userId)
{
    const string sqlText = "Select Id, ... from Posts where ..."

    object[] parameters = new object[] {userId};
    return this.Database.SqlQuery(sqlText, parameters);
}

【讨论】:

  • 感谢您的回答。是的,如果我规范化数据库会很容易,但它是一个旧系统,我只是做了一个简单的例子来描述我遇到的问题,在项目中这个领域涉及很多事情,需要更多时间来重构它同时我现在只想修复它。不管怎样,谢谢你的回答。
【解决方案2】:

如果您无法对其进行规范化,这里有一个可能的解决方案:

var sql = "select PostId,UserIds from Post";
sql += $" outer apply string_split(UserIds,',') where value={targetId}";

DBContext.Posts.FromSqlRaw(sql).ToList();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-31
    • 1970-01-01
    • 1970-01-01
    • 2016-05-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多