英文:
Entity Framework Core CosmosDB upsert (update/insert) recognition
问题
我有一个一段时间前构建的包,它与EF创建的SQL数据库非常配合。最近我尝试使用它与CosmosDB和GUID“主键”一起使用。这是我第一次接触Cosmos。
在保存记录时,我会检查主键的值:如果它是该类型的默认值,我认为它是插入操作;如果不是,默认为更新操作。
我模糊地记得出于性能原因这样做,所以在更新记录之前我不会从数据库中获取它们。似乎没有内置的upsert功能?
我相信在这里插入CosmosDB时,Id
属性会被填充,因为CosmosDB不会生成自己的Id,而是依赖EF提供。这是合理的,但打破了我的约定。
我想最重要的问题是,有没有更好的方法来处理这里的“upsert”,而不依赖于Id
是否未设置?
更多信息
传入的TModel data
是来自用户请求,此时我们:
- 还没有从数据库中获取任何记录
- 上下文中没有其他待更改的记录
- 还有另一个几乎相同的函数,它添加了一系列数据,然后调用
SaveChanges
,所以我们在这里是相当谨慎的工作负载
之所以在这里使用upsert模式,是因为用户有一个单一的API端点用于保存数据,无论是新记录还是现有记录。
我们实际上是通过告诉数据库“这是更新”或“这是插入”来绕过了DB upsert的性能问题,这个方法简单地被分解了,因为对于插入的记录,CosmosDB在这一点上生成了Id
,而在SQL版本中这不是一个问题。
看起来我可能需要更深入地研究CosmosDB的内部和它们与EF的交互。
英文:
I have a package I built a while back that works with EF-created SQL databases just great. I've recently had a call to try to use with CosmosDB and guid "primary keys". My first foray into Cosmos.
When saving records I'm checking the value of the primary key: if it's the default value for the type, I consider it an insert; if it's not, then I consider it an update.
public virtual TModel Save(TModel data)
{
_dynamic.Add(data);
_context.Entry(data).State =
data.Id.Equals(default(TKey))
? EntityState.Added
: EntityState.Modified;
_context.SaveChanges();
return data;
}
I vaguely recall doing this for a performance reason, so I'm not pulling the records down from the database before updating them. It doesn't appear that there's a built-in upsert?
I believe the Id
property is populated here on inserts for CosmosDB because CosmosDB doesn't generate its own Ids and instead relies on EF to provide it. Which is sensible, but breaks my convention here.
I suppose the ultimate question is, is there a better way to handle "upserts" here that doesn't rely on the Id
being not-set?
More Info
The TModel data
passed in is from a user request, at this point we:
- Haven't pulled anything down from the database
- Don't have any other records with pending changes in the context
- There's another, nearly identical function that adds a range of data then calls
SaveChanges
so we are being somewhat diligent about work load here
The reason for doing the upsert pattern here is that the user has a single API endpoint that data gets saved to, whether it's a new record or an existing one.
We are essentially circumventing the DB upsert performance issues by telling it "this is an update" or "this is an insert" with this method, it's simply broken down because the Id
for an inserted record is generated at this point for CosmosDB, where that wasn't an issue with the SQL version.
It looks as though I may just need to dig deeper into the CosmosDB internals and their EF interactions.
答案1
得分: 1
Entity Framework Core CosmosDB upsert (update/insert) recognition:
>下面是一种更好的处理“upserts”的方法,它不依赖于Id
未设置。
解释:
-
在
try
块内创建了一个新的类型为TModel的数据记录,并使用存储库执行了一个upsert操作。 -
upsert操作要么创建一个新文档(如果不存在),要么更新一个现有文档。
-
在upsert方法中定义了一个查询,以确定是否存在具有指定角色的任何现有数据。
-
使用查询迭代器执行查询,并使用
FirstOrDefault()
获取第一个结果。 -
如果找不到现有数据,则为文档生成一个新的“id”,以防指定的“Role”属性没有现有数据,否则更新现有数据。
var client = new CosmosClient(cosmosEndpoint, cosmosKey);
var cosmosContainer = client.GetContainer("upsertDb", "upsertCont");
var repository = new YourRepository(cosmosContainer);
try
{
var newData = new TModel
{
Role = "FullStackDeveloper",
Name = "Balaji"
};
Console.WriteLine($"Role: {newData.Role}");
await repository.Upsert(newData);
Console.WriteLine("Upsert operation completed successfully.");
}
catch (CosmosException ex)
{
Console.WriteLine($"CosmosException: {ex.Message}");
Console.WriteLine($"Status Code: {ex.StatusCode}");
}
catch (Exception ex)
{
Console.WriteLine($"An unexpected error occurred: {ex.Message}");
}
public class YourRepository
{
private readonly Container _container;
public YourRepository(Container container)
{
_container = container;
}
public async Task Upsert(TModel data)
{
var partitionKey = new PartitionKey(data.Role);
try
{
var query = new QueryDefinition($"SELECT * FROM c WHERE c.Role = @role").WithParameter("@role", data.Role);
var queryIterator = _container.GetItemQueryIterator<TModel>(query);
var existingData = (await queryIterator.ReadNextAsync()).FirstOrDefault();
if (existingData == null)
{
data.id = Guid.NewGuid().ToString();
await _container.CreateItemAsync(data, partitionKey);
}
else
{
data.id = existingData.id; // Set the existing 'id' before upserting
await _container.UpsertItemAsync(data, partitionKey);
}
}
catch (CosmosException ex)
{
throw;
}
}
}
输出:
英文:
Entity Framework Core CosmosDB upsert (update/insert) recognition:
>Below is a better way to handle "upserts" here that doesn't rely on the Id
being not-set
Explanation:
-
A new data record of type TModel is created inside the
try
block, and an upsert operation is carried out using the repository. -
An upsert operation either creates a new document if it doesn't exist or updates an existing document.
-
A query is defined in the upsert method to determine whether any existing data with the specified Role exists.
-
A query iterator is used to perform the query, and the first result is obtained
using.FirstOrDefault()
. -
If no existing data is found, a new "id" is generated for the document in case there is no existing data with the specified "Role" property else it updates the existing data.
var client = new CosmosClient(cosmosEndpoint, cosmosKey);
var cosmosContainer = client.GetContainer("upsertDb", "upsertCont");
var repository = new YourRepository(cosmosContainer);
try
{
var newData = new TModel
{
Role = "FullStackDeveloper",
Name = "Balaji"
};
Console.WriteLine($"Role: {newData.Role}");
await repository.Upsert(newData);
Console.WriteLine("Upsert operation completed successfully.");
}
catch (CosmosException ex)
{
Console.WriteLine($"CosmosException: {ex.Message}");
Console.WriteLine($"Status Code: {ex.StatusCode}");
}
catch (Exception ex)
{
Console.WriteLine($"An unexpected error occurred: {ex.Message}");
}
public class YourRepository
{
private readonly Container _container;
public YourRepository(Container container)
{
_container = container;
}
public async Task Upsert(TModel data)
{
var partitionKey = new PartitionKey(data.Role);
try
{
var query = new QueryDefinition($"SELECT * FROM c WHERE c.Role = @role").WithParameter("@role", data.Role);
var queryIterator = _container.GetItemQueryIterator<TModel>(query);
var existingData = (await queryIterator.ReadNextAsync()).FirstOrDefault();
if (existingData == null)
{
data.id = Guid.NewGuid().ToString();
await _container.CreateItemAsync(data, partitionKey);
}
else
{
data.id = existingData.id; // Set the existing 'id' before upserting
await _container.UpsertItemAsync(data, partitionKey);
}
}
catch (CosmosException ex)
{
throw;
}
}
}
Output:
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论