在 API 中返回数据时防止延迟属性初始化
Prevent Lazy properties initialization when data is returned in API
我遇到了懒惰的问题。
案例是:
我有具有惰性属性的 DataLayer 模型。每个模型都可以有惰性 属性,return 是其他模型的列表,可以具有惰性属性。
例如:
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);
}
由于无限递归,我得到 Whosebug 异常(用户对象 returns 已初始化惰性公司和公司 returns 惰性用户列表等)
所以我开始在 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
}
我有这些 类 到 return 所有值的包装器
public class UserFull : User
{
}
public class CompanyFull : Company
{
}
和 return User/Company 而不是 UserFull/CompanyFull
现在的问题是:
还有其他方法可以解决这个问题吗?
如有任何建议,我将不胜感激。
谢谢 :)
public User GetUser(Guid userId)
{
using (var c = ConnectionToDataBase())
{
return new User(c.Users.FirstOrDefault(u=>u.Id==userId));
}
}
这里的问题是,当从 api 编辑 return 时,您的导航属性将永远不会执行。它们必须预先执行到 return 数据。将您的 return 类型更改为 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();
}
}
发生了什么:
当您 return 来自 API 的对象时,它必须被序列化为 "sent across the wire"。为了进行这种序列化,对象的每个 属性 都被评估并翻译成 JSON (或您使用的任何序列化)。因为每个 属性 被访问,导航属性被评估,触发延迟加载。
正如您正确指出的那样,循环导航属性会导致 WhosebugExceptions。
如何解决:
有几种方法可以解决这个问题(匿名对象是一种),但作为一名优秀的开发人员,拥有强类型 return 会很好。创建视图模型!
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
}
现在当你 return 公司时,你不会得到 WhosebugException 但你仍然得到用户!
缺点(如果你可以这么说的话)是你需要小心你的 ViewModels 以确保没有循环引用。幸运的是,您可以为当前 ViewModel 无法表示的情况创建更多 ViewModel。
我遇到了懒惰的问题。 案例是: 我有具有惰性属性的 DataLayer 模型。每个模型都可以有惰性 属性,return 是其他模型的列表,可以具有惰性属性。 例如:
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);
}
由于无限递归,我得到 Whosebug 异常(用户对象 returns 已初始化惰性公司和公司 returns 惰性用户列表等)
所以我开始在 API
[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
}
我有这些 类 到 return 所有值的包装器
public class UserFull : User
{
}
public class CompanyFull : Company
{
}
和 return User/Company 而不是 UserFull/CompanyFull
现在的问题是:
还有其他方法可以解决这个问题吗?
如有任何建议,我将不胜感激。
谢谢 :)
public User GetUser(Guid userId)
{
using (var c = ConnectionToDataBase())
{
return new User(c.Users.FirstOrDefault(u=>u.Id==userId));
}
}
这里的问题是,当从 api 编辑 return 时,您的导航属性将永远不会执行。它们必须预先执行到 return 数据。将您的 return 类型更改为 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();
}
}
发生了什么:
当您 return 来自 API 的对象时,它必须被序列化为 "sent across the wire"。为了进行这种序列化,对象的每个 属性 都被评估并翻译成 JSON (或您使用的任何序列化)。因为每个 属性 被访问,导航属性被评估,触发延迟加载。
正如您正确指出的那样,循环导航属性会导致 WhosebugExceptions。
如何解决:
有几种方法可以解决这个问题(匿名对象是一种),但作为一名优秀的开发人员,拥有强类型 return 会很好。创建视图模型!
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
}
现在当你 return 公司时,你不会得到 WhosebugException 但你仍然得到用户!
缺点(如果你可以这么说的话)是你需要小心你的 ViewModels 以确保没有循环引用。幸运的是,您可以为当前 ViewModel 无法表示的情况创建更多 ViewModel。