Dotnet EF Núcleo Linq cadena contiene una lista de cadena de split por comas

0

Pregunta

Tengo un modelo como este en la base de datos:

Post (PostId int, identificaciones del usuario varchar(MAX)), ejemplo de Post (12, "1,2,3,7,9,20")

Quiero consultar por id de usuario, por ahora, yo uso este:

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

Pero el problema es que si el objetivo es 1, es también volver Post con UserIds = "15,16" Trato de usar Regex como Regex.IsMatch(_.UserIds, $"\\b{targetId}\\b") pero SQL no se puede traducir.

Es cualquier forma de resolver este caso?

2
1

Por lo que su base de datos tiene una tabla llena de Posts. Cada Post parece ser publicado por cero o más (tal vez uno o más) de los Usuarios. A mí me parece, que también dispone de una tabla de Users. Cada User ha publicado cero o más Posts.

A mí me parece que hay muchos-a-muchos de relación entre Users y Posts: Cada Usuario haya publicado cero o más de los Puestos; cada Post ha sido publicado por cero (uno?) o más Usuarios.

Normalmente en una base de datos que permitiría la implementación de muchos-a-muchos relación con una mesa especial: la tabla de unión.

No utilice la tabla de unión. Su base de datos no está normalizada. Tal vez su actual problema puede ser resuelto sin necesidad de cambiar la base de datos, pero veo tantos problemas que se han de resolver, tal vez no ahora, pero en el futuro: ¿qué inmenso trabajo que debe hacer si desea eliminar un usuario? ¿Cómo conseguir que todos "los mensajes que el usuario [10] ha publicado" ¿Y si el Usuario [10] no quieren ser mencionado ya en la publicación de la lista de Post [23]? Cómo evitar que el Usuario [10] se menciona dos veces en el Post[23]:

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

Normalizar la base de datos

Considere la posibilidad de actualizar la base de datos con una tabla de unión y deshacerse de la columna de cadena Post.UserIds. Esto podría resolver todos estos problemas a la 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;}
}

Y la tabla de unión:

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

Nota: [UserId, PostId] es único. Uso de la clave principal

En el marco de entidades de las columnas de las tablas están representados por los no-propiedades virtuales. Las propiedades virtuales que reflejan las relaciones entre las tablas (uno-a-muchos, muchos-a-muchos)

Nota: una clave externa es una verdadera columna de una tabla, por lo tanto, una clave externa es no-virtual.

Para configurar muchos-a-muchos, puede utilizar la API Fluida:

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 regreso a su problema

Una vez que haya implementado la tabla de unión de su solicitud de datos va a 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(),
    });

O, si usted no desea que los datos de usuario, inicie con los Puestos de:

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

A algunas personas no les gusta usar el virtual ICollections, o que utilice una versión de entity framework que no es compatible con esta. En ese caso, usted tendrá que hacer la combinación ti mismo:

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

Solución sin necesidad de base de datos normalizado

Si realmente no puedes convencer a tu jefe de proyecto que una adecuada base de datos evitará un montón de problemas en el futuro, considere la posibilidad de crear un texto SQL que obtener el adecuado puestos para usted.

Su DbContext representa la implementación actual de la base de datos. Se describen las tablas y las relaciones entre las tablas. La adición de un método para recuperar los Posts de un usuario, me parece una de fiar método para la DbContext.

Mi SQL es un poco oxidado, usted sabe mejor que yo cómo hacer esto en SQL. Supongo que usted va a obtener la esencia:

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

Gracias por tu respuesta. Sí, será fácil si me normalizar la base de datos, pero es un sistema antiguo, acabo de hacer un ejemplo sencillo para describir el problema que se me metió en el proyecto: este campo se refiere a un montón de cosas, necesitan más tiempo para refactorizar mientras yo sólo quiero revisión ahora. De todos modos, gracias por tu respuesta.
Jihai
1

He aquí una posible solución si no se puede normalizar es:

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

En otros idiomas

Esta página está en otros idiomas

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