如何使用2个表中的数据创建Entity Framework DbSet

时间:2019-02-18 02:04:18

标签: vb.net entity-framework entity-framework-6

在开始提出问题之前,我会指出这是我正在做的编程课程中的作业,我担心代码需要在VB中。

场景: 我们正在编写一个应用程序来帮助管理兽医诊所。有一个无法更改的旧版MySQL数据库。与宠物及其所有者有关的信息存储在两个单独的表中(“宠物”表和“所有者”表,并且这些表由CustomerId的FK链接。我们能够使用我们选择的数据访问技术和ORM,并且我选择使用EF来利用变更跟踪(我希望不必编写此代码)。

我需要做的是创建一个包含来自pet表和owner表的信息的Entity Framework DbSet。我已经看过EF中的表拆分,但是pet和owner的两个“实体”没有相同的主键(据我所知,这是必需的)。

我查看了以下文章,但没有帮助:

Entity Framework and DbSets

DbSet in Entity Framework

Return data from two tables with Entity Framework

我正在使用EF6和“现有数据库中的代码优先”工作流。

我的Pet类看起来很简单(为简便起见,我删除了自动生成的数据注释):

Partial Public Class Pet

    Public Sub New()
        bookings = New HashSet(Of Booking)()
        stays = New HashSet(Of Stay)()
    End Sub

    Public Property petID As Integer
    Public Property petName As String
    Public Property species As String
    Public Property breed As String
    Public Property DOB As Date?
    Public Property gender As String
    Public Property weight As Single?
    Public Property customerID As Integer?
    Public Overridable Property bookings As ICollection(Of Booking)
    Public Overridable Property customer As Customer
    Public Overridable Property stays As ICollection(Of Stay)
End Class

我的客户班级:

Partial Public Class Customer

    Public Sub New()
        pets = New HashSet(Of Pet)()
    End Sub

    Public Property customerID As Integer
    Public Property title As String
    Public Property firstName As String
    Public Property lastName As String
    Public Property gender As String
    Public Property DOB As Date?
    Public Property email As String
    Public Property phone1 As String
    Public Property phone2 As String
    Public Property street1 As String
    Public Property street2 As String
    Public Property suburb As String
    Public Property state As String
    Public Property postcode As String
    Public Overridable Property state1 As State
    Public Overridable Property pets As ICollection(Of Pet)
    Public ReadOnly Property FullName() As String
        Get
            Return $"{Me.lastName}, {Me.firstName}"
        End Get
    End Property
End Class

我还有一个PetInfo类,它不会映射到数据库:

Public Class PetInfoModel

    Public Property PetID As Integer
    Public Property PetName As String
    Public Property Species As String
    Public Property Breed As String
    Public Property DOB As Date
    Public Property Gender As String
    Public Property Weight As Decimal
    Public Property OwnerFirstName As String
    Public Property OwnerLastName As String

    Public ReadOnly Property OwnerName() As String
        Get
            Return $"{OwnerLastName}, {OwnerFirstName}"
        End Get
    End Property

End Class

现在要讲的难点是:我希望能够在上下文中将PetInfoModel用作DbSet来利用EF更改跟踪。

如果有什么不同(我认为不应该这样),我将WPF MVVM和Caliburn.Micro用于UI。最终目标是使List绑定到WPF数据网格。

任何帮助或建议都将受到欢迎。感谢您的时间和精力。

致谢

史蒂夫·蒂斯

1 个答案:

答案 0 :(得分:1)

我对VB不太熟悉,所以我必须用C#编写答案,我想您的要旨是

因此,您拥有DbSet<Pet> PetsDbSet<Customer> Customers,并且您想要创建一个看起来像是DbSet<PetInfoModel> PetInfoModels的东西。

您确定要具有类似DbSet的功能吗?您想添加/查找/附加/删除PetInfoModels吗?还是只想查询数据?

PetInfoModel问题

在我看来,如果您想添加一个新的PetInfoModel(其PetId为零)和现有客户的名称,就会遇到麻烦:

Add(new PetInfoModel
{
     PetId = 0;
     PetName = "Felix"
     OwnerFirstName = "John",
     OwnerLastName = "Doe",
     Species = "Cat",
     ...
});
Add(new PetInfoModel
{
     PetId = 0;
     PetName = "Nero"
     OwnerFirstName = "John",   // NOTE: Same owner name
     OwnerLastName = "Doe",
     Species = "Dog",
     ...
});

我们有一位带两只宠物的顾客:猫和狗吗?还是我们有两个同名客户,每个客户都有一只宠物?

如果您不仅要查询PetInfoModels(添加/更新/删除),还需要找到解决方案。我认为,如果添加CustomerId,大多数问题都将得到解决。但是再说一遍:您的PetInfoModel只是“与他的主人宠物”属性的子集,这使得创建PetInfoModel

的想法有点用处

无论如何:假设您已经定义了一个适当的PetInfoModel,并且您确实希望能够创建/检索/更新/删除(CRUD)PetInfoModels,就好像您的数据库表为{ {1}}。

数据库与存储库

您应该意识到DbContext代表什么。它代表您的数据库。 DbContext的PetInfoModels属性表示数据库中的表。您的数据库没有包含DbSet<...>的表,因此您的DbContext应该没有此表。

另一方面:通常,您会在DbContext周围看到一个包装类,该包装类表示可以存储在存储库中的内容。此类通常称为存储库。

实际上,存储库仅告诉您 您的数据是存储的,而不是如何存储的:它可以是CSV文件,也可以是具有表结构与存储库可以处理的数据序列不同。

恕我直言,我认为让您的DbContext代表您的数据库并创建一个Repository类是明智的选择,该类以数据库用户想要的格式代表存储的数据。

至少,我认为存储库应该能够创建/检索/更新/删除(CRUD)客户和宠物。稍后,我们将为PetInfoModels添加CRUD函数。

客户和宠物

RepositoryItem是可以存储/查询/从存储库中删除的东西。每个RepositoryItem都可以通过主键标识

PetInfoModels

为了保证这个主键,我创建了一个接口IID,并让我所有的DbSet类都实现了此接口。这样可以增强“查找和删除”功能:

interface IRepositoryItem<TRepositoryItem> : IQueryable<TRepositoryItem>
   where TRepositoryItem : class
{
    TRepositoryItem Add(TRepositoryItem item);
    TRepositoryItem Find (params object[] keyValues);
    void Remove(TRepositoryItem item);
}

如果我们有一个DbSet,则实现IRespositoryItem很容易:

interface IID
{
    int Id {get; }
}

class Student : IId
{
    public int Id {get; set;}
    ...
}

interface IRepositoryItem<TRepositoryItem> : IQueryable<TRepositoryItem>
   where TRepositoryItem : IID
{
    TRepositoryItem Add(TRepositoryItem item);
    TRepositoryItem Find (int id);
    void Remove(TRepositoryItem item);

    // or remove the item with primary key:
    void Remove(int id);
}

如果您定义了接口IID:

class RepositoryDbSet<TRepositoryItem> : IRepositoryItem<TRepositoryItem>
    where TRepositoryItem : class
{
    public IDbSet<TRepositoryItem> DbSet {get; set;}

    public TRepositoryItem Add(TRepositoryItem item)
    {
        return this.DbSet.Add(item);
    }
    public TRepositoryItem Find (params object[] keyValues)
    {
        return this.DbSet.Find(keyValues);
    }

    public void Remove(TRepositoryItem item)
    {
         return this.DbSet.Remove(item);
    }

    public void Remove(TRepository

    // implementation of IQueryable / IEnumerable is similar: use this.DbSet
}

现在,我们已经定义了代表存储库中的集合的类,我们可以开始创建存储库本身。

public TRrepositoryItem Find(int id)
{
    return this.DbSet.Find(id);
}
public void Remove(int id)
{
    TRepositoryItem itemToRemove = this.Find(id);
    this.DbSet.Remove(itemToRemove);
}

我们仍然必须创建代表class VetRepository : IDisposable { public VetRepository(...) { this.dbContext = new DbContext(...); this.customers = new RepositoryItem<Customer> {DbSet = this.dbContext.Customers}; this.pets = new RepositoryItm<Pet> {DbSet = this.dbContext.Pets}; } private readonly DbContext dbContext; // the old database private readonly IRepositoryItem<Customer> customers; private readonly IRepositoryItem<Pet> pets; // TODO IDisposable: Dispose the dbcontext // Customers and Pets: public IRepositoryItem<Customer> Customers => this.customers; public IRepositoryItem<Pet> Pets => this.pets; IRepositoryItem<PetInfoModel> PetInfoModels = // TODO public void SaveChanges() { this.DbContext.SaveChanges(); } // TODO: SaveChangesAsync } 的存储库类。此类应实现IRepositoryItem。这样,存储库的用户将不会注意到数据库没有包含PetInfoModels的表

PetInfoModels

嗯,如果您真的想CRUD,您看到class RepositoryPetInfoModel : IRepositoryItem<PetInfoModel> { // this class needs both Customers and Pets: public IDbSet<Customer> Customers {get; set;} public IDbSet<Pet> Pets {get; set;} public PetInfoModel Add(PetInfoModel petInfo) { // check the input, reject if problems // decide whether we have a new Pet for new customer // or a new pet for existing customer // what to do with missing fields? // what to do if Customer exists, but his name is incorrect? Pet petToAdd = ... // extract the fields from the petInfo Customer customerToAdd = ... // or: customerToUpdate? // Add the Pet, // Add or Update the Customer } 遇到多少麻烦吗?

检索很容易:只需创建一个将Pet和他的所有者连接起来的查询,然后为PetInfoModel选择字段。例如

PetInfoModel

更新也相当容易:PetId和CustomerId应该存在。获取Pet和Customer,并使用PetInfoModel中的相应字段更新字段

删除将再次导致问题:如果所有者拥有第二只宠物怎么办?仅删除宠物而不删除所有者?还是删除所有者和所有历史宠物,包括您没有提到的宠物?

结论

如果您只想查询数据,那么引入IQueryable<PetInfoModel> CreateQuery() { // Get all Customers with their Pets return this.Customers.Join(this.Pets { customer => customer.Id, // from every Customer take the primary key pet => pet.CustomerId, // from every Pet take the foreign key // Result selector: take every Customer with a matching Pet // to make a new PetInfoModel (customer, pet) => new PetInfoModel { CustomerId = customer.Id, OwnerFirstName = customer.FirstName, ... PetId = pet.Id, PetName = pet.Name, ... }); } 不会成为问题。

要真正进行欺诈PetInfoModel,您将遇到几个问题,尤其是在拥有两个宠物的主人和拥有相同名字的主人的概念上。我建议不要对PetInfoModels进行CRUD,只查询它们。

建议在数据库和“存储的数据”(存储库)概念之间进行适当的分隔,因为它允许您拥有与存储库用户所看到的模型不同的数据库。