Dotnet EF Núcleo Linq string contém uma lista de seqüência de caracteres dividida por vírgula

0

Pergunta

Eu tenho um modelo como esse no banco de dados:

Post (PostId int, UserIds varchar(MAX)), exemplo de Postagem (12, "1,2,3,7,9,20")

Eu quero a consulta por UserId, por agora, eu uso este:

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

Mas o problema é que se o alvo for 1, ele também return Post com UserIds = "15,16" Eu tento usar Regex como Regex.IsMatch(_.UserIds, $"\\b{targetId}\\b") mas o SQL não pode traduzi-lo.

É alguma forma de resolver este caso?

2
1

Portanto, o seu banco de dados tem uma mesa cheia de Posts. Todos Post parece ser publicado por zero ou mais (talvez um ou mais) Usuários. Parece-me, que você também tem uma tabela de Users. Todos User postado zero ou mais Posts.

Parece-me que há uma relação muitos-para-muitos, uma relação entre a Users e Posts: Cada Usuário tem postado zero ou mais Cargos; todo Post foi publicado por zero (uma?) ou mais Usuários.

Normalmente em um banco de dados que você gostaria de implementar um muitos-para-muitos em relação com uma tabela especial: a tabela de junção.

Você não utilizar a tabela de junção. Seu banco de dados não é normalizado. Talvez o seu problema pode ser resolvido sem alterar o banco de dados, mas eu vejo tantos problemas que você terá que resolver, talvez não agora, mas no futuro próximo: o que imenso trabalho que você precisa fazer se você deseja excluir um usuário? Como conseguir que todos os "Posts que o usuário [10] publicou" E que se o Usuário [10] não quer ser mencionada mais na lista de publicação do Post [23]? Como impedir que o Usuário [10] é mencionado duas vezes no Post[23]:

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

Normalizar o banco de dados

Considere atualizar o banco de dados com uma tabela de junção e de se livrar da cadeia coluna Post.UserIds. Isso iria resolver todos estes problemas de uma vez.

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

E a tabela de junção:

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

Nota: [UserId, "PostId"] é único. Use esta uma chave primária

No entity framework colunas das tabelas são representadas por não-virtual propriedades. O virtual propriedades refletem as relações entre as tabelas (um-para-muitos, muitos-para-muitos)

Nota: uma chave estrangeira é uma coluna real em uma tabela, portanto, uma chave estrangeira é não-virtual.

Para configurar muitos-para-muitos, você pode usar a API Fluente:

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

De volta para o seu problema

Depois de implementar a tabela de junção seu pedido de dados vai ser fácil:

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

Ou, se você não quiser que qualquer utilizador dados, começar com os Posts:

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

Algumas pessoas não gostam de usar o virtual ICollections, ou eles usam uma versão do entity framework não oferece suporte a esta. Nesse caso, você vai ter que fazer a Juntar-se:

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

Solução sem banco de dados normalizado

Se você realmente não pode convencer seu líder de projeto que um banco de dados adequado vai evitar uma série de problemas no futuro, considere a possibilidade de criar um texto SQL que chegar a cargos apropriados para você.

O DbContext representa a implementação atual de seu banco de dados. Ele descreveu as tabelas e as relações entre as tabelas. Adicionando um método para buscar os Posts de um usuário, parece-me um método legítimo para o DbContext.

Meu SQL é um pouco enferrujado, você vai saber muito melhor do que eu como fazer isso no SQL. Eu acho que você vai obter a essência:

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);
}
2021-11-23 11:09:01

Obrigado por sua resposta. Sim, vai ser fácil se eu normalizar o banco de dados, mas é um sistema antigo, eu fiz apenas um exemplo simples para descrever o problema que eu tenho em, no projeto este campo corresponde a um monte de coisas, precisam de mais tempo para refatorar isso enquanto eu só quero hotfix agora. De qualquer maneira, obrigado por sua resposta.
Jihai
1

Aqui está uma possível solução, se você não pode normalizar-lo:

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

DBContext.Posts.FromSqlRaw(sql).ToList();
2021-11-23 18:13:09

Em outros idiomas

Esta página está em outros idiomas

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Česk
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................