我在資料庫中有一個這樣的模型:
Post (PostId int, UserIds varchar(MAX)),示例 Post (12, "1,2,3,7,9,20")
我想通過 UserId 查詢它,現在,我使用這個:
DBContext.Posts.Where(_ => _.UserIds.Contains(targetId)).ToList();
但問題是,如果目標是 1,它也會回傳 Post with UserIds = "15,16" 我嘗試使用 Regex 之類的,Regex.IsMatch(_.UserIds, $"\\b{targetId}\\b")但 SQL 無法翻譯它。
有什么辦法可以解決這個案子嗎?
uj5u.com熱心網友回復:
所以你的資料庫有一個填充了Posts. 每個Post似乎都是由零個或多個(可能是一個或多個)用戶發布的。在我看來,您還有一張Users. 每個User都張貼了零個或多個Posts。
在我看來,Users和之間存在多對多關系Posts:每個用戶都發布了零個或多個帖子;每個帖子都由零(一個?)或更多用戶發布。
通常在資料庫中,您會使用一個特殊的表:連接表來實作多對多關系。
您不使用聯結表。您的資料庫未規范化。也許您當前的問題可以在不更改資料庫的情況下解決,但我看到您必須解決的問題太多了,也許不是現在,但在不久的將來:如果要洗掉用戶,您需要做哪些大量作業?你如何獲得所有“用戶 [10] 發布的帖子” 如果用戶 [10] 不想在帖子 [23] 的發布串列中再被提及怎么辦?如何防止用戶 [10] 在帖子 [23] 中被提及兩次:
UserIds = 10, 3, 5, 10, 7, 10
規范化資料庫
考慮使用聯結表更新資料庫并去掉字串列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;}
}
注意:[UserId, PostId] 是唯一的。使用這個作為主鍵
在物體框架中,表的列由非虛擬屬性表示。虛擬屬性反映表之間的關系(一對多、多對多)
注意:外鍵是表中的真實列,因此外鍵是非虛擬的。
要配置多對多,您可以使用 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,或者他們使用不支持此功能的物體框架版本。在這種情況下,您必須自己進行 Join:
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);
}
uj5u.com熱心網友回復:
如果您無法對其進行標準化,這是一個可能的解決方案:
var sql = "select PostId,UserIds from Post";
sql = $" outer apply string_split(UserIds,',') where value={targetId}";
DBContext.Posts.FromSqlRaw(sql).ToList();
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/364663.html
標籤:sql-server 实体框架 林克 实体框架核心
