Dotnet EF Core Linq stringa contiene un elenco di stringa divisi da una virgola

0

Domanda

Ho un modello come questo nel database:

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

Voglio query by UserId, per ora, io uso questo:

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

Ma il problema è che se il target è di 1, anche il ritorno Post con UserIds = "15,16" Ho provato a usare Regex come Regex.IsMatch(_.UserIds, $"\\b{targetId}\\b") ma l'SQL non posso tradurre.

È un modo per risolvere questo caso?

2
1

Così il database contiene una tabella con Posts. Ogni Post sembra essere inviato da zero o più (forse uno o più Utenti. Mi sembra anche di disporre di una tabella di Users. Ogni User ha pubblicato zero o più Posts.

Mi sembra che c'è molti-a-molti relazione tra Users e Posts: Ogni Utente ha postato zero o più Posti; ogni Post è stato inviato da zero (uno?) o più Utenti.

Normalmente in un database, è necessario implementare una molti-a-molti relazione con una speciale tabella: la tabella di collegamento.

Non utilizzare la tabella di collegamento. Il database non è normalizzato. Forse il tuo attuale problema può essere risolto senza cambiare il database, ma io la vedo così tanti problemi che si devono risolvere, forse non ora, ma in un prossimo futuro: che cosa è immenso lavoro è necessario fare se si desidera eliminare un utente? Come si fa a ottenere tutti "i Post che l'utente [10] ha inviato" E che cosa se l'Utente [10] non vuole essere menzionato in più nella pubblicazione elenco dei Post [23]? Come impedire che l'Utente [10] è citato due volte nel Post[23]:

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

Normalizzare il database

Considerare l'aggiornamento del database con una tabella di collegamento e di sbarazzarsi della stringa colonna Post.UserIds. Questo risolverebbe tutti questi problemi in una volta.

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 la tabella di collegamento:

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

Nota: [UserId, PostId] è unico. Utilizzare questa una chiave primaria

In entity framework colonne delle tabelle sono rappresentati dalla non-virtuale proprietà. La proprietà virtuale riflettere le relazioni tra le tabelle (uno-a-molti, molti-a-molti)

Nota: una chiave esterna è una vera e propria colonna in una tabella, quindi una chiave esterna non è virtuale.

Per configurare molti-a-molti, è possibile utilizzare Fluentemente 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});
}

Torna al tuo problema

Una volta implementata la tabella di collegamento dati richiesta sarà facile:

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

O, se non si desidera che i dati dell'utente, iniziare con i Post:

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

Alcune persone non piace usare il virtuale ICollections, o si utilizza una versione di entity framework non supporta questa. In questo caso, dovrete fare il Join di te:

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

Soluzione senza database normalizzato

Se davvero non si può convincere il vostro capo progetto di un adeguato database consentirà di evitare un sacco di problemi in futuro, prendere in considerazione per creare un testo SQL ottenere la corretta post per voi.

Il DbContext rappresenta l'attuale stato di attuazione del database. Ha descritto le tabelle e le relazioni tra le tabelle. Aggiunta di un metodo per recuperare il Post di un utente, mi pare, una legittima metodo per il DbContext.

My SQL è un po ' arrugginito, saprete meglio di me come fare questo in SQL. Credo che si otterrà il nocciolo della questione:

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

Grazie per la tua risposta. Sì, sarà facile se mi normalizzare il database, ma è un vecchio sistema, ho appena fatto un semplice esempio per descrivere il problema che ho avuto, nel progetto in questo campo si riferisce a un sacco di cose, hanno bisogno di più tempo per effettuare il refactoring mentre io voglio solo aggiornamento rapido (hotfix) è ora. Comunque, grazie per la tua risposta.
Jihai
1

Ecco una possibile soluzione, se non è possibile normalizzare:

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

In altre lingue

Questa pagina è in altre lingue

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