我对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来请求它。 ....
有谁知道为什么?
答案 0 :(得分:1)
JSON序列化是通过使用反射在内部实现的。因此,它尝试访问您构建JSON结果的每个公共属性。
您可以在不希望序列化的导航属性上添加[JsonIgnore]
属性。这对于防止序列化产生循环引用(例如,对于多对多关系)也是有用的。
话虽如此,我建议使用ViewModels而不是实体。迟早你需要添加除直接来自实体本身的属性之外的其他属性(例如:只读计算字段,与您的实体无关但在表单中需要的其他字段......等)。您可以在实体的部分定义中添加这些属性,但是当您拥有许多视图时,它将不再可维护/可读。
第二点是,除非您出于特定原因确实需要它,否则我建议停用延迟加载有三个主要原因:
答案 1 :(得分:0)
这可能有效:
创建一个或多个定义要序列化的属性的接口。每个“快照”实体的一个接口。
让您的EF实体实现所有接口。
然后创建一个合约解析程序,该解析程序仅适用于在传递给解析程序的接口类型上定义的属性。请参阅Serialize only interface properties to JSON with Json.net并将答案与合同解析程序代码一起使用。
一旦您对合同解析器进行了编码,您就可以传递EF实体以及您喜欢的任何接口。
希望序列化程序将忽略合同解析器未“看到”的属性。
如果有效,请将您自己的答案与工作代码一起发布,并将其标记为答案。