3

我们正在使用 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属性Project返回参与其中的用户 id 的集合。和方法AddParticipantUserRemoveParticipantUser好吧,你明白了。

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

你能想到什么更好的方法,允许我只插入那些已添加的 id,删除那些已删除的,而保留那些仍然存在于集合中的那些?

4

5 回答 5

2

实际上,我认为删除重新插入方法比任何基于比较的解决方案都要好。

首先,它易于实施。

其次,它避免了另一个数据获取进行比较。

您能否进一步解释为什么这种方法不受欢迎,也许我没有听懂。

于 2013-07-10T14:25:26.843 回答
2

我想如果您做很多此类事情,事件溯源将是理想的,但通常我们,作为开发人员,不会使用太多新技术:)

因为我不是 ORM 的粉丝,所以我之前所做的只是按照与工作单元相同的方式保留更改列表。因此,每次添加参与者时,我都会将其添加到AddedParticipants集合中,对于已删除的参与者,我会将其添加到RemovedParticipants集合中。我想人们可以把它带到一个单独的列表中,其中每个条目的更改类型。但我相信你明白了。

然后,存储库将使用跟踪的更改来执行相关的数据操作。可能不是最漂亮的解决方案,但它可以完成工作。

我可能会补充一点,对于较短的列表,我只是删除并重新插入,但是,正如您所提到的,在这种情况下它可能有点重,而且一种尺寸并不适合所有人:)

当然,正如 Arie 所建议的那样,重新加载列表和比较就可以了。

于 2013-07-10T16:39:04.237 回答
1

我们的方法(使用 SQL Server 2008 R2)是结合使用表值参数 (TVP) 和合并语句。

这样,代码将项目列表作为表传递给存储过程。然后,proc 在执行插入/更新/删除的合并语句中使用该表。

您需要一个用户定义的表类型,例如:

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
于 2013-07-10T17:42:20.387 回答
1

您是否考虑过将所有 id 序列化为一列?这样,您只需更新一条记录。如果您担心集合同时发生变化,您可能希望实现乐观并发。

于 2013-07-11T01:44:54.643 回答
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));
于 2013-07-10T11:43:55.917 回答