更新数据库集合的最佳策略

时间:2013-07-10 10:27:42

标签: c# collections domain-driven-design

我们正在开发一个使用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集合。 ProjectAddParticipantUser方法,嗯,你得到它。

我们发现的问题如下。当项目实例发送到存储库以在数据库上进行更新时,RemoveParticipantUser ID集合可能从上次检索时发生了变化:可能已添加了一些ID,某些ID可能已被删除,有些可能完全相同。我们可以使用强力方法并从数据库中删除此项目的所有参与者用户,然后重新插入当前用户,但我想探索更好的方法。

您能想到哪些更好的方法,允许我只插入已添加的ID,删除已删除的ID,并保留未包含在集合中的ID?

5 个答案:

答案 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));