How do I create an Entity Framework DbSet with data from 2 tables - vb.net

Before I start my question, I will point out that this is for an assignment in a programming course I am doing, and I'm afraid the code needs to be in VB.
Scenario:
We are writing an app to help manage a veterinary clinic. There is a legacy MySQL database which cannot be changed. Information relating to pets and their owners are stored in two separate tables ("pets" table and "owners" table, and the tables are linked by the FK of CustomerId. We are able to use our choice of data access technologies and ORMs, and I have chosen to use EF to take advantage of the Change Tracking (I'd prefer to not have to write this code).
What I need to do is create an Entity Framework DbSet that contains information from both the pet and owner tables. I have looked at Table splitting in EF, but the two "entities" of pet and owner do not have the same primary key (which as I understand Table Splitting is required).
I have reviewed the following articles, and they have not helped:
Entity Framework and DbSets
DbSet in Entity Framework
Return data from two tables with Entity Framework
I am using EF6 and the "Code First from existing Database" workflow.
My Pet class looks like (I've removed the auto generated data annotations for brevity):
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
My Customer 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
I also have a PetInfo class that does NOT map to the DB:
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
Now for the hard part: I would like to be able to use the PetInfoModel as a DbSet in my context to take advantage of the EF change tracking.
If it makes any difference (I don't think it should), I am using WPF MVVM and Caliburn.Micro for the UI. The ultimate goal is to get a List bound to a WPF datagrid.
Any assistance or suggestions would be more than welcome. Thanks for your time and efforts.
Regards
Steve Teece

I'm not very familiar with VB, so I'll have to write the answer in C#, I think you get the gist.
So you have DbSet<Pet> Pets and DbSet<Customer> Customers and you want to create something that acts as if it was a DbSet<PetInfoModel> PetInfoModels.
Are you sure you want something that acts like a DbSet? You want to be able to Add / Find / Attach / Remove PetInfoModels? Or do you only want to query data?
Problems with PetInfoModel
It seems to me that you get into troubles, if you want to Add a new PetInfoModel with a zero PetId, and the name of an existing Customer:
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",
...
});
Do we have one Customer with two Pets: a Cat and a Dog? Or do we have two Customers, with the same name, each with one Pet?
If you want more than just query PetInfoModels (Add / Update / Remove), you'll need to find a solution for this. I think most problems will be solved if you add a CustomerId. But then again: your PetInfoModel would just be a subset of the properties of a "Pet with his Owner", making it a bit useless to create the idea of a PetInfoModel
Anyway: let's assume you've defined a proper PetInfoModel and you really want to be able to Create / Retrieve / Update / Delete (CRUD) PetInfoModels as if you have a database table of PetInfoModels.
Database versus Repository
You should realize what your DbContext represents. It represents your database. The DbSet<...> properties of your DbContext represent the tables in your database. Your database does not have a table with PetInfoModels, hence your DbContext should not have this table.
On the other hand: Quite often you'll see a wrapper class around your DbContext that represents the things that can be stored in your Repository. This class is usually called a Repository.
In fact, a Repository only tells you that your data is stored, not how it is stored: it can be a CSV-file, or a database with a table structure different than the data sequences that can be handled by your repository.
IMHO I think it is wise to let your DbContext represent your database and create a Repository class that represents the stored data in a format that users of your database want.
As a minimum, I think a Repository should be able to Create / Retrieve / Update / Delete (CRUD) Customers and Pets. Later we'll add CRUD functions for PetInfoModels.
Customers and Pets
A RepositoryItem is something that can be stored / queried / removed from the repository. Every RepositoryItem can be identified by a primary key
interface IRepositoryItem<TRepositoryItem> : IQueryable<TRepositoryItem>
where TRepositoryItem : class
{
TRepositoryItem Add(TRepositoryItem item);
TRepositoryItem Find (params object[] keyValues);
void Remove(TRepositoryItem item);
}
To guarantee this primary key, I created an interface IID and let all my DbSet classes implement this interface. This enhances Find and Remove:
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);
}
If we have a DbSet the implementation of an IRespositoryItem is easy:
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
}
If you defined interface IID:
public TRrepositoryItem Find(int id)
{
return this.DbSet.Find(id);
}
public void Remove(int id)
{
TRepositoryItem itemToRemove = this.Find(id);
this.DbSet.Remove(itemToRemove);
}
Now that we've defined the class that represents a set in the Repository, we can start creating the Repository itself.
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
}
We still have to create a repository class that represents the PetInfoModels. This class should implement IRepositoryItem. This way users of the repository won't notice that the database doesn't have a table with PetInfoModels
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
}
Hm, do you see how much troubles your PetInfoModel encounters if you really want to CRUD?
Retrieve is easy: just create a Query that joins the Pet and his Owner and select the fields for a PetInfoModel. For example
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,
...
});
}
Update is also fairly easy: PetId and CustomerId should exist. Fetch the Pet and Customer and update the fields with the corresponding fields from PetInfoModel
Delete will lead to problems again: what if the Owner has a 2nd Pet? Delete only the Pet but not the Owner? Or Delete the Owner and all hist Pets, inclusive the Pets you didn't mention?
Conclusion
If you only want to query data, then it won't be a problem to introduce a PetInfoModel.
To really CRUD PetInfoModels, you'll encounter several problems, especially with the concept of Owners with two Pets, and Owners having the same name. I would advise not to CRUD for PetInfoModels, only query them.
A proper separation between your database and the concept of "stored data" (Repository) is advisable, because it allows you to have a database that differs from the model that users of your Repository see.

