在Web API上的多对多关系
在Web API上的多对多关系
我正在努力学习Entity Framework Core。不幸的是,当你创建一个多对多的关系时,它似乎要求一个连接类。请参见下面的类结构:
Person(人)
Sport(运动)
PersonSport(人-运动)
一个人有很多种运动,而一种运动也有很多人参与。在使用Entity Framework时,我会在Person类中放置一个Sport集合,在Sport类中放置一个Person集合。然而,现在我必须创建PersonSport类。请参见下面的API控制器(这是复制问题所需的唯一代码):
namespace WebApplication1.Controllers { [Route("api/[controller]")] [ApiController] public class PersonController : Controller { [Route("")] [ProducesResponseType(typeof(Person), (int)HttpStatusCode.OK)] //[ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] [HttpGet] public async Task GetPerson() { Person p1 = new Person { Id = Guid.NewGuid() }; Sport s1 = new Sport { Id = Guid.NewGuid(), Description = "Football" }; Sport s2 = new Sport { Id = Guid.NewGuid(), Description = "Running" }; PersonSport ps1 = new PersonSport { Person = p1, PersonId = p1.Id, Sport = s1, SportId = s1.Id }; PersonSport ps2 = new PersonSport { Person = p1, PersonId = p1.Id, Sport = s2, SportId = s2.Id }; List personSports = new List (); personSports.Add(ps1); personSports.Add(ps2); p1.AssignSports(personSports); return Ok(p1); } } public class Entity { public Guid Id { get; set; } } public class Person : Entity { private readonly List _personSports; public IReadOnlyCollection PersonSports => _personSports.AsReadOnly(); //public Guid Id { get; set; } public Person() { _personSports = new List (); } public void AssignSports(IEnumerable personSports) { this._personSports.AddRange(personSports); } } public class Sport : Entity { //public Guid Id { get; set; } public string Description { get; set; } } public class PersonSport { public Person Person { get; set; } public Sport Sport { get; set; } public Guid PersonId { get; set; } public Guid SportId { get; set; } } }
我调试Web API项目并导航到:http://localhost:57320/api/Person,看到如下结果:
注意到JSON格式不正确。如果我尝试从控制台应用程序/单元测试应用程序中使用Web API,我会看到以下错误:
System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host --- End of inner exception stack trace --- at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error) at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.GetResult(Int16 token) at System.Net.Http.HttpConnection.FillAsync() at System.Net.Http.HttpConnection.ChunkedEncodingReadStream.CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) at System.Net.Http.HttpClient.GetStringAsyncCore(Task`1 getTask) at ConsoleApp1.Program.CallWebAPI()
我该如何解决这个问题?我用于消费Web API的代码如下:
HttpClient enquiryClient = new HttpClient(); var responseString = await enquiryClient.GetStringAsync("http://localhost:57320/api/Person"); var response = JsonConvert.DeserializeObject(responseString);
很多到很多的关系是Web API中常见的一个问题。这个问题的出现是因为在实现实体框架之前,实体中的痛点在于实体本身。解决这个问题的方法是实现实体框架。下面是一个示例代码片段,用于测试API:
public async TaskGetPerson() { var person = new Person() { Id = new Guid.NewGuid() }; var sports = new [] { new Sport() { Id = Guid.NewGuid(), Description = "Football" }, new Sport() { Id = Guid.NewGuid(), Description = "Soccer" } }; person.AssignSports(sports); return Ok(person); }
在实现实体框架后,其他部分,如虚拟属性、连接表、数据加载和导航属性等,将会出现。下面的代码片段是将json选项添加到启动文件中的解决方法:
.AddJsonOptions(options => { options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects; })
关于第一个问题,是的,这是正确的。如果设置了实体,可以将运动分配给人员。这样可以更直观地进行操作。在幕后,实体框架将填充连接表,尽管这取决于设置。
另一个要指出的事情是,由于使用了API,更清晰的方法是将域模型与数据层分开。这需要额外的工作,但你将获得一个更易维护的解决方案。有关更多信息,请查看以下链接:
stackoverflow.com/questions/23648832
如果你正在使用CQRS,将写数据库映射到域模型是正常的做法。你提到的链接谈到了MVVM,我认为这是一个客户端模式。当你谈论隔离域模型时,是否指的是这样的内容?
github.com/sapiens/ModernMembership/blob/master/src/ModernMembership/LocalMember.cs
关于这个问题,我的回答是在仓储模式中获取数据对象,然后在返回之前将其转换为域对象。直接映射到域模型是一个好的做法,但你应该将其映射到数据模型作为ORM和域模型之间的中介。这是我观点,希望能理解。
术语可能会令人困惑,因为它们的定义并不总是正确使用的。提示:如果你正在使用ORM,则不要使用仓储模式:ORM已经是仓储模式。所以:数据库->ORM/实体/数据模型->域模型,其中1)是数据库本身(没有代码/类等),2)是ORM,实体框架,实体,数据层等等可以使用的其他名称,3)是域,它在应用程序中使用,在第三方中公开,并在业务逻辑中使用。这也是CQRS的结果和输入。
数据模型、数据层、实体、ORM基本上都是相同的:它们都存在于数据层上下文中,靠近数据库。通过API公开的东西必须接近域模型,具有自说明的对象。
数据库映射到域模型。在这种情况下,如何避免PersonSport实体,并使用Person.Sports集合而不是Person.personsports?
定义“映射”。在我的定义中,它是从一个“东西”到另一个“东西”的转换;所以:数据库映射到实体/ORM,实体/ORM映射到域模型,反之亦然。
这是最后一个问题,然后我就会接受。如果我正在使用CQRS,那么我将拥有一个域模型、一个写模型(映射到数据库的实体类)和一个读模型(映射到数据库的实体类)。我对你说的理解正确吗?
是的,让我举个例子。在CQRS中,你有查询和命令。你的命令可能是一个在域中定义的操作。例如:AddNotificationMethod;如果这个命令在更广泛的域中定义,比如跨应用程序:它需要是一个域模型,因为实体可能在所有这些应用程序中都没有定义。对于查询也是一样的:比如说GetCar,结果是一辆车(显然)。但它是一个域车还是一个实体车?如果你将来要在不同的上下文中使用这辆车,它应该是一个域模型。
请注意:如果你创建一个API,你几乎可以定义其他应用程序参与你的设置。