【发布时间】:2016-03-06 04:11:32
【问题描述】:
从这里开始是我的桌子。不是很有趣,只是一个主键、数据和外键连接它们。一切都有一个独特的约束。
CREATE TABLE [dbo].[Providers] (
[id] BIGINT IDENTITY (1, 1) NOT NULL,
[name] NVARCHAR (850) NOT NULL,
CONSTRAINT [PK_Providers] PRIMARY KEY CLUSTERED ([id] ASC),
CONSTRAINT [UQ_Providers_name] UNIQUE NONCLUSTERED ([name] ASC)
);
CREATE TABLE [dbo].[Templates] (
[id] BIGINT IDENTITY (1, 1) NOT NULL,
[provider] BIGINT NOT NULL,
[repositoryId] NVARCHAR (842) NOT NULL,
CONSTRAINT [PK_Templates] PRIMARY KEY CLUSTERED ([id] ASC),
CONSTRAINT [UQ_Templates_repositoryId] UNIQUE NONCLUSTERED ([provider] ASC, [repositoryId] ASC),
CONSTRAINT [FK_Templates_ToProviders] FOREIGN KEY ([provider]) REFERENCES [dbo].[Providers] ([id])
);
使用实体框架,我想在Templates 中插入一行。但是,我在插入时不知道Providers 中的匹配行是否存在。如果许多人同时插入(两个模板具有相同的Provider),我想对竞争条件进行防御。
我相信,为了实现这一点,我需要首先获取或创建提供程序,然后在持久化(至少一个完整的数据库往返)之后创建模板。
我想出了一个我认为可行的解决方案,但它似乎非常复杂,因为我不得不想象这是一个非常常见的场景。我希望有人能告诉我我做错了什么或者我可以如何做得更好。
以下代码位于我的 DbContext 实现中。
public async Task<Provider> GetOrCreateProvider(String name)
{
var provider = new Provider { name = name };
return await GetOrCreateEntity(Providers, provider);
}
public async Task<Template> GetOrCreateTemplate(Provider provider, String repositoryId)
{
var template = new Template { Provider = provider, repositoryId = repositoryId };
return await GetOrCreateEntity(Templates, template);
}
private async Task<T> GetOrCreateEntity<T>(DbSet<T> dbSet, T newEntity) where T : class
{
var maybeEntity = await dbSet.Where(entity => entity.Equals(newEntity)).FirstOrDefaultAsync();
if (maybeEntity != null)
return maybeEntity;
try
{
dbSet.Add(newEntity);
await SaveChangesAsync();
return newEntity;
}
catch (UpdateException exception) when ((exception.InnerException as SqlException)?.Number == 2601 || (exception.InnerException as SqlException)?.Number == 2627)
{
return await dbSet.Where(entity => entity.Equals(newEntity)).FirstOrDefaultAsync();
}
}
实体类由 VS 从我的数据库表中自动生成,并添加了 .Equals 覆盖。这里是Provider,Template 类似,但引用单个 Provider 而不是集合:
public partial class Provider
{
public Provider()
{
Templates = new HashSet<Template>();
}
public long id { get; set; }
[Required]
[StringLength(850)]
public string name { get; set; }
public virtual ICollection<Template> Templates { get; set; }
public override Boolean Equals(Object otherObject)
{
if (Object.ReferenceEquals(this, otherObject))
return true;
if (otherObject == null)
return false;
var other = otherObject as Provider;
if (other == null)
return false;
return name == other.name;
}
public override Int32 GetHashCode()
{
return name.GetHashCode();
}
}
用法如下所示:
var provider = await GetOrCreateProvider("foo");
var template = await GetOrCreateTemplate(provider, "bar");
这样做的主要问题(除了执行如此简单的操作所需的样板数量之外)是我必须在最佳情况下对服务器进行两次完整的往返(当提供者和模板都已经存在)。在最坏的情况下,我必须进行 6 次往返:
- 获取提供者失败
- 约束冲突创建提供者
- 获取提供者
- 获取模板失败
- 约束冲突创建模板
- 获取模板
我不太担心最坏的情况,但我担心最好的情况,因为往返次数会随着我的对象图的复杂性而增加。如果我有其他引用模板表中某些内容的内容,我现在最多可以进行 3 次往返。
【问题讨论】:
-
如何创建实体类?你可以给他们看吗?
-
new Provider("foo") -
我还稍微简化了代码,以便您现在可以更清楚地看到
new Provider("foo")。 -
我现在意识到你的意思是类本身。我已经用我的一门课进行了编辑。它是由 Visual Studio 使用新的 ADO.NET 实体框架向导从 DB 自动创建的。
-
在开始使用 C# 编码之前,我总是会从 SQL 的角度考虑这一点。如果我纯粹在 SQL 中执行此操作,我是否必须担心上述所有步骤?如果是这样,当我在 EF 中执行相同操作时,我将不得不考虑所有这些步骤。
标签: c# entity-framework entity-framework-6 azure-sql-database