如何使用AWS Go SDK仅更新DynamoDB中的单个字段

huangapple go评论88阅读模式
英文:

How to only update a single field in DynamoDB using AWS Go SDK

问题

希望有人能对我一直在思考的这个问题提供一些指导。

我在 Dynamo 中有一个表,我们称之为 people,在这个表中,我有 id 作为分区键,namelastNamestatus 作为属性。
我希望能够只更新单个属性或者除了 ID 之外的所有属性。
现在,我是这样做的。我创建了以下结构体:

type PersonUpdate struct {
	FirstName string `json:"firstName,omitempty"`
	LastName  string `json:"lastName,omitempty"`
	Status    string `json:"status,omitempty"`
}

从服务器发送的请求只是更新人的姓氏,所以我们的请求体如下所示:

{
	"lastName": "bob"
}

在我们将请求绑定到结构体之后,发送到 Dynamo 的对象如下所示:

{
	"firstName": "",
	"lastName": "bob",
	"status": ""
}

现在当写入 Dynamo 时,你可以看到,只有一个属性应该被更新,而其余的空/空值应该被忽略。

执行此操作的代码可以简化为以下步骤:

// 将对象编组
_, err := dynamodbattribute.MarshalMap(person)
if err != nil {
	fmt.Println("some error marshaling")
}
// 创建更新输入
input := &dynamodb.UpdateItemInput{
	Key: map[string]*dynamodb.AttributeValue{
		"id": {
			S: aws.String("1234"),
		},
	},
	TableName: aws.String("people"),
	ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
		":fn": {
			S: aws.String(person.FirstName),
		},
		":ln": {
			S: aws.String(person.LastName),
		},
		":st": {
			S: aws.String(person.Status),
		},
	},
	UpdateExpression: aws.String("set firstName = :fn, lastName = :ln, status = :st"),
	ReturnValues:     aws.String("UPDATED_NEW"),
}
// 发送更新请求到 Dynamo
_, err := service.UpdateItem(input)

现在,更新没有问题,问题是 firstNamestatus 的值为 null 也被传递了。我尝试阅读他们的文档,但更加困惑了。我知道 Java SDK 允许你传递一个名为 UPDATE_SKIP_NULL_ATTRIBUTES 的标志,它允许你跳过那些空值,只更新那些有数据的值。我不知道在 Go 中的等效方法是什么,任何帮助/指导都将是很好的。

更新:

尝试使用以下模型:

type UserModel struct {
	FirstName string `json:"firstName,omitempty" dynamodbav:"firstName,omitempty"`
	LastName  string `json:"lastName,omitempty" dynamodbav:"lastName,omitempty"`
}

按照 @jarmod 和 @fedonev 给出的建议,从逻辑上和使用文档来看,这应该是有效的,但不幸的是,它没有起作用。

决定将 SDK 从 V1 切换到 V2,看看是否更新会有帮助,但我仍然陷入同样的困境。这是我的更新函数的样子。

update := expression.Set(expression.Name("firstName"), expression.Value(user.FirstName))
update.Set(expression.Name("lastName"), expression.Value(user.LastName))

expr, err := expression.NewBuilder().WithUpdate(update).Build()

if err != nil {
	"log error here..."
}

_, err = svc.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
	TableName:                 aws.String("people"),
	Key:                       map[string]types.AttributeValue{"id": id},
	ExpressionAttributeNames:  expr.Names(),
	ExpressionAttributeValues: expr.Values(),
	UpdateExpression:          expr.Update(),
	ReturnValues:              types.ReturnValueUpdatedNew,
})

if err != nil {
	"log error here..."
}

最终,我通过编写一个查询和创建函数来解决了我的问题,该函数基本上通过我们想要更新的 ID 进行查询,接受 JSON 负载,并将查询与我们需要更新的内容进行比较,任何空字段都将被查询结果替换。到目前为止,它解决了我的问题,但我仍然想知道如何使用其预期的函数进行更新。

英文:

hope someone can shed some light on this issue I've been trying to wrap my head around.

I've got this table in Dynamo, let's call it people and in this table, I've got the attributes of id as our partition key, name, lastName, and status.
I'd like to be able to just update either a single attribute or all of them save for the ID.
Now, this is how I've gone about it. I've created the following struct:

type PersonUpdate struct {
	FirstName     string `json:"firstName,omitempty"`
	LastName      string `json:"lastName,omitempty"`
	Status        string `json:"status,omitempty"`
}

And the request coming from the server would be to just update the person's last name, so our request body would look as follows:

{
	"lastName": "bob"
}

After we bind our request to our struct the object that would be sent to dynamo looks as as such:

