实体框架GetOrCreate?

时间:2016-03-06 04:11:32

标签: c# entity-framework entity-framework-6 azure-sql-database

从这里开始是我的桌子。不是很有趣,只是连接它们的主键,数据和外键。一切都有一个独特的约束。

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覆盖。以下是ProviderTemplate类似,但引用了单个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次往返:

  1. 未能获得提供者
  2. 约束违规创建提供程序
  3. 获取提供者
  4. 无法获取模板
  5. 约束违规创建模板
  6. 获取模板
  7. 我并不十分关注最糟糕的情况,但我担心最好的情况,因为往返次数随着对象图的复杂性而增加。如果我有其他东西引用了模板表格中的内容,我现在最多可以进行3次往返。

1 个答案:

答案 0 :(得分:0)

存储过程将在一次调用数据库和单个事务中执行所有步骤。请查看merge命令,例如introductionreference

您可以使用此方法从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;