Related

Set dynamic table name(database table) to one single entity in EntityFrameworkCore

I have many tables with the same model structure but with table names and data is different.
E.g
//Model
public class pgl1
{
public string id {get;set;}
public string name {get;set;}
}
public class pgl2
{
public string id {get;set;}
public string name {get;set;}
}
My tables in database are pgl_1, pgl_2, pgl_3 etc...
Context class -
public class MyContext : DbContext
{
public DbSet<pgl1>? pgl_1{ get; set; } //pgl_1 is database table name
public DbSet<pgl2>? pgl_2{ get; set; } //pgl_2 is database table name
}
And I will implement this using below.
var context = new MyContext();
List<<*pgl1>> list1 = new List<<*pgl1>>();
listb = context.pgl1.ToList<<*pgl1>>();
List<<*pgl2>> list2 = new List<<*pgl2>>();
list2 = context.pgl2.ToList<*pgl2>>();
I want only one Model and one Dbset for multiple tables.
Is this possible.
I have searched lot for this but did not get any proper solution.
Any answers will really helpful.
Thanks.
The main problem here is that table for entity sets up in OnModelCreating method which executes right after your database context is instantiated on incoming http request and you can't change it after.
The simplest workaround here is do not use entity-to-model binding in OnModelCreating, but write your own CRUD methods in your DbContext implementation, which would use tableName argument to dynamically build SQL query string.
base.Database.ExecuteSqlRaw($"INSERT INTO {tableName} ...");

Entity Framework Core: using navigation properties without foreign key

