从这里开始是我的桌子。不是很有趣,只是连接它们的主键,数据和外键。一切都有一个独特的约束。
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])
);
使用Entity Framework,我想在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次往返。
答案 0 :(得分:0)
存储过程将在一次调用数据库和单个事务中执行所有步骤。请查看merge
命令,例如introduction或reference。
您可以使用此方法从EF执行存储过程:
private static int ExecuteSqlCount(string statement, SqlParameter[] paramsSql)
{
using (Entities dbContext = new Entities())
{
var total = dbContext.Database.SqlQuery<int>(statement, paramsSql).First();
return total;
}
}
返回值是USP返回的任何值。该方法被称为(对于返回int的USP):
var parameters = new List<SqlParameter>();
string statement = "exec uspPersonAdd @personName = @name, @activeFlag = @active";
parameters.Add(new SqlParameter("@name", person.PersonName));
parameters.Add(new SqlParameter("@active", person.Active));
int id = ExecuteSqlCount(statement, parameters.ToArray());
您需要using System.Data.SqlClient;