如何在数据层中编写 Linq 方法

本文关键字:Linq 方法 数据 | 更新日期: 2023-09-27 17:48:55

我正在考虑如何在.net项目的经典3层架构中使用Linq。学徒,Linq to SQL 应该出现在数据层中。我选择 Linq 的原因是,与使用存储过程相比,它可以为我节省很多代码时间。我在网上搜索了一些关于 Linq 的插入/更新/删除方法的搜索,但没有找到使用实体更新记录的合适方法。通常,人们会使用以下方式进行更新:

    public void UpdateUser(String username, String password, int userId) 
    {      
          using (var db = new UserDataContext()){
             var user = db.user.Single(p => p.Id = userId);
             user.Username = username;          
             user.Password = password;          
             db.SubmitChanges();      
           } 
    } 

为什么我们不使用实体来传递这样的记录:

public void Update(Application info)
{
    VettingDataContext dc = new VettingDataContext(_connString);
    var query = (from a in dc.Applications
                 where a.Id==info.Id
                 select a).First();
    query = info;
    try{
        dc.SubmitChanges();
        }
    catch(Exception e){
       //...
        }
 }

但不幸的是,上面的代码因为"query=info"而出错,但是如果我将"info"中的每个值分配给"query",它就可以正常工作。

query.firstName=info.firstName;
query.lastName=info.lastName;

因此,如果此表有 40 个字段,我必须编写 40 行代码。有没有更简单的方法来进行更新?希望我清楚地描述这个问题。

如何在数据层中编写 Linq 方法

添加另一个答案作为注释不足以扩展我之前的答案。

让我们退后一步,从逻辑角度看一下你想在这里做什么。您希望告知数据访问层应如何使用需要写入的所有新/更改值来更新数据库。

执行此操作的一种非常常见的方法是传递具有这些更改的实体(这就是您在示例中所做的)。如您所见,这可能会变得棘手,因为如果您只是用更改的实体覆盖实体变量,Linq2Sql 将失去更改跟踪...仅仅因为新实体被分配给同一个变量,并不意味着 Linq2Sql 会自动从新对象中获取更改......事实上,Linq2Sql 对新对象一无所知......

例:

// In domain layer:
MyEntity entity = new MyEntity();
entity.PrimaryKey = 10;
entity.Name = "Toby Larone";
entity.Age = 27;
myDataRepository.Update(entity);
// In data layer:
void Update(MyEntity changedEntity)
{
    using (var db = new DataContext())
    {
        var entity = (from e in db.MyEntities
                      where e.PrimaryKey == changedEntity.PrimaryKey
                      select e).First();
        // Linq2Sql now has change tracking of "entity"... any changes made will be persisted when SubmitChanges is called...
        entity = changedEntity;
        // Linq2Sql does **not** have change tracking of changedEntity - the fact that it has been assigned to the same variable that once stored a tracked entity does not mean that Linq2Sql will magically pick up the changes...
        db.SubmitChanges(); // Nothing happens - as far as Linq2Sql is concerned, the entity that was selected in the first query has not been changed (only the variable in this scope has been changed to reference a different entity).
    }
}

现在您已经看到,将每个字段分配给实体而不是替换它按预期工作 - 这是因为更改是对原始实体进行的,该实体仍在 Linq2Sql 更改跟踪系统中。

此问题的一个可能的解决方案是编写一种方法,将另一个实体的更改"应用"到现有实体,即:

partial class MyEntity
{
    void ApplyChanges(MyEntity changedEntity)
    {
        this.PrimaryKey = changeEntity.PrimaryKey;
        this.Name = changedEntity.Name;
        this.Age = changedEntity.Age;
    }
}

然后,您的数据访问将如下所示:

// In data layer:
void Update(MyEntity changedEntity)
{
    using (var db = new DataContext())
    {
        var entity = (from e in db.MyEntities
                      where e.PrimaryKey == changedEntity.PrimaryKey
                      select e).First();
        // Linq2Sql now has change tracking of "entity"... any changes made will be persisted when SubmitChanges is called...
        entity.ApplyChanges(changedEntity);
        db.SubmitChanges(); // Works OK...
    }
}

但我确定您不喜欢这个解决方案 - 因为您所做的只是有效地将重复的字段分配从存储库中移出并移动到实体类本身中......

回到逻辑角度 - 您真正需要做的就是告诉数据访问存储库 2 件事 - 1) 您要更新的记录和 2) 更改是什么。发送一个包含这两个要求的全新实体对于实现这一目标不是必需的,事实上我认为这是非常低效的。

在以下示例中,您向数据存储库发送更改,而不是整个实体。因为没有实体,没有更改跟踪问题需要解决

例:

// In domain layer:
myDataRepository.Update(10, entity =>
{
    entity.Name = "Toby Larone";
    entity.Age = 27;
});
// In data layer:
void Update(int primaryKey, Action<MyEntity> callback)
{
    using (var db = new DataContext())
    {
        var entity = (from e in db.MyEntities
                      where e.PrimaryKey == primaryKey
                      select e).First();
        // Linq2Sql now has change tracking of "entity"... any changes made will be persisted when SubmitChanges is called...
        // The changes that were sent are being applied directly to the Linq2Sql entity, which is already under change tracking...
        callback(entity);
        db.SubmitChanges();
    }
}

在前面的示例中,字段分配发生了两次 - 一次是在描述要进行的更改时,另一次是在需要将这些更改应用于 Linq2Sql 更改跟踪实体时在数据存储库中。

使用回调,字段分配仅发生一次 - 更改本身的描述是更新跟踪实体的内容。

我希望我解释得足够好:)

考虑一下数据存储库实际需要什么才能执行更新。它不需要包含这些更改的对象,而是需要对需要进行哪些更改的描述。这可以很容易地封装到回调委托中...

public void UpdateUser(int userId, Action<User> callback)
{
    using (var db = new DataContext())
    {
        User entity = db.Users.Where(u => u.Id == userId).Single();
        callback(entity);
        db.SubmitChanges();
    }
}
myrepository.UpdateUser(userId, user =>
{
    user.Username = username;
    user.Password = password;
    // etc...
});

queryinfo 的类型不同。它们可能与您具有相同的属性,但代码不知道这一点。

现在,如果你想避免编写一堆不必要的代码,你可以使用像AutoMapper这样的第三方库,它可以为你做到这一点。