使用LINQ(LINQ2SQL)避免ObjectDisposedException

时间:2014-12-03 18:39:19

标签: c# linq entity-framework

我正在进入"懒惰的IO问题"在Linq,我还没有找到一个我满意的解决方案

设置问题

假设我们有SQL表,看起来像

create table Person (
    id int primary key not null,
    name text not null,
)

create table Dog (
    name text primary key not null,
    ownerid text primary key not null references Person(name)
)

在C#中,我们希望使用LINQ和Entity Framework来处理这个问题。实体框架类定义为partial,因此我们可以扩展它们以添加.Get(string)方法,这样可以生成非常干净的代码。

public partial class Dog 
{
    public static Dog Get(string dogname) 
    {
        using (var db = new MyDataContext())
        {
            // LINQ is lazy and doesn't load the referenced Person
            return db.Dogs.Single(d => d.name == dogname);
        }
    }
}

问题发生的地方

现在我们尝试将Dog对象用于某事

public string DogJson(string dogname) 
{
    var dog = Dog.Get(dogname);
    return JsonConvert.SerializeObject(dog);
}

由于我们的实例dog因外键而包含dog.OwnerJsonConvert当然会尝试将其包含在json字符串中。但是由于DataContext被释放且LINQ是惰性的,因此当然会引发ObjectDisposedException,因为在我们处理DataContext时没有评估dog.Person

在这种情况下,我们根本不关心Owner对象,我们只想将Dog序列化为json。没有这种方法的最佳方法是什么?

我的解决方案

我有一个解决方案,但我并不是特别喜欢它。将投影用于异常对象并返回Dog,因为我们不允许在查询中显式构造Dog

public static Dog Get(string dogname)
{
    using (var db = new MyDataContext())
    {
        var tmpdog = db.Dogs.Where(d => d.name == dogname)
            .Select(d => new { name = d.name, ownerid = d.ownerid}).Single();
        return new Dog() { name = tmpdog.name, ownerid = tmpdog.ownerid};
    }
}

我不喜欢这种解决方案,因为它不能很好地扩展。这个例子只有两个属性,这很快就会失控。 LINQ通常会生成非常优雅的代码,这根本不是优雅的。它也倾向于程序员

有点像我在这里采取错误的方法。

3 个答案:

答案 0 :(得分:2)

我之前也遇到过这个问题,但幸运的是实体框架提供了一种简单的方法。您可以在查询之前禁用延迟加载和动态代理的创建。这将允许json序列化程序毫无例外地运行。

public static Dog Get(string dogname) 
{
    using (var db = new MyDataContext())
    {
        db.Configuration.ProxyCreationEnabled = false;
        db.Configuration.LazyLoadingEnabled = false;
        return db.Dogs.Single(d => d.name == dogname);
    }
}

答案 1 :(得分:1)

真正的问题是你的调用者应该指定DbContext的生命周期,而不是被调用者,因为DogJson方法定义了工作单元。理想情况下,您应该将DbContext实例传递给静态Get方法。

因此,您的Get代码应该更像这样:

public static Dog Get(string dogname, MyDataContext db)
{
    var result = db.Dogs.SingleOrDefault(d => d.name == dogname);
    return result;
}

然后,您可以在来电者中进行所有DTO修改,因为这确实是您的工作单位:

public string DogJson(string dogname) 
{
    using (var db = new MyDataContext())
    {
        var dog = Dog.Get(dogname, db);
        var dogDTO = new Dog { name = dog.name, ownerid = dog.ownerid };
        return JsonConvert.SerializeObject(dogDTO);
    }
}

答案 2 :(得分:1)

在Newtonsoft中序列化对象时,请参阅this question有关忽略属性的信息,我相信这是包含JsonConvert.SerializeObject函数的库。

总结most popular answer是将[JsonIgnore]属性添加到您不希望序列化的字段中。在您的情况下,这是Owner所以代码将是:

[JsonIgnore]
public Person Owner{ get; set; }

原始海报最终使用了own answer中所述的虚拟属性。