I have following object model:
public class SharingRelation:BaseEntity
{
public Guid? Code { get; set; }
public string Value { get; set; }
}
public class SecondLevelShareEntity : BaseEntity
{
public string Name { get; set; }
public Guid? SharingCode { get; set; }
public List<SharingRelation> SharingRelations { get; set; }
}
In my database (it may be poor db design but I need to answer this question for research), SharingRelation is some sort of dependent entity of SecondLevelShareEntity on Code == SharingCode values. I can have two entities of type SecondLevelShareEntity with same SharingCode value. So, for each of them I need to get all related SharingRelation objects depending on Code and SharingCode values. I can do it using SQL and join on this columns. But how can I do it using EF Core and navigation properties (I want to get all dependent entities using Include() for example)? When I configure my entities like this
public class SharingRelationEntityTypeConfiguration : BaseEntityTypeConfiguration<SharingRelation>
{
public override void Configure(EntityTypeBuilder<SharingRelation> builder)
{
base.Configure(builder);
builder.HasOne<SecondLevelShareEntity>().WithMany(x => x.SharingRelations).HasForeignKey(x => x.Code)
.HasPrincipalKey(x => x.SharingCode);
}
}
EF Core creates foreign key and marks it unique. I am obviously getting an error that that is impossible to have several SecondLevelShareEntity with the same SharingCode
System.InvalidOperationException : The instance of entity type 'SecondLevelShareEntity' cannot be tracked because another instance with the key value '{SharingCode: 8a4da9b3-4b8e-4c91-b0e3-e9135adb9c66}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
How can I avoid creation of foreign key, but keep using navigation properties (as far, as I see normal queries with navigations generate simple JOIN statements)
UPDATED I can provide real data in database. SecondLevelShareEntity table looks like this:
_id Name SharingCode
----------------------------------------------------------------------
1 "firstSecondLevelEnt" "efcb1c96-0ef1-4bb3-a952-4a6511ab448b"
2 "secondSecondLevelEnt" "efcb1c96-0ef1-4bb3-a952-4a6511ab448b"
And SharingRelation table looks like this:
_id Value Code
----------------------------------------------------------------------
1 "firstSharingRelation" "efcb1c96-0ef1-4bb3-a952-4a6511ab448b"
2 "secondSharingRelation" "efcb1c96-0ef1-4bb3-a952-4a6511ab448b"

Can't fetch certain types of nested objects in Ebean

