EF 4.1替换一行?

时间:2011-09-11 23:12:13

标签: entity-framework entity-framework-4 crud

我有一个问题,我想要在类中更新或添加一行,具体取决于数据库中存在的记录。在那里,我没有“创建/更新”方法,而是使用“保存(实体)”方法。

这个想法是对数据库检查实体,如果它存在,那么它是一个更新,如果它没有,那么它显然是一个创建。

使用EF 4.1的问题是,一旦我通过相同的上下文从数据库中读取行,那么这又会创建该行的内存缓存。当我尝试通过附加/添加例程替换行时,它显然会在已存在的行周围抛出异常(因为它试图强制currentRow和newRow进入同一个表并且在重新调整时失败)。

我的解决方法基本上是在上下文中使用.Remove(),它标记要从内存中删除的行,然后在同一个USING事务中重新添加它。

 var ctx = new SecurityContext(this.ConnectionString);
        using(ctx)
        {
            var dbEntry = (ctx.Accounts.Where(a => a.AccountId == entity.AccountId || a.Username == entity.Username)).FirstOrDefault();
            ctx.Accounts.Remove(dbEntry);

            if (dbEntry != null)
            {
                ctx.Entry(entity).State = EntityState.Added;
            } else
            {
                ctx.Accounts.Add(entity);
                ctx.Entry(entity).State = EntityState.Added;
            }


            ctx.SaveChanges();
        }

我的问题是 - 这是典型的路线吗?还是有更聪明/更清洁的方式?

4 个答案:

答案 0 :(得分:0)

我认为此代码应该有效,使用Attach而不是Add

var  ctx = new SecurityContext(this.ConnectionString); 
using(ctx) 
{ 
    ctx.Accounts.Attach(entity);
    ctx.Entry(entity).State = ctx.Accounts.Any(
        a => a.AccountId == entity.AccountId || 
        a.Username == entity.Username) ? 
        EntityState.Modified : EntityState.Added;
    ctx.SaveChanges(); 
} 

原谅奇怪的包装 - 想让它适合页面而不滚动。

答案 1 :(得分:0)

好吧,我认为这是一个让我成为笨蛋的案例..因为我意识到当创建两个记录时可能发生冲突,除了AccountId之外完全相同(假设它们是通过调用者类生成的) 。我已将DAO修改为以下版本,现在它可以正常工作。

如果您愿意,请选择相应的代码:)

    public class AccountDAO : BaseDAO<Account>
{
    public AccountDAO(string connectionString) : base(connectionString)
    {
    }
    public override Account Save(Account entity)
    {
        var ctx = new SecurityContext(this.ConnectionString);
        using(ctx)
        {
            //var dbEntry = (ctx.Accounts.Where(a => a.AccountId == entity.AccountId || a.Username == entity.Username)).FirstOrDefault();
            var dbEntry = (ctx.Accounts.Any(a => a.AccountId == entity.AccountId || a.Username.Contains(entity.Username)));
            //ctx.Accounts.Remove(entity);
            if(!dbEntry)
            {
                ctx.Accounts.Add(entity);
                ctx.Entry(entity).State = EntityState.Added;
            } else
            {
                var currEntity = Read(entity);
                entity.AccountId = currEntity.AccountId;
                ctx.Accounts.Add(entity);
                ctx.Entry(entity).State = EntityState.Modified;
            }

            ctx.SaveChanges();
        }
        return entity;
    }
    public override Account Read(Account entity)
    {
        using (var ctx = new SecurityContext(this.ConnectionString))
        {
            var newEntity = (ctx.Accounts.Where(a => a.AccountId == entity.AccountId || a.Username.Contains(entity.Username))).FirstOrDefault();
            return newEntity;
        }            
    }
    public override void Delete(Account entity)
    {
        using (var ctx = new SecurityContext(this.ConnectionString))
        {
            var ent = Read(entity);
            ctx.Entry(ent).State = EntityState.Deleted;
            ctx.Accounts.Remove(ent);
            ctx.SaveChanges();
        }     
    }
}

答案 2 :(得分:0)

参见样本

 private void Save(Action<Controls.SaveResult> saveResult)
    {
        if (SavableEntity.EntityState != EntityState.Unmodified)
        {
            if (SavableEntity.EntityState == EntityState.Detached || SavableEntity.EntityState == EntityState.New)
                this.SetIsBusy("Creating new...");
            else
                this.SetIsBusy("Saving changes...");
            DomainContext.SavePartial(SavableEntity, p =>
            {
                this.ReleaseIsBusy();
                if (p.HasError)
                {
                    var res = new Controls.SaveResult() { HasErrors = true };
                    if (saveResult != null)
                        saveResult(res);
                    if (this.EntitySaved != null)
                    {
                        EntitySaved(this, new SavedEventArgs() { result = res });
                    }
                    p.MarkErrorAsHandled();
                }
                else
                {
                    Messenger.Default.Send<T>(this.SavableEntity);
                    var res = new Controls.SaveResult() { HasErrors = false };
                    if (saveResult != null)
                        saveResult(res);
                    if (this.EntitySaved != null)
                        EntitySaved(this, new SavedEventArgs() { result = res });
                    if (this.CloseAfterSave)
                        this.RaiseRequestToClose();
                    this.RaisePropertyChanged("Title");
                }
                RaisePropertyChanged("SavableEntity");

            }, false);
        }

    }

答案 3 :(得分:0)

这可能就是你如何表达这个问题,但你似乎有一种奇怪的方法。

  1. 如果要获取和更新应用程序的同一层中的实体,则不应在保存时重新创建上下文,只需挂起对提取实体的上下文的引用,它将跟踪对实体的更改,您只需在同一个上下文中调用SaveChanges()。否则你就是在反对它的基本设计。

  2. 你应该养成在事务中包装SaveChanges()的习惯。如果SaveChanges()触发更改/插入数据库中的多行,则存在保存部分更改的风险。你应该永远保存所有东西。

    使用(TransactionScope ts = new TransactionScope()) {
       ctx.SaveChanges();
       ts.Complete(); }

  3. 如果您正在使用wcf为中间层开发3层应用程序,并因此从客户端序列化实体,您可以简单地添加客户端通过的新属性“IsNew”。如果不是新的,您应该使用Attach()。例如,如果是IsNew,那么ctx.Accounts.Add(实体),否则ctx.Accounts.Attach(实体)

  4. 假设上述情况,如果您有一个IsNew实体,但希望确保它不存在作为中间层的最终检查(我假设您的客户端已经尝试让用户编辑现有的实体(如果存在))。您应该首先在数据库上添加唯一性约束,因为这是对重复项的最终防御。其次,您可以采用已经存在的方法,检查实体是否存在于数据库中,然后手动合并实体(如果这是您所需的功能),或抛出异常/并发异常,强制客户端重新加载真正的实体,他们可以修改那个。

  5. 第4点相当复杂,有很多方法,对我来说太复杂了,无法尝试描述。但请注意,如果您采用自己的方法检查它是否存在,那么决定添加/附加,确保将其包装在事务中,否则新实体有可能在其间由另一个用户/进程添加检查(使用Where())和SaveChanges()。