【问题标题】:Entity Framework GetOrCreate?实体框架 GetOrCreate?
【发布时间】: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 覆盖。这里是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. 获取模板

我不太担心最坏的情况,但我担心最好的情况,因为往返次数会随着我的对象图的复杂性而增加。如果我有其他引用模板表中某些内容的内容,我现在最多可以进行 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


【解决方案1】:

存储过程将在一次调用数据库和一次事务中完成所有步骤。查看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;

【讨论】:

  • 我正在寻找一种从实体框架中执行此操作的方法。您的建议是否可以从 Entity Framework 中获得?
  • 是的,我使用这种方法来执行存储过程,如我编辑的答案所示。
  • 如果我正确理解您在这里所做的事情,那并不是真正利用实体框架。它只是对数据库进行常规 SQL 调用。如果我要走这条路,我还不如放弃 Entity Framework 并使用传统的 SQL。特别是,msdn.microsoft.com/en-us/data/jj592907.aspx 说:“从数据库上的 SqlQuery 返回的结果永远不会被上下文跟踪,即使对象是实体类型的实例。”所以这样做可能会导致长期维护问题。
猜你喜欢
  • 2017-07-07
  • 2020-04-11
  • 1970-01-01
  • 2015-12-26
  • 2014-04-01
  • 2014-11-11
  • 2021-05-01
  • 1970-01-01
  • 2010-10-07
相关资源
最近更新 更多