我们正在开发一个使用C#4和SQL Server 2008 R2的系统,并遵循域驱动的设计原则。没有使用ORM。在某些情况下,我们有一个实体,其中包含另一个实体的集合,例如:
public class Project
{
public IdCollection ParticipantUsers
{
get
{
//...
}
}
public void AddParticipantUser(Id idUser)
{
//...
}
public void RemoveParticipantUser(Id idUser)
{
//...
}
}
ParticipantUsers
的{{1}}属性返回参与其中的用户的ID集合。 Project
和AddParticipantUser
方法,嗯,你得到它。
我们发现的问题如下。当项目实例发送到存储库以在数据库上进行更新时,RemoveParticipantUser
ID集合可能从上次检索时发生了变化:可能已添加了一些ID,某些ID可能已被删除,有些可能完全相同。我们可以使用强力方法并从数据库中删除此项目的所有参与者用户,然后重新插入当前用户,但我想探索更好的方法。
您能想到哪些更好的方法,允许我只插入已添加的ID,删除已删除的ID,并保留未包含在集合中的ID?
答案 0 :(得分:2)
实际上我认为delete-reinsert方法比任何基于比较的解决方案都要好。
首先,它很容易实现。
其次,它避免了另一个数据提取以进行比较。
你能否进一步解释为什么这种方法不是首选的,也许我没有理解。
答案 1 :(得分:2)
我认为如果你做了很多这样的事情,事件采购将是理想的,但通常我们作为开发人员不会多使用新技术:)
由于我不是ORM的粉丝,我之前所做的只是简单地按照工作单元的相同行保存变更列表。因此,每次添加参与者时,我都会将其添加到AddedParticipants
集合中,对于已删除的参与者,我会将其添加到RemovedParticipants
集合中。我想有人可以将它进一步扩展到每个条目的变化类型的单个列表。但我相信你明白了。
然后,存储库将使用跟踪的更改来执行相关的数据操作。可能不是最漂亮的解决方案,但它可以完成工作。
我可能会补充一点,对于较短的列表,我只是删除并重新插入,但正如您所提到的,在这种情况下它可能有点沉重,并且一种尺寸并不适合所有:)
当然,重新加载列表和比较会像Arie建议的那样正常工作。
答案 2 :(得分:1)
我们的方法(使用SQL Server 2008 R2)是将表值参数(TVP)与合并语句结合使用。
这样,代码将项列表作为表传递给存储过程。然后,proc在合并语句中使用该表,该语句执行insert / update / delete。
您需要用户定义的表类型,例如:
CREATE TYPE [dbo].[Participants] AS TABLE(
[ProjectId] [int] NOT NULL,
[ParticipantId] [int] NOT NULL,
PRIMARY KEY CLUSTERED
(
[ProjectId] ASC,
[ParticipantId] ASC
)WITH (IGNORE_DUP_KEY = OFF)
)
GO
然后你需要一个C#类来保存数据。只需将IdCollection
修改为类似于:
public class IdCollection : List<ParticipantUsers>, IEnumerable<SqlDataRecord> {
#region IEnumerable<SqlDataRecord> Members
IEnumerator<SqlDataRecord> IEnumerable<SqlDataRecord>.GetEnumerator() {
SqlDataRecord rec = new SqlDataRecord(
new SqlMetaData("ProjectId", System.Data.SqlDbType.Int),
new SqlMetaData("ParticipantId", System.Data.SqlDbType.Int)
);
foreach (ParticipantUser ans in this) {
rec.SetInt32(0, ans.ProjectId);
rec.SetInt32(1, ans.ParticipantId);
yield return rec;
}
}
#endregion
}
您的存储过程如下所示:
CREATE PROCEDURE [dbo].[ParticpantUpdate]
@Particpants core.Participants READONLY
AS
BEGIN
SET NOCOUNT ON;
MERGE INTO dbo.ProjectParticipants pp
USING @Particpants RG
ON (RG.ProjectId = pp.ProjectId)
AND (RG.ParticipantId = pp.ParticipantId)
WHEN NOT MATCHED BY TARGET THEN
INSERT( ProjectId, ParticipantId)
VALUES( RG.ProjectId, RG.ParticipantId)
WHEN NOT MATCHED BY SOURCE
AND target.ProjectId in (SELECT ProjectId from @Participants) THEN
DELETE;
;
END
调用proc(使用Enterprise Library):
SqlDatabase db = DatabaseFactory.CreateDatabase("myconnstr") as SqlDatabase;
using ( DbCommand dbCommand = db.GetStoredProcCommand("dbo.ParticipantUpdate") ) {
db.AddInParameter(dbCommand, "Participants", SqlDbType.Structured, participantList);
db.ExecuteNonQuery(dbCommand);
} // using dbCommand
答案 3 :(得分:1)
您是否考虑将所有ID序列化为单个列?这样,您只需更新一条记录。如果您担心同时更改了集合,则可能需要实现乐观并发。
答案 4 :(得分:0)
orgData - 是包含来自数据库的原始数据的列表/表/其他可枚举对象(在此情况下更改之前的IdCollection ParticipantUsers)
newData - 是包含要更新的数据的列表/表/其他可枚举对象
var orgList = orgData.Select(a => a.Id);
var newList = newData.Select(a => a.Id);
var itemsToAdd = newData.Where(a => !orgList.Contains(a.Id));
var itemsToDel = orgData.Where(a => !newList.Contains(a.Id));