Dotnet EF Core Lınq dizesi virgülle ayrılmış bir liste dizesinde içerir

0

Soru

Veritabanında böyle bir model var:

Post (PostId int, Kullanıcı adları varchar(MAKS.)), örnek Post (12, "1,2,3,7,9,20")

UserId ile sorgulamak istiyorum, şimdilik bunu kullanıyorum:

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

Ancak sorun şu ki, hedef 1 ise, UserIds = "15,16"ile Postayı da döndürür. Regex'i kullanmaya çalışıyorum Regex.IsMatch(_.UserIds, $"\\b{targetId}\\b") ancak SQL bunu çeviremez.

Bu davayı çözmenin bir yolu var mı?

2
1

Yani veritabanınızda aşağıdakilerle dolu bir tablo var Posts. Her Post sıfır veya daha fazla (belki bir veya daha fazla) Kullanıcı tarafından gönderilmiş gibi görünüyor. Bana öyle geliyor ki, senin de bir masan var. Users. Her User sıfır veya daha fazla yayınladı Posts.

Bana öyle geliyor ki, aralarında çok-çok bir ilişki var Users ve Posts: Her Kullanıcı sıfır veya daha fazla Gönderi gönderdi; her Gönderi sıfır tarafından gönderildi (bir?) veya daha fazla Kullanıcı.

Normalde bir veritabanında özel bir tabloyla çok-çok ilişkisi uygularsınız: birleşim tablosu.

Bağlantı tablosunu kullanmıyorsun. Veritabanınız normalleştirilmedi. Belki şu anki sorununuz veritabanını değiştirmeden çözülebilir, ancak çözmeniz gereken pek çok sorun görüyorum, belki şimdi değil, yakın gelecekte: bir kullanıcıyı silmek istiyorsanız ne kadar büyük bir çalışma yapmanız gerekir? "Kullanıcının [10] yayınladığı tüm Gönderileri" nasıl elde edersiniz Ve Kullanıcı [10] artık Yayın listesinde [23] belirtilmek istemiyorsa ne olur? Bu Kullanıcının nasıl önleneceği [10] Post'ta iki kez belirtilmiştir[23]:

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

Veritabanını normalleştir

Veritabanını bir bağlantı tablosuyla güncellemeyi ve dize sütunundan kurtulmayı düşünün Post.UserIds. Bu, tüm bu sorunları bir kerede çözecektir.

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

Ve bağlantı tablosu:

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

Not: [UserId, PostId] benzersizdir. Bunu birincil anahtar olarak kullanın

Entity framework'te tabloların sütunları sanal olmayan özelliklerle temsil edilir. Sanal özellikler tablolar arasındaki ilişkileri yansıtır (bire-çok, çok-çok)

Not: yabancı anahtar tablodaki gerçek bir sütundur, dolayısıyla yabancı anahtar sanal değildir.

Çok-çok yapılandırmak için Fluent API'sini kullanabilirsiniz:

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

Sorununuza geri dönün

Bağlantı tablosunu uyguladıktan sonra veri isteğiniz kolay olacaktır:

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

Veya herhangi bir kullanıcı verisi istemiyorsanız, Gönderilerle başlayın:

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

Bazı insanlar sanal koleksiyonları kullanmaktan hoşlanmazlar veya bunu desteklemeyen entity framework'ün bir sürümünü kullanırlar. Bu durumda, Katılmayı kendiniz yapmanız gerekecek:

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

Normalleştirilmiş veritabanı olmadan çözüm

Proje liderinizi uygun bir veritabanının gelecekte birçok sorunu önleyeceğine gerçekten ikna edemiyorsanız, sizin için uygun gönderileri alan bir SQL metni oluşturmayı düşünün.

Dbcontext'iniz veritabanınızın geçerli uygulamasını temsil eder. Tablolar ve tablolar arasındaki ilişkileri tanımladı. Bir kullanıcının Gönderilerini almak için bir yöntem eklemek bana DbContext için okunaklı bir yöntem gibi görünüyor.

Sql'im biraz paslı, bunu sql'de nasıl yapacağımı benden daha iyi bileceksin. Sanırım özünü anlayacaksın.:

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

Teşekkürler cevap için. Evet, veritabanını normalleştirirsem kolay olacak, ancak eski bir sistem, içine girdiğim sorunu tanımlamak için basit bir örnek oluşturdum, projede bu alan birçok şeyle ilgili, yeniden düzenlemek için daha fazla zamana ihtiyacım var. şimdi düzeltmek istiyorum. Her neyse, cevabın için teşekkürler.
Jihai
1

Normalleştiremiyorsanız olası bir çözüm:

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

Diğer dillerde

Bu sayfa diğer dillerde

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