Wiersz Linq jądra Dotnet EF znajdują się na liście ciąg znaków oddzielony przecinkiem

0

Pytanie

Mam taki model w bazie danych:

Komunikat (postID int, identyfikatory użytkowników varchar(MAX)), przykład wiadomości (12, "1,2,3,7,9,20")

Chcę poprosić go według identyfikatora użytkownika, w tej chwili używam to:

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

Ale problem w tym, że jeśli celem jest równa 1, to zwraca również komunikat z identyfikatorami użytkowników = "15,16". Staram się korzystać z wyrażeń regularnych, takich jak Regex.IsMatch(_.UserIds, $"\\b{targetId}\\b") ale SQL nie może to tłumaczyć.

Czy istnieje jakiś sposób rozwiązać tą sprawę?

2
1

W ten sposób w bazie danych znajduje się tabela zawierająca Posts. Każdy Post wydaje się, że wysłany przez zero lub więcej (może być jeden lub więcej) Użytkowników. Wydaje mi się, że masz również tabela Users. Każdy User opublikował zero lub więcej Posts.

Wydaje mi się, że istnieje powiązanie "wiele do wielu" między Users i Posts: Każdy Użytkownik opublikował zero lub więcej wiadomości; każdy wpis został opublikowany zerem (jednym?) lub wieloma Użytkownikami.

Zazwyczaj w bazie danych implementowania relacja wiele-do-wielu za pomocą specjalnej tabeli: tabela połączeń.

Nie używasz kabla stół. Baza danych nie jest unormowana. Być może, swojej obecnej problem można rozwiązać bez zmiany bazy danych, ale widzę, że wiele problemów, które trzeba rozwiązać, może nie teraz, ale w niedalekiej przyszłości: jaką ogromną pracę trzeba wykonać, jeśli chcesz usunąć tego użytkownika? Jak masz wszystkie Wiadomości, które opublikował użytkownik [10]", I co zrobić, jeśli Użytkownik [10] już nie chce być na liście publikacji postu [23]? Jak zapobiegać, aby ten użytkownik [10] dwa razy wspomniano w poście[23]:

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

Normalizacja bazy danych

Pomyśl o tym, aby zaktualizować bazę danych tabeli połączeń i pozbyć się kolumny string Post.UserIds. To by rozwiązało wszystkie te problemy <url>.

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

I łączący stół:

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

Uwaga: [Identyfikator użytkownika, postID] jest wyjątkowy. Użyj tego klucza głównego

W entity framework kolumny tabeli podane są невиртуальными właściwości. Wirtualne właściwości odzwierciedlają relacje między tabelami (jeden do wielu, wiele do wielu).

Uwaga: klucz obcy-to prawdziwy kolumnę w tabeli, dlatego, klucz obcy nie jest wirtualny.

Aby skonfigurować "wiele do wielu", można użyć 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});
}

Wracając do twojego problemu

Jak tylko umieszczasz tabeli połączeń, żądanie danych będzie proste:

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

Lub, jeśli nie potrzebujesz żadnych danych użytkownika, zacznij z wiadomości:

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

Niektórym ludziom nie podoba się korzystać z wirtualnych kolekcji ICollections, lub korzystają z wersji entity framework, która tego nie obsługuje. W tym przypadku trzeba będzie dołączyć samodzielnie:

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

Rozwiązanie bez нормализованной bazy danych

Jeśli naprawdę nie możesz przekonać kierownika projektu w tym, że właściwa baza danych uniknąć wielu problemów w przyszłości, pomyśl o tym, aby stworzyć tekst SQL, który będzie zawierać właściwe wiadomości dla ciebie.

Twój DbContext reprezentuje bieżącą realizację bazy danych. W nim zostały udokumentowane tabel i relacji pomiędzy tabelami. Dodanie metody do pobierania wiadomości użytkownika wydaje mi się uzasadniony metodą DbContext.

Mój SQL trochę zardzewiały, będziesz wiedział o wiele lepiej ode mnie, jak to zrobić w SQL. Myślę, że zrozumiesz istotę:

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

Dziękuję za odpowiedź. Tak, to będzie łatwe, jeśli нормализую bazy danych, ale to stary system, po prostu doprowadził prosty przykład, aby opisać problem z jakim się spotkałem, w projekcie pole to odnosi się do wielu rzeczy, trzeba więcej czasu, aby ją refaktoryzacji, podczas gdy ja po prostu chcę ją naprawić teraz. W każdym razie, dziękuję za odpowiedź.
Jihai
1

Oto rozwiązanie, jeśli nie można go unormować:

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

W innych językach

Ta strona jest w innych językach

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