EF 4.1 替换一行?

发布于 2024-12-04 02:25:58 字数 967 浏览 1 评论 0原文

我有一个问题,在类中我想根据数据库中现有的记录更新或添加一行。在这一点上,我没有“创建/更新”方法,而是有“保存(实体)”方法。

这个想法是根据数据库检查实体,如果存在,则它是更新,如果不存在,则显然是创建。

使用 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();
        }

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

I have an issue where inside a class I want to update or add a row depending on the record existing inside the DB. In that, rather than having "Create/Update" methods I have a "Save(entity)" method.

The idea is the entity is checked against the database, if it exists then it's an update if it doesn't then its obviously a create.

Using EF 4.1 the problem is that once I read the row from the database via the same context, then this in turn creates a memory cache of that row. When I then try and replace the row via say an attach/add routine it will obviously throw an exception around the row already existing etc (as its trying to force both currentRow and newRow into the same table and fails in reconcillation).

My work-around for this is basically to use the .Remove() on the context which in marks the row for removal from memory and then re-add it in the same USING transaction.

 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();
        }

My Question is - is this the typical route to take? or is there much smarter / cleaner ways?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

岛徒 2024-12-11 02:25:59

我相信这段代码应该可以工作,使用 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(); 
} 

请原谅奇怪的包装 - 希望使其适合页面而不滚动。

I believe this code should work, using Attach rather than 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(); 
} 

Forgive the weird wrapping - wanted to make it fit on the page without scrolling.

把昨日还给我 2024-12-11 02:25:59

好吧,我认为这是我是个傻瓜的情况。因为我意识到当创建完全相同的两条记录时可能会发生冲突,除了 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();
        }     
    }
}

Ok, me thinks it was a case of me being a dumbass.. In that i realised the conflict may have occured when two records were being created the exact same except the AccountId were different (given they were generated via caller class). I've modifed the DAO to be the below and now it works.

Pick the code apart if you like :)

    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();
        }     
    }
}
ぃ双果 2024-12-11 02:25:59

见样本

 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);
        }

    }

see sample

 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);
        }

    }
缺⑴份安定 2024-12-11 02:25:59

这可能是你提出问题的方式,但你的方法似乎很奇怪。

  1. 如果您要在应用程序的同一层中获取和更新实体,则在保存时不应重新创建上下文,只需保留对获取实体的上下文的引用即可,它将跟踪对您的实体,您只需在同一上下文上调用 SaveChanges() 即可。否则,您将与它的基本设计作斗争。

  2. 您应该养成将 SaveChanges() 包装在事务中的习惯。如果 SaveChanges() 触发对数据库中超过 1 行的更改/插入,您将面临保存部分更改的风险。您应该始终保存所有内容或什么也不保存。

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

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

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

第4点相当复杂,并且有很多方法,对于我来说太复杂了,无法描述。但要注意,如果您采用检查其存在的方法,然后决定添加/附加,请确保将其包装在事务中,否则新实体有可能在两者之间被另一个用户/进程添加你检查(用你的Where())和SaveChanges()。

It might be how you worded the question, but you seem to have a strange approach.

  1. If you are fetching and updating an entity in the same tier of an application, you shouldn't recreate your context when you save, just hang on to a reference to the context that fetches the entity and it will track the changes to your entity, and you just call SaveChanges() on that same context. You are fighting against its fundamental design otherwise.

  2. You should get in the habit of wrapping you SaveChanges() in a transaction. If SaveChanges() triggers changes / inserts to more than 1 row in the database, you run the risk of saving partial changes. You should always save everything or nothing.

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

  3. If you are developing a 3 tier app using perhaps wcf for the middle tier, and therefore serializing the entity from a client, you could simply add a new property "IsNew" which the client passes through. If not new, you should use Attach(). eg If IsNew, then ctx.Accounts.Add(entity), else ctx.Accounts.Attach(entity)

  4. Assuming the above, if you have an IsNew entity, but want to ensure it doesn't exist as a final check in your middle tier (I assume you client would have already attempted to let the user edit the existing entity if it exists). You should first put a uniqueness constraint on the database, as this is your final defence against duplicates. Second, you can take the approach you have already, where you check if the entity exists in the database, and then either merge the entities by hand if that is your required functionality, or throw an exception / concurrency exception that forces the client to reload the real entity and they can modify that one.

Point 4 is fairly complex, and there are a number of approaches, which are too complex for me to bother trying to describe. But beware, if you take your approach where you check it exists, then decide to add/attach, make sure you wrap that in a transaction, as otherwise there is a chance the new entity will be added by another user/process in between when you check (with your Where()) and SaveChanges().

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文