Строка Linq ядра Dotnet EF содержит в списке строку, разделенную запятой

0

Вопрос

У меня есть такая модель в базе данных:

Сообщение (postID int, идентификаторы пользователей varchar(МАКС)), пример сообщения (12, "1,2,3,7,9,20")

Я хочу запросить его по идентификатору пользователя, на данный момент я использую это:

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

Но проблема в том, что если цель равна 1, она также возвращает сообщение с идентификаторами пользователей = "15,16". Я стараюсь использовать регулярные выражения, такие как Regex.IsMatch(_.UserIds, $"\\b{targetId}\\b") но SQL не может это перевести.

Есть ли какой-нибудь способ раскрыть это дело?

2
1

Таким образом, в вашей базе данных есть таблица, заполненная Posts. Каждый Post кажется, что опубликовано ноль или больше (может быть, один или несколько) Пользователи. Мне кажется, что у вас также есть таблица Users. Каждый User опубликовал ноль или более Posts.

Мне кажется, что существует связь "многие ко многим" между Users и Posts: Каждый Пользователь опубликовал ноль или более сообщений; каждое сообщение было опубликовано нулем (одним?) или несколькими Пользователями.

Обычно в базе данных вы реализуете отношение "многие ко многим" с помощью специальной таблицы: таблицы соединений.

Вы не используете соединительный стол. Ваша база данных не нормализована. Возможно, вашу текущую проблему можно решить без изменения базы данных, но я вижу так много проблем, которые вам придется решить, возможно, не сейчас, а в ближайшем будущем: какую огромную работу вам нужно будет выполнить, если вы хотите удалить пользователя? Как вы получаете все "Сообщения, которые опубликовал пользователь [10]", И что делать, если Пользователь [10] больше не хочет упоминаться в списке публикаций поста [23]? Как предотвратить, чтобы этот пользователь [10] дважды упоминался в посте[23]:

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

Нормализовать базу данных

Подумайте о том, чтобы обновить базу данных таблицей соединений и избавиться от столбца string 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;}
}

Примечание: [Идентификатор пользователя, postID] уникален. Используйте этот первичный ключ

В entity framework столбцы таблиц представлены невиртуальными свойствами. Виртуальные свойства отражают отношения между таблицами (один ко многим, многие ко многим).

Примечание: внешний ключ-это реальный столбец в таблице, следовательно, внешний ключ не является виртуальным.

Чтобы настроить "многие ко многим", вы можете использовать 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, или они используют версию entity framework, которая этого не поддерживает. В этом случае вам придется присоединиться самостоятельно:

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

Спасибо за ваш ответ. Да, это будет легко, если я нормализую базу данных, но это старая система, я просто привел простой пример, чтобы описать проблему, с которой я столкнулся, в проекте это поле относится ко многим вещам, нужно больше времени для ее рефакторинга, пока я просто хочу исправить ее сейчас. В любом случае, спасибо за ваш ответ.
Jihai
1

Вот возможное решение, если вы не можете его нормализовать:

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

На других языках

Эта страница на других языках

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

Популярное в этой категории

Популярные вопросы в этой категории