EF懒惰加载

时间:2014-01-08 11:42:09

标签: c# entity-framework lazy-loading

我对Lazy Loading感到困惑。 我的印象是它在访问它时会加载导航属性,但在我的代码中,它似乎试图将其全部拉入。 这可能是由于我的服务/存储库模式,但目前我正在获得循环引用:(

当我打电话给我这样的服务时:

using (var service = new UserService(new CompanyService()))
{
    var u = await service.GetAll();                  

    return new JsonResult { Data = new { success = true, users = u } }; // Return our users
}

它带来了用户

的列表
public partial class User : IdentityUser
{
    public User()
    {
        // ...
        this.MemberOf = new List<Group>();
        // ...
    }

    // ...

    // ...
    public virtual ICollection<Group> MemberOf { get; set; }
    // ...
}

然后似乎带来了

的列表
public partial class Group
{
    public Group()
    {
        // ...
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    // ...

    // ...
    public virtual Company Company { get; set; }
    // ...
}

然后引入公司

public partial class Company
{
    public Company()
    {
        this.Assets = new List<Asset>();
        // ..
    }

    public string Id { get; set; }
    public string Name { get; set; }
    // ..

    // ...
    public virtual ICollection<Asset> Assets { get; set; }
    // ...
}

带来资产

的列表
public partial class Asset
{
    public Asset()
    {
        // ...
        this.Categories = new List<Category>();
        // ...
    }

    public int Id { get; set; }
    public string FileName { get; set; }
    public string ThumbNail { get; set; }
    // ...

    // ...
    public virtual ICollection<Category> Categories { get; set; }
    // ...
}

带来类别列表,这是循环引用发生的地方,因为它引入了一个资产列表,其中列出了类别等。

我认为使用延迟加载,它只会引入用户及其导航属性,就是这样,除非我另外说明了吗?

我尝试使用此方法而不是我的服务(只是为了测试);

var u = new SkipstoneContext().Users;

return new JsonResult { Data = new { success = true, users = u } }; // Return our users

但我仍然得到循环参考。

有人可以向我解释为什么它会尝试加载所有导航属性,如果有什么我可以(轻松)做到阻止它吗?

更新2

Keith建议使用Interfaces和 ContractResolver 来帮助进行序列化,这就是我所做的。

首先,我创建了2个新接口:

public interface IBaseUser
{
    string Id { get; set; }
    string UserName { get; set; }
    string Email { get; set; }
    bool IsApproved { get; set; }
    bool IsLockedOut { get; set; }

    ICollection<Group> MemberOf { get; set; }
}

public interface IBaseGroup
{
    int Id { get; set; }
    string Name { get; set; }
}

并且模型实现了这些类

public partial class User : IdentityUser, IBaseUser

public partial class Group : IBaseGroup

所以这是第一步,第二步是创建 ContractResolver 类,如下所示:

public class InterfaceContractResolver : DefaultContractResolver
{
    private readonly Type interfaceType;

    public InterfaceContractResolver(Type interfaceType)
    {
        this.interfaceType = interfaceType;
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var properties = base.CreateProperties(this.interfaceType, memberSerialization);

        return properties;
    }
}

我现在获取用户的方法如下:

public async Task<JsonNetResult> Get()
{
    try
    {
        using (var service = new UserService(new CompanyService()))
        {
            var u = await service.GetAll();

            var serializedObject = JsonConvert.SerializeObject(u,
                new JsonSerializerSettings()
                {
                    ContractResolver = new InterfaceContractResolver(typeof(IBaseUser))
                });

            return new JsonNetResult { Data = new { success = true, users = serializedObject } }; // Return our users
        }
    }
    catch (Exception ex)
    {
        return new JsonNetResult { Data = new { success = false, error = ex.Message } };
    }
}

因此,当此代码运行时,我收到错误:

  

无法将类型为“System.Data.Entity.DynamicProxies.Group_D0E52FCCF207A8F550FE47938CA59DEC7F963E8080A64F04D2D4E5BF1D61BA0B”的对象强制转换为“Skipstone.Web.Identity.IBaseUser”。

这是有道理的,因为 InterfaceContractResolver 仅对接口类型有所期望。

问题是如何解决这个问题?

更新3

好的,现在变得很傻。

所以我通过这样做禁用了Lazy Loading:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.Configuration.LazyLoadingEnabled = false; // Disable Lazy Loading

        // ...
    }

然后我创建了这个存储库:

public abstract class Repository<TEntity> : IDisposable, IRepository<TEntity> where TEntity : class
{
    public DbContext Context { get; private set; }
    public IQueryable<TEntity> EntitySet { get { return this.DbEntitySet; } }
    public DbSet<TEntity> DbEntitySet { get; private set; }

    public Repository(DbContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        this.Context = context;
        this.DbEntitySet = context.Set<TEntity>();
    }

    public Task<TEntity> GetAsync(object id)
    {
        return this.DbEntitySet.FindAsync(new object[]
        {
            id
        });
    }

    public IEnumerable<TEntity> GetAll()
    {
        return this.DbEntitySet;
    }

    public IEnumerable<TEntity> GetAll(string include)
    {
        return this.DbEntitySet.Include(include);
    }

    public IEnumerable<TEntity> GetAll(string[] includes)
    {
        foreach (var include in includes)
            this.DbEntitySet.Include(include);

        //var t = this.DbEntitySet.Include("Settings").ToList();

        // return this.GetAll();
        return this.DbEntitySet;
    }

    public void Add(TEntity model)
    {
        this.DbEntitySet.Add(model);
    }

    public void Remove(TEntity model)
    {
        this.Context.Entry<TEntity>(model).State = EntityState.Deleted;
    }

    public void Dispose()
    {
        this.Context.Dispose();
    }
}
我的UserRepository继承的

public class UserRepository : Repository<User>
{
    public UserRepository(DbContext context)
        : base(context)
    {
    }
}

然后我的服务有这个方法:

    public IList<User> GetAll(string include)
    {
        return this.repository.GetAll(include).Where(model => model.CompanyId.Equals(this.companyId, StringComparison.OrdinalIgnoreCase)).ToList();
    }

现在我的 JsonResult 方法如下所示:

    // 
    // AJAX: /Users/Get

    public JsonResult Get()
    {
        try
        {
            using (var service = new UserService(new CompanyService()))
            {
                var u = service.GetAll("MemberOf");                  

                return new JsonResult { Data = new { success = true, users = u } }; // Return our users
            }
        }
        catch (Exception ex)
        {
            return new JsonResult { Data = new { success = false, error = ex.Message } };
        }
    }

并猜猜是什么?!?!? 如果我在返回之前设置了一个断点,我看到的所有属性都已填充,导航属性都没有设置,除了 MemberOf 这正是我想要的(对于现在)但当我跨过返回时,我得到了与以前相同的错误!!!!

  

Newtonsoft.Json.dll中发生了'Newtonsoft.Json.JsonSerializationException'类型的异常,但未在用户代码中处理

     

附加信息:使用类型'System.Data.Entity.DynamicProxies.Asset_AED71699FAD007BF6F823A5F022DB9888F62EBBD9E422BBB11D7A191CD784288'检测到自引用循环。路径'用户[0] .Company.Assets [0] .Categories [0] .Assets'。

如何/为什么这可能?

更新4

这似乎是因为属性仍然标有Virtual。当我删除虚拟时,我停止了资产的错误,而现在我将其用于 CreatedBy 属性。

这是我的用户类:

public partial class User : IdentityUser
{
    public string CompanyId { get; set; }
    public string CreatedById { get; set; }
    public string ModifiedById { get; set; }
    public System.DateTime DateCreated { get; set; }
    public Nullable<System.DateTime> DateModified { get; set; }
    public System.DateTime LastLoginDate { get; set; }
    public string Title { get; set; }
    public string Forename { get; set; }
    public string Surname { get; set; }
    public string Email { get; set; }
    public string JobTitle { get; set; }
    public string Telephone { get; set; }
    public string Mobile { get; set; }
    public string Photo { get; set; }
    public string LinkedIn { get; set; }
    public string Twitter { get; set; }
    public string Facebook { get; set; }
    public string Google { get; set; }
    public string Bio { get; set; }
    public string CompanyName { get; set; }
    public string CredentialId { get; set; }
    public bool IsLockedOut { get; set; }
    public bool IsApproved { get; set; }
    public bool CanEditOwn { get; set; }
    public bool CanEdit { get; set; }
    public bool CanDownload { get; set; }
    public bool RequiresApproval { get; set; }
    public bool CanApprove { get; set; }
    public bool CanSync { get; set; }
    public bool AgreedTerms { get; set; }
    public bool Deleted { get; set; }

    public Company Company { get; set; }
    public User CreatedBy { get; set; }
    public User ModifiedBy { get; set; }
    public ICollection<Asset> Assets { get; set; }
    public ICollection<Category> Categories { get; set; }
    public ICollection<Collection> Collections { get; set; }
    public ICollection<Comment> Comments { get; set; }
    public ICollection<LocalIntegration> LocalIntegrations { get; set; }
    public ICollection<Page> Pages { get; set; }
    public ICollection<Rating> Ratings { get; set; }
    public ICollection<Theme> Themes { get; set; }
    public ICollection<Group> MemberOf { get; set; }
    public ICollection<Category> ForbiddenCategories { get; set; }
    public ICollection<Page> ForbiddenPages { get; set; }
}

这是错误:

  

Newtonsoft.Json.dll中发生了'Newtonsoft.Json.JsonSerializationException'类型的异常,但未在用户代码中处理

     

附加信息:为类型为“System.Data.Entity.DynamicProxies.User_E9B58CAAA82234358C2DE2AF8788D33803C4440F800EA8E015BE49C58B010EDF”的属性“CreatedBy”检测到自引用循环。路径'用户[0]'。

我不明白如何 CreatedBy 属性正在实现,因为它不是虚拟属性,我不会使用Eager或Explicit loading来请求它。 ....

有谁知道为什么?

2 个答案:

答案 0 :(得分:1)

JSON序列化是通过使用反射在内部实现的。因此,它尝试访问您构建JSON结果的每个公共属性。

您可以在不希望序列化的导航属性上添加[JsonIgnore]属性。这对于防止序列化产生循环引用(例如,对于多对多关系)也是有用的。

话虽如此,我建议使用ViewModels而不是实体。迟早你需要添加除直接来自实体本身的属性之外的其他属性(例如:只读计算字段,与您的实体无关但在表单中需要的其他字段......等)。您可以在实体的部分定义中添加这些属性,但是当您拥有许多视图时,它将不再可维护/可读。

第二点是,除非您出于特定原因确实需要它,否则我建议停用延迟加载有三个主要原因:

  • 如果您非常熟悉您的ORM,您可能会遇到严重的性能问题(例如,您可以谷歌搜索“额外的延迟加载”)
  • 极难处理异常,因为代码的每个部分都可能抛出与SQL请求执行失败相关的异常,而不仅仅是从数据库上下文本身查询实体的部分。
  • 延迟加载往往会延长上下文的生命周期(因为你不能丢弃它,直到你确定你永远不会懒得加载一个属性)。

答案 1 :(得分:0)

这可能有效:

创建一个或多个定义要序列化的属性的接口。每个“快照”实体的一个接口。

让您的EF实体实现所有接口。

然后创建一个合约解析程序,该解析程序仅适用于在传递给解析程序的接口类型上定义的属性。请参阅Serialize only interface properties to JSON with Json.net并将答案与合同解析程序代码一起使用。

一旦您对合同解析器进行了编码,您就可以传递EF实体以及您喜欢的任何接口。

希望序列化程序将忽略合同解析器未“看到”的属性。

如果有效,请将您自己的答案与工作代码一起发布,并将其标记为答案。