{
	"firstName": "",
	"lastName": "bob",
	"status": "",
}

Now when it's time to write to dynamo as you can see, only one attribute should be updated while the rest which are empty/null should be ignored.

The code written to perform this action can be condensed to the following actions:

	// Marshal our object
	_, err := dynamodbattribute.MarshalMap(person)
	if err != nil{
		fmt.Println("some error marshaling")
	}
	// Create our update input
	input := &dynamodb.UpdateItemInput{
		key: map[string]*dynamodb.AttributeValue{
			"id":{
				S: aws.String("1234"),
			},
		},
		TableName: aws.String("people"),
		ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
			":fn": {
				S: aws.String(person.FirstName),
			},
			":ln":{
				S: aws.String(person.LastName),
			},
			":st":{
				S: aws.String(person.Status),
			},
		},
		UpdateExpression: aws.String("set firstName = :fn, lastName = :ln, status = :st"),
		ReturnValues: aws.String("UPDATED_NEW"),
	}
	// Send update request to Dynamo
	_, err := service.UpdateItem(input)

Now, the update goes through with no issues, the problem is that the values for firstName and status which are null are getting passed as well. I've tried to go through their documentation but left a bit more confused. I know for a fact the Java SDK allows you to pass a flag called UPDATE_SKIP_NULL_ATTRIBUTES which allows you to skip those empty values and only update those which have data. I don't know what would be the equivalent in Go, any assistance/guidance would be great.

Update:

Tried to use the following model:

type UserModel struct {
	FirstName string `json:"firstName,omitempty" dynamodbav:"firstName,omitempty"`
	LastName  string `json:"lastName,omitempty" dynamodbav:"lastName,omitempty"`
}

following the suggestion given by @jarmod and @fedonev. Logically and using the docs it makes sense why this should work, unfortunately, it didn't

Decided to switch the SDK from V1 to V2 and see if maybe updating it would help, again though I'm in the same hole. This is what my update function looks like.

    update :=expression.Set(expression.Name("firstName"),expression.Value(user.FirstName))
	update.Set(expression.Name("lastName"), expression.Value(user.LastName))

	expr, err := expression.NewBuilder().WithUpdate(update).Build()

	if err != nil {
		"log error here..."
	}

	_, err = svc.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
		TableName:                 aws.String("people"),
		Key:                       map[string]types.AttributeValue{"id": id},
		ExpressionAttributeNames:  expr.Names(),
		ExpressionAttributeValues: expr.Values(),
		UpdateExpression:          expr.Update(),
		ReturnValues:              types.ReturnValueUpdatedNew,
	})

	if err != nil {
		"log error here..."

	}

I ended up solving my problem by writing a query-and-create function, which pretty much queries by the ID that we'd like to update takes in the JSON payload, and make a diff of the query against what we need to update and any empty fields get replaced by the query results. So far it solves my problem but I still would like to know how to go about doing an update using its intended function.

答案1

得分: 2

根据 @jarmod 的评论,当将结构体进行编组时,可以使用 dynamodbav:",omitempty" 标签来跳过零值字段。

type PersonUpdate struct {
    FirstName     string `json:"firstName,omitempty" dynamodbav:",omitempty"`
    LastName      string `json:"lastName,omitempty" dynamodbav:",omitempty"`
    Status        string `json:"status,omitempty" dynamodbav:",omitempty"`
}

[编辑:添加用法] MarshalMap 现在会忽略零值字段,并遵循标签。遍历映射以构建更新表达式:

av, _ := attributevalue.MarshalMap(person)

update := expression.UpdateBuilder{}

for k, v := range av {
        update = update.Set(expression.Name(k), expression.Value(v))
}

构建表达式并将其输出传递给 UpdateItem,就像在原始问题中一样。

英文:

As @jarmod's comment says, apply the dynamodbav:",omitempty" tag to skip zero-value fields when marshaling the struct:

type PersonUpdate struct {
    FirstName     string `json:"firstName,omitempty" dynamodbav:",omitempty"`
    LastName      string `json:"lastName,omitempty" dynamodbav:",omitempty"`
    Status        string `json:"status,omitempty" dynamodbav:",omitempty"`
}

[Edit: add usage] MarshalMap will now ignore zero-value fields, respecting the tags. Iterate over the map to construct the update expression:

av, _ := attributevalue.MarshalMap(person)

update := expression.UpdateBuilder{}

for k, v := range av {
        update = update.Set(expression.Name(k), expression.Value(v))
}

Build the expression and pass its outputs to UpdateItem, as in the OP.

huangapple
  • 本文由 发表于 2022年12月15日 10:23:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/74806198.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定