我遇到了懒惰的问题。 案例是: 我有带Lazy属性的DataLayer模型。每个模型都可以具有lazy属性,该属性返回其他模型的列表,该列表可以具有延迟属性。 例如:
public class User
{
private Guid id;
public Guid Id {
get { return id;}
set{
id=value;
lazyCompany=new Lazy<Company>(()=>GetCompanyByUserId(value));
}
}
public string Name {get; set;}
public Company Company =>lazyCompany?.Value // lazy returns user's company
private Lazy<Company> lazyComapany;
}
public class Company
{
public int Id {get; set;}
public string Name {get; set;}
public List<User> Users // lazy returns list of users in company
}
如果我在C#中使用它,我没有问题。我可以从User对象获得用户公司,我可以从Company对象获取公司中的所有用户。
但是当我在API中使用相同的对象时会发生有趣的事情
[HttpGet]
public dynamic User(Guid userId)
{
return GetUser(userId);
}
我因为无休止的递归而得到StackOverflow异常(User对象返回初始化的lazy Company和Company返回懒惰的用户列表,依此类推)
所以我开始在API中使用Anonym对象
[HttpGet]
public dynamic User(Guid userId)
{
var res=GetUser(Guid userId);
return new {res.Id, res.Name};
}
或者
[HttpGet]
public dynamic Company(Guid companyId)
{
var res=GetCompany(int companyId).Select(s=>new {s.Id, s.Name}).ToList();
return res;
}
它解决了这个问题,但我开始寻找其他解决方案,因为我认为这不是处理它的最佳方式。
所以我继续寻找替代解决方案。我发现了另外一个:封装和继承。
public class User
{
public Guid Id {get; set;}
public string Name {get; set;}
protected Company Company // lazy returns user's company
}
public class Company
{
public int Id {get; set;}
public string Name {get; set;}
protected List<User> Users // lazy returns list of users in company
}
我有这些类的包装器来返回所有值
public class UserFull : User
{
}
public class CompanyFull : Company
{
}
并返回User / Company而不是UserFull / CompanyFull
现在的问题是:
我会感激任何建议。
Thanx :)
public User GetUser(Guid userId)
{
using (var c = ConnectionToDataBase())
{
return new User(c.Users.FirstOrDefault(u=>u.Id==userId));
}
}
答案 0 :(得分:1)
这里的问题是,当从api返回时,您的导航属性将永远不会执行。必须预先执行它们才能返回数据。将您的返回类型更改为UserFull。
//return full data
public UserFull GetUser(Guid userId)
{
using (var c = ConnectionToDataBase())
{
var user = c.Users
.Where(u => u.Id==userId)
.Select(u => new UserFull
{
Id = u.Id,
Name = u.Name,
Company = u.Company //force execution
}).FirstOrDefault();
return user;
}
}
//return partial data
public dynamic GetUser(Guid userId)
{
using (var c = ConnectionToDataBase())
{
var user = c.Users
.Where(u => u.Id==userId)
.Select(u => new
{
Id = u.Id,
Name = u.Name,
}).FirstOrDefault();
return user;
}
}
[HttpGet, Route("api/users/{userId:guid}")]
public IHttpActionResult User(Guid userId)
{
try
{
var res = GetUser(Guid userId);
return Ok(res);
}
catch
{
return InternalServerError();
}
}
答案 1 :(得分:1)
发生了什么:
从API返回对象时,必须将其序列化为“通过线路发送”。为了执行此序列化,将评估对象的每个属性并将其转换为JSON(或您使用的任何序列化)。由于访问了每个属性,因此会评估导航属性,从而触发延迟加载。
正如您所正确指出的,循环导航属性会导致StackOverflowExceptions。
如何修复:
有几种方法可以解决这个问题(匿名对象就是其中之一),但作为一个优秀的开发人员,拥有一个强类型的返回会很不错。创建一个ViewModel!
public class UserViewModel
{
public Guid Id {get; set;}
public string Name {get; set;}
}
我强烈推荐AutoMapper,因为它可以非常轻松地在实体和ViewModel之间进行转换。
使用AutoMapper:
[HttpGet]
public UserViewModel User(Guid userId)
{
var res=GetUser(Guid userId);
return Mapper.Map<UserViewModel>(res);
}
这变得更好。由于AutoMapper会自动在同名属性之间进行映射,因此您可以利用这些导航属性
public class CompanyViewModel
{
public int Id {get; set;}
public string Name {get; set;}
public List<UserViewModel> Users
}
现在当你返回公司时,你没有获得StackOverflowException,但你仍然得到了用户!
缺点(如果你可以称之为),你需要小心你的ViewModel,以确保没有循环引用。幸运的是,您可以为当前ViewModel无法表示的情况创建更多ViewModel。