如何使用实体框架核心解决方案实现阶梯模式



所以。。。我正试图遵循.Net核心实体框架项目中的阶梯模式。我使用的是EF Core 2.1。

我的解决方案结构是:

MyApp.Api    
MyApp.Data
MyApp.Data.Interfaces
MyApp.Logic
MyApp.Logic.Interfaces

例如,API(作为依赖根(依赖于所有其他项目,但其他项目仅依赖于抽象。

MyApp.Data  
MyApp.Data.Interfaces
MyApp.Logic
MyApp.Logic.Interfaces
MyApp.Data.Interfaces

我有一个复杂的数据模型,具有子导航属性。我遇到的问题是,输入json与复杂类型的模式匹配,但当它进入验证/模型绑定阶段时(即,它没有进入Action方法(,就被认为是无效的。如果我移除子道具,它会接受它作为有效输入(我使用的是[FromBody]属性,btw(。

作为参考,这里是控制器方法:

[HttpPost]
public IActionResult Post([FromBody] House house)
{
var item = houseService.Add(house);
return CreatedAtRoute("GetById", new { id = item.Id }, item);
}

问题代码在MyApp.Data项目中(该项目仅引用MyApp.Data.Interfaces项目(。对于我使用的儿童/导航道具界面,比如:

// MyApp.Data.Interfaces
public interface IHouse 
{
string Name {get;set;}
IEnumerable<IRoom> Rooms {get;set;}    
}
public interface IRoom
{
int Windows {get;set;}
}
// MyApp.Data
using MyApp.Data.Interfaces;
public class House : IHouse 
{
public string Name {get;set;}
public IEnumerable<IRoom> Rooms {get;set;}
}

如果我在那个端点上发布一些Json,我会得到一个错误:

{
"name": "Dunroamin",
"rooms": [
{
"windows": 2
}
]
}
HTTP 400: Bad Request
{
"rooms[0].windows": [
"The input was not valid."
]
}

如果我将代码更改为以下内容(用具体类型替换子属性的接口(,错误就会消失。

// MyApp.Data.Interfaces
using MyApp.Data;
public interface IHouse 
{
string Name {get;set;}
IEnumerable<Room> Rooms {get;set;}    
}
public interface IRoom
{
int Windows {get;set;}
}
// MyApp.Data
using MyApp.Data.Interfaces;
public class House : IHouse 
{
public string Name {get;set;}
public IEnumerable<Room> Rooms {get;set;}
}

但这违反了楼梯模式,因为我在MyApp.Data.Interfaces项目中使用的是混凝土类型。接口库不应该依赖于任何东西。在EF Core和楼梯模式方面,我看到了一个隐含的困难参考,但没有提出解决方案。我可以看出,EF需要一个具体的类型来将其映射到SQL查询,但这两件事怎么能共存呢?值得吗?

顺便说一句,我不致力于这个架构,我只是想再次练习使用它,我很固执,所以我愿意为了它而让它工作…

这是一个困难的问题,希望这能有所帮助。一开始可能看起来是重复的,但从长远来看,最好这样做。模型和实体可以是自主的,有自己的实现和变量,并且只公开所需的内容,这样我们也可以提供一个安全的解决方案。此外,100%自适应,因此您可以删除旧的域实现并将其引用到新的域实现。完全确保服务不会因为缺少引用或具体类而失败。

[API]MyApp.Web

ref -- >> MyApp.Services (will resolve the abstractions)
ref -- >> MyApp.Domain (will resolve the abstractions)
HomeModel : IHomeModel
{
public int Id { get; set; }
public string ToJson() { }
}

我创建引用-创建我自己的对象,以在Controller中公开它自己的属性和需求,然后我可以将它映射到服务模型并传递它。我添加了对域的引用,以防在MyApp.Service接收MyApp.domain.Abstraction对象作为构造函数中的参数时,如果需要,我想使用依赖项注入。

【服务】

MyApp.Services.Abstraction(不一定是接口(

IHomeModel 
{
int Id { get; set; }
string Name { get; set; }
}
IHomeService
{
void Update(IHomeModel model);
}

在这里,我只定义了服务的行为,即执行操作所需的模型。

MyApp.Services

ref -- >> MyApp.Domain.Abstractions
ref -- >> MyApp.Services.Abstractions
HomeService : IHomeService
{
public void Update(IHomeModel model);
}
internal Home : IHome { }
internal HomeModel : IHomeModel 
{
public int Id { get; set; }
public void ValidateBusinessRule() { }
internal static IHome ToEntity() { }
}

在这里,我实现了这两个步骤,这样我就可以自由地使用数据模型来执行服务中所需的任何操作,例如业务规则,约束域不知道但服务知道的任何内容。

然后我需要创建我的具体类型来映射数据并将其传递到域层,你可以使用AutoMapper,但它更大的问题是当你在接口中有方法时,当你只使用POCO或只使用属性时,它会令人惊讶地工作。但最后也是一样,它创建了一个类,在运行时创建了实例,所有这些都可以在设计时完成,并减少了时间和复杂性。

[域]

MyApp.Domain.Abstractions(不一定是接口(

IHome 
{
int Id { get; set; }
string Name { get; set; }
} 

在这里,我定义了我想向更高级别公开的内容。

MyApp.Domain.

ref -- >> MyApp.Domain.Abstractions
Home : IHome 
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Created { get; set; }
public DateTime LastUpdate { get; set; }
}

在我的域中,我可以使用已创建和上次更新的属性,或者保存我不想与更高步骤共享的任何其他值。

总之,主要问题是当我们试图";保存";为每个实现项目编写类所花费的时间和精力,因此我们希望从Web到Domain共享相同的实例。你会看到你的项目在类和类型的数量上有所增长,但会大大减少每个项目所需的包的大小;"适合";具有域逻辑等的服务…

另一种是共享";实体";在抽象项目中,但这将再次打破模式。

最新更新