I'm trying to fetch nested objects in Ebean, but it isn't working. I get the User. It has Addresses. The Addresses each have a House. But the House only has an id. All other properties are null. I read on this other forum that there may be a bug in Ebean, but it was from 2011. Is there a way to make this work?
Note: Address and House have a OneToOne relationship.
Note: I left out #Entity and #Id for simplicity.
public class User {
#OneToMany
public List<Address> addresses;
public static Finder<String, User> find = new Finder(String.class, User.class);
// This is my query
public static Event find(Long id) {
return find.fetch("addresses").fetch("addresses.house").where().eq("id", id).findUnique();
}
}
public class Address {
#OneToOne(cascade = CascadeType.ALL, mappedBy = "address")
public House house;
}
public class House {
#OneToOne
public Address address;
public String somePropertyThatIsNullWhenIUseMyQuery;
}
Ebean.find(User.class).fetch("addresses.house", new FetchConfig().query())
works for me. If you still dont see it, u might want to use
Address.getHouse().getSomeProperty()
Sometimes when u just pass the object to JSON f.e. properties shown as null :(

NHibernate QueryOver distinct

I have this
scenario:
class User
{
Id,
UserName
}
class UserRelationship
{
User GroupUser,
User MemberUser
}
and query
var query = QueryOver.Of<UserRelationship>()
.JoinqueryOver(x=>x.MemberUser)
.Where(x=>x.UserName == "TestUser");
Now I want to return List Distinct User, so I cannot do
TransformUsing(Transformers.DistinctRootEntity)
because this will give me the UserRelationship.
I need something like this:
Select distinct user.ID
from UserRelationship relationship
inner join User user on user.ID = relationship.MemberUser_ID
Please help
thanks
Given the classes:
public class User
{
public virtual int Id {get; set;}
public virtual string UserName {get; set;}
}
public class UserRelationship
{
public virtual int Id {get; set;}
public virtual User GroupUser {get; set;}
public virtual User MemberUser {get; set;}
}
And the fluent mappings of:
public class UserMap : ClassMap<User>
{
public UserMap()
{
Id(x=>x.Id).GeneratedBy.Native();
Map(x=>x.UserName);
}
}
public class UserRelationshipMap : ClassMap<UserRelationship>
{
public UserRelationshipMap(){
Id(x=>x.Id).GeneratedBy.Native();
References(x=>x.GroupUser);
References(x=>x.MemberUser);
}
}
You want to retrieve a list of distinct "User" based on "MemberUser" from the UserRelationship class.
var distinctMemberUsers = QueryOver.Of<UserRelationship>()
.Select(x => x.MemberUser.Id);
var users = QueryOver.Of<User>()
.WithSubquery.WhereProperty(x=>x.Id).In(distinctMemberUsers)
This should use a In clause in the SQL to give you a distinct list of User.
I know this post is old but I just came across the same problem and thought I would share an answer I found to be much simpler.
No matter what - NHibernate will have to query multiple rows for each parent object (unless you use a SubSelect instead of a Join). Because of this, we know we're going to get a list of say, 500 objects, when there are really only 100 unique objects.
Since these objects are already queried, and already in memory - why not use LINQ?
Based on this question: LINQ's Distinct() on a particular property the answer with the most +'s gives a very eloquent solution. Create another list, and have LINQ do the distinct comparison. If we could do distinct at the database it would clearly be the better option - but since that's not an option, LINQ seems to be a good solution.

How to map an interface in nhibernate?

I'm using two class NiceCustomer & RoughCustomer which implment the interface ICustomer.
The ICustomer has four properties. They are:
Property Id() As Integer
Property Name() As String
Property IsNiceCustomer() As Boolean
ReadOnly Property AddressFullText() As String
I don't know how to map the interface ICustomer, to the database.
I get an error like this in the inner exception.
An association refers to an unmapped class: ICustomer
I'm using Fluent and NHibernate.
You can map directly to interfaces in NHibernate, by plugging in an EmptyInterceptor during the configuration stage. The job of this interceptor would be to provide implementations to the interfaces you are defining in your mapping files.
public class ProxyInterceptor : EmptyInterceptor
{
public ProxyInterceptor(ITypeHandler typeHandler) {
// TypeHandler is a custom class that defines all Interface/Poco relationships
// Should be written to match your system
}
// Swaps Interfaces for Implementations
public override object Instantiate(string clazz, EntityMode entityMode, object id)
{
var handler = TypeHandler.GetByInterface(clazz);
if (handler == null || !handler.Interface.IsInterface) return base.Instantiate(clazz, entityMode, id);
var poco = handler.Poco;
if (poco == null) return base.Instantiate(clazz, entityMode, id);
// Return Poco for Interface
var instance = FormatterServices.GetUninitializedObject(poco);
SessionFactory.GetClassMetadata(clazz).SetIdentifier(instance, id, entityMode);
return instance;
}
}
After this, all relationships and mappings can be defined as interfaces.
public Parent : IParent {
public int ID { get; set; }
public string Name { get; set; }
public IChild Child { get; set; }
}
public Child : IChild {
public int ID { get; set; }
public string Name { get; set; }
}
public class ParentMap : ClassMap<IParent>
{
public ParentMap()
{
Id(x => x.ID).GeneratedBy.Identity().UnsavedValue(0);
Map(x => x.Name)
}
}
...
This type of technique is great if you want to achieve true decoupling of your ORM, placing all configuration/mappings in a seperate project and only referencing interfaces. Your domain layer is then not being polluted with ORM, and you can then replace it at a later stage if you need to.
how are you querying? If you're using HQL you need to import the interface's namespace with an HBM file with this line:
<import class="name.space.ICustomer, Customers" />
If you're using Criteria you should just be able to query for ICustomer and it'll return both customer types.
If you're mapping a class that has a customer on it either through a HasMany, HasManyToMany or References then you need to use the generic form:
References<NiceCustomer>(f=>f.Customer)
If you want it to cope with either, you'll need to make them subclasses
Subclassmap<NiceCustomer>
In which case I think you'll need the base class Customer and use that for the generic type parameter in the outer class:
References<Customer>(f=>f.Customer)
Regardless, you shouldn't change your domain model to cope with this, it should still have an ICustomer on the outer class.
I'm not sure if the 1.0RTM has the Generic form working for References but a quick scan of the changes should show the change, which I think is a two line addition.
It is not possible to map an interface in nhibernate. If your goal is to be able to query using a common type to retrieve both types of customers you can use a polymorphic query. Simply have both your classes implement the interface and map the classes normally. See this reference:
https://www.hibernate.org/hib_docs/nhibernate/html/queryhql.html (section 11.6)