LINQ to SQL:附加或不附加

时间:2010-04-21 01:09:51

标签: c# linq-to-sql

所以我有一个真的很难找出什么时候我应该附加到一个对象上,什么时候我不应该附加到一个对象上。首先,这是我(非常简化的)对象模型的一个小图。

Schema

在我的DAL中,每次进行与数据相关的操作时,我都会创建一个新的DataContext。比如说,我想保存一个新用户。在我的业务层中,我创建了一个新用户。

var user = new User();
user.FirstName = "Bob";
user.LastName = "Smith";
user.Username = "bob.smith";
user.Password = StringUtilities.EncodePassword("MyPassword123");
user.Organization = someOrganization; // Assume that someOrganization was loaded and it's data context has been garbage collected.

现在我想保存此用户。

var userRepository = new RepositoryFactory.GetRepository<UserRepository>();
userRepository.Save(user);

NEATO!这是我的保存逻辑:

public void Save(User user)
{
 if (!DataContext.Users.Contains(user))
 {
  user.Id = Guid.NewGuid();
  user.CreatedDate = DateTime.Now;
  user.Disabled = false;

  //DataContext.Organizations.Attach(user.Organization);
  DataContext.Users.InsertOnSubmit(user);
 }
 else 
 {
  DataContext.Users.Attach(user);
 }

 DataContext.SubmitChanges();

 // Finished here as well.
 user.Detach();
}

所以,我们在这里。您会注意到我将DataContext附加到组织的位置注释掉。如果我附加到组织,我会得到以下例外:

  

NotSupportedException: 已尝试附加或添加   不是新的实体,也许是新的   已从另一个DataContext加载。   这不受支持。

嗯,这不起作用。让我试试,不要附加(即注释掉关于附加到组织的那一行)。

  

DuplicateKeyException: 无法使用已经存在的密钥添加实体   正在使用中。

WHAAAAT?我只能假设这是试图插入一个显然是错误的新组织。

那么,这些交易是什么?我该怎么办?什么是正确的方法?似乎L2S让它变得比它应该更难......

编辑:我刚注意到,如果我尝试查看挂起的更改集(dataContext.GetChangeSet()),我会得到前面描述的相同的NotSupportedException !!怎么了,L2S?!

3 个答案:

答案 0 :(得分:7)

它可能无法完全像这样工作,但这是我概念化它的方式:当你从DataContext召唤一个对象时,Linq所做的事情之一是跟踪这个对象随时间的变化,所以它知道什么如果您提交更改,请保存。如果丢失了这个原始数据上下文,从它召唤的Linq对象没有从它从数据库中召唤的时间内发生的变化的历史记录。

例如:

DbDataContext db = new DbDataContext();
User u = db.Users.Single( u => u.Id == HARD_CODED_GUID );
u.FirstName = "Foo";
db.SubmitChanges();

在这种情况下,由于User对象是从数据上下文中召唤出来的,因此它跟踪了对“u”的所有更改,并知道如何将这些更改提交给数据库。

在您的示例中,您有一个User对象,该对象已在某处保留(或从其他地方传递,并且没有它从其传唤的原始DataContext)。在这种情况下,您必须将其附加到新数据上下文。

User u; // User object passed in from somewhere else
DbDataContext db = new DbDataContext();
u.FirstName = "Foo";
DbDataContext.Users.Attach( u );
db.SubmitChanges();

由于用户和组织之间的关系只是数据模型中的GUID(OrganizationId),因此您只需附加用户对象。

我不确定你的脚手架代码,但也许是这样的:

    private const Guid DEFAULT_ORG = new Guid("3cbb9255-1083-4fc4-8449-27975cb478a5");
    public void Save(User user)
    {
        if (!DataContext.Users.Contains(user))
        {
            user.Id = Guid.NewGuid();
            user.CreatedDate = DateTime.Now;
            user.Disabled = false;
            user.OrganizationId = DEFAULT_ORG; // make the foreign key connection just
                                               // via a GUID, not by assigning an
                                               // Organization object

            DataContext.Users.InsertOnSubmit(user);
        }
        else
        {
            DataContext.Users.Attach(user);
        }

        DataContext.SubmitChanges();

    }

答案 1 :(得分:1)

因此,当您从数据库中获取一个对象时,使用“attach”,将其分离(例如通过在其他地方的W​​eb服务上进行编组),并希望将其放回数据库中。而不是调用.Attach(),而是调用.InsertOnSubmit()。你几乎在概念上,你只是用错误的方法做你想做的事。

答案 2 :(得分:0)

我使用了400多列的大桌子。我无法映射和测试所有这些!

从数据库中获取原始对象,并将其与修改后的对象一起附加。只要确保重新填充的对象是完全填充的,否则它将用空格覆盖DB!

或者您可以将原始GET复制到内存中并处理MOdel的正确副本(不仅仅是引用),然后传递原始GET和更改后的内容,而不是像我在示例中那样。这只是它如何运作的一个例子。

public void Save(User user)
{

    if (!DataContext.Users.Contains(user))
    {
        user.Id = Guid.NewGuid();
        user.CreatedDate = DateTime.Now;
        user.Disabled = false;
        user.OrganizationId = DEFAULT_ORG; // make the foreign key connection just
                                           // via a GUID, not by assigning an
                                           // Organization object

        DataContext.Users.InsertOnSubmit(user);
    }
    else
    {
        var UserDB = DataContext.Users.FirstOrDefault(db => db.id == user.id); //Costs an extra call but its worth it if oyu have 400 columns!
        DataContext.Users.Attach(user, userDB); //Just maps all the changes on the flu
    }

    DataContext.SubmitChanges();

}