繁体   English   中英

管理实体框架上下文的最佳方式是什么?

[英]What is the best way to manage Entity Framework contexts?

我有这样的事情:

static Employee getEmployee(string ssn){
    MyEntity context = null;
    Employee employee;
    try{
        context = new MyEntity();
        employee = context.Employee.Where(X => X.ssn.Equals(ssn));
    }
    catch (Exception exc){
        LogLibrary.WriteLog(exc.Message);
    }
    finally{
        if (context != null){
            context.Database.Connection.Close();
        }
    }
    return employee;
}

static Employee addEmployee(Employee emp){
    MyEntity context = null;
    Employee employee;
    try{
        context = new MyEntity();
        context.Employee.Add(e);
    }
    catch (Exception exc){
        LogLibrary.WriteLog(exc.Message);
    }
    finally{
        if (context != null){
            context.Database.Connection.Close();
        }
    }
}

这是我要实现的代码:

Employee myNewEmployee = DBClass.getEmployee("12345");
myNewEmployee.name = "John";
DBClass.AddEmployee(myNewEmployee);

但我显然收到以下异常: An entity object cannot be referenced by multiple instances of IEntityChangeTracker所以我被建议执行以下操作:

Employee myNewEmployee = DBClass.getEmployee("12345");
Employee myNewEmployee2 = new Employee();
// manually copy all the fields from myNewEmployee to myNewEmployee2
myNewEmployee2.name = "John";
DBClass.AddEmployee(myNewEmployee2);

但我认为这可能是低效的,因为我正在浪费时钟周期来拥有相同 object 的相同副本。我们应该为整个应用程序使用单个 static 上下文吗? (这是一个带有母版页的 ASPx 项目)。 我在哪里可以阅读更多关于“如何使用上下文”的信息? 太感谢了。

由于代码为每次调用使用完全独立的 DbContext 实例,因此在确保更新所有唯一和键后,技术上可以更新实例以将其复制并保存为新员工。 但是,您现有的代码通过处置 DbContext 使实体跟踪引用成为孤立对象。 类似 getEmployees 的代码应该使用AsNoTracking()在分离的 state 中加载实体,或者在处理上下文之前从 DbContext 中分离实例。 该方法也可以简化为删除finally块以使用using块处理处置:

static Employee getEmployee(string ssn)
{
    using(var context = new MyEntity())
    {
        try
        {
            return context.Employee.AsNoTracking().Single(X => X.ssn.Equals(ssn));
        }
        catch (Exception exc)
        {
            LogLibrary.WriteLog(exc.Message);
        }
    }
    return null;
}

或者,您可以在返回之前分离实体:

        try
        {
            var employee = context.Employee.Single(X => X.ssn.Equals(ssn));
            context.Entry(employee).State = EntityState.Detached;
            return employee;
        }

using块负责处理 scope 中的实例。 finally一个块错过了,你有一个未处理的 DbContext 实例。

当现在处置的 DbContext跟踪该初始实体时,您可以使用如下代码:

var employee = getEmployee("12345");
employee.SSN = "54321";
employee.Name = "John";
using(var context = new MyEntity())
{
    context.Employees.Add(employee);
    context.SaveChanges();
}

另一种方法是克隆实体。有几种方法可以做到这一点,包括手动复制值或利用 Automapper 复制值。 这可以配置为忽略复制键和唯一值。 最基本的:

var config = new MapperConfiguration(cfg => cfg.CreateMap<Employee, Employee>());
var mapper = config.CreateMapper();

var sourceEmployee = getEmployee("12345");
var newEmployee = mapper.Map<Employee>(sourceEmployee);
newEmployee.SSN = "54321";
newEmployee.Name = "John";
using(var context = new MyEntity())
{
    context.Employees.Add(newEmployee);
    context.SaveChanges();
}

此代码需要确保更新主键值和任何唯一约束。 如果 employee 表有一个 EmployeeId PK,并且该键被设置为一个身份,那么它应该被自动覆盖。 否则,如果 PK 类似于 SSN,您需要在保存之前确保它是一个新的且唯一的值。 为此,您应该首先检查数据库以确保新的 SSN 是唯一的:

using(var context = new MyEntity())
{
    if (!context.Employees.Any(x => x.SSN == newEmployee.SSN))
    {
        context.Employees.Add(newEmployee);
        context.SaveChanges();
    }
    else 
    {
        // handle that the new SSN is already in the database.
    }
}

关于仅使用单个 static DbContext:不,这不是 EF 的好主意。 默认情况下,DbContexts 会跟踪它们加载的每个实例,除非明确告知不要这样做。 这意味着它们存活的时间越长,它们跟踪的实例就越多,消耗 memory 并导致性能下降,因为 EF 将不断检查其已知的跟踪实例以查看它是否应该返回该实例而不是从数据库中提取的新实例。 在大多数情况下它仍然运行查询,因此处理跟踪实例并不会像您可能认为的将行为与缓存进行比较那样节省性能。 通常您会希望多个调用与单个 DbContext 实例相关联,因此 DbContext 的范围过于精细会降低灵活性。 例如,如果您想更新公司中的 Position 并将其与员工相关联,则使用 getEmployee() 方法将其范围限定为自己的 DBContext 实际上可能会产生意想不到的后果,例如此示例:

using (var context = new MyEntity())
{
    var position = context.Positions.Single(x => x.PositionId == positionId);
    var employee = getEmployee(ssn);
    position.Employee = employee;
    context.SaveChanges();
}

这最终可能会导致在尝试插入新员工时出现重复约束错误,或者它将插入员工记录的全新克隆。 (如果 Employee 为其 PK 配置了身份)原因是 Position 由 DbContext 的一个实例管理,而 getEmployee() 使用的是一个完全独立的 DbContext 实例。 position 不知道“employee”是一个已有的记录,而是把它当作一个全新的记录。 确保这些实例关联在一起的正确方法是确保它们都与同一个 DbContext 实例关联:

using (var context = new MyEntity())
{
    var position = context.Positions.Single(x => x.PositionId == positionId);
    var employee = context.Employees.Single(x => x.SSN == ssn);
    position.Employee = employee;
    context.SaveChanges();
}

或者确保此代码和 getEmployee 都注入了相同的DbContext实例,而不是在方法中限定它的范围。 (即依赖注入)使用像你的代码这样的结构化分离实例是可能的,但它可能会变得非常混乱,所以要小心。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM