Set dynamic table name(database table) to one single entity in EntityFrameworkCore - asp.net-core

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} ...");

Related

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"

How do I create an Entity Framework DbSet with data from 2 tables

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.

How to auto-load details (with conditions) associated with an entity using Ria Services?

I'm developing a project using Silverlight 4 and Entity Framework 4 and I'm trying to auto-load the details (with conditions) associated with an entity when the client loads the EntityQuery.
So far, I've been able to put in place a solution, using the Include attribute, that returns all the details associated with the master entity. What I'm missing here is to be able to filter out the details based on some criteria.
As an example, here's what my entities look like:
Entity Movie
Id (int)
[Include]
MovieLocalizedInformations (EntityCollection<MovieLocalizedInformation>)
Entity MovieLocalizedInformation
Id (int)
Movie_Id (int)
LanguageCode (eg.: en)
Title
On my DomainService object, I expose the following method:
public IQueryable<Movie> GetMovies( string languageCode )
{
return this.ObjectContext.Movies.Include( "MovieLocalizedInformations" );
}
This works fine. But when I try to add where clause to filter out the localized information based on the language code, only the movies get loaded on the client.
Is there a way to achieve the filtering in one query?
Note: I'm also using the DomainDataSource with paging on the client so the solution needs to work with that.
Any help would be greatly appreciated!
Thanks,
Jacques.
Not sure about Enitity Framework but with a LinqToSqlDomainService you use the LoadWith loadOption
to include the details entities and then use the AssociateWith LoadOption to filter the detail e.g
DataLoadOptions options = new DataLoadOptions();
options.LoadWith<Movies>(i => i.MovieLocalizedInformations);
options.AssociateWith<Movies>(i => i.MovieLocalizedInformations.Where(d=> myListOfIds.Contains(d.LocationId)));
Ok,
For efficiency reason, I decided to go with custom DTO object that fetches the localized information and flatten the result.
But, the same problem occurred when my custom DTO needed to reference another custom localized DTO.
Here is how I came to do the same as the .Include( "PropertyName" ) that the ObjectSet offers:
Entity LocalizedMovieCollection
public class LocalizedMovieCollection
{
[Key]
public int Id { get; set; }
public string Name { get; set; } (the result of a sub query based on the language)
[Include]
[Association( "LocalizedMovieCollection_LocalizedMovies", "Id", "MovieCollection_Id" )]
public IEnumerable<LocalizedMovie> Movies { get; set; }
}
Entity LocalizedMovie
public class LocalizedMovie
{
[Key]
public int Id { get; set; }
public string Name { get; set; } (the result of a sub query based on the language)
public int MovieCollection_Id { get; set; }
[Include]
[Association( "LocalizedMovie_LocalizedMovieCollection", "MovieCollection_Id", "Id", IsForeignKey = true]
public LocalizedMovieCollection MovieCollection { get; set; }
}
Then, I've declared two methods: One that returns an IQueryable of LocalizedMovieCollection and the other, an IQueryable of LocalizedMovie. (Note: There must be at least one method that returns each type of entity for the entity to get auto-generated on the Silverlight client)
My goal is to automatically load the MovieCollection associated with a Movie so the method definition to get the movies is as follow:
public IQueryable<LocalizedMovie> GetMovies( string languageCode )
{
return from movie in this.ObjectContext.Movies
join movieLocalizedInfo in this.ObjectContext.MovieLocalizedInformations
on movie equals movieLocalizedInfo.Movie
join movieCollection in this.ObjectContext.MovieCollections
on movie.MovieCollection equals movieCollection
join movieCollectionLocalizedInfo in this.ObjectContext.MovieCollectionLocalizedInformations
on movieCollection equals movieCollectionLocalizedInfo.MovieCollection
where movieLocalizedInfo.LanguageCode == languageCode && movieCollectionLocalizedInfo.LanguageCode == languageCode
select new LocalizedMovie()
{
Id = movie.Id,
Name = movieLocalizedInfo.Name
MovieCollection_Id = movieCollection.Id,
MovieCollection = new LocalizedMovieCollection(){ Id = movieCollection.Id, Name = movieCollectionLocalizedInfo.Name }
}
}
When the Silverlight client loads the query, all the LocalizedMovies and their associated LocalizedMovieCollections will be loaded into the context.

NHibernate Projection Components

Hello guys im trying to hydrate a DTO using projections in NHibernate this is my code
IList<PatientListViewModel> list =
y.CreateCriteria<Patient>()
.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("Birthdate"), "Birthdate")
.Add(Projections.Property("Doctor.Id"), "DoctorId")
.Add(Projections.Property("Gender"), "Gender")
.Add(Projections.Property("Id"), "PatientId")
.Add(Projections.Property("Patient.Name.Fullname"), "Fullname")
)
.SetResultTransformer(Transformers.AliasToBean<PatientListViewModel>())
.List<PatientListViewModel>();
this code is throwing an exception? anyone know what is the problem?
here is the error message
Message: could not resolve property: Patient.Name.Fullname of: OneCare.Domain.Entities.Patient
You have to create a join to your Parent.Name property.
So try before setting the projections to create in alias to your Patient.Name property
e.q.
IList<PatientListViewModel> list =
y.CreateCriteria<Patient>()
.CreateAlias("Name", "name")
.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("Birthdate"), "Birthdate")
.Add(Projections.Property("Doctor.Id"), "DoctorId")
.Add(Projections.Property("Gender"), "Gender")
.Add(Projections.Property("Id"), "PatientId")
.Add(Projections.Property("name.Fullname"), "Fullname")
)
Sorry I did not check this, as all depend on your entities classes. But the idea is that you have to create an alias.
If you can not fix the issue, please provide the your classes.
Updated!
I've created two entities, Patient and Doctor:
public class Patient : AdvanceEntity
{
public virtual DateTime BirthDate { get; set; }
public virtual Doctor Doctor { get; set; }
public virtual int Gender { get; set; }
public virtual string Name { get; set; }
}
public class Doctor : AdvanceEntity
{
public virtual string Name { get; set; }
}
Next the repository contains only yours query translated to Criteria API
public IList<Patient> GetPatientsForDoctor(long doctorId)
{
return this.Session.CreateCriteria(typeof(Patient), "patient")
.CreateAlias("patient.Doctor", "doc")
.Add(Restrictions.Eq("doc.Id", doctorId))
.List<Patient>()
;
}
And here is the unittest and the result of the query
[Test]
public void CanGetPatients()
{
var repository = new PatientRepository();
repository.GetPatientsForDoctor(1L);
}
and result is:
NHibernate: SELECT this_.patientId as patientId70_1_, this_.birthDate as birthDate70_1_, this_.gender as gender70_1_,
this_.name as name70_1_, this_.deletedDate as deletedD5_70_1_, this_.doctorId as doctorId70_1_,
this_.deletedById as deletedB7_70_1_, doc1_.doctorId as doctorId71_0_, doc1_.name as name71_0_,
doc1_.deletedDate as deletedD3_71_0_, doc1_.deletedById as deletedB4_71_0_
FROM Patients this_
inner join Doctors doc1_ on this_.doctorId=doc1_.doctorId
WHERE doc1_.doctorId = #p0;#p0 = 1
As I said you need just to create an Alias and join tables between them.
But I think, that using HQL is more plausible in this case. Use criteria only you have dynamic queries. As you can see, the criteria one select all fields which could create a performance lack. Of couse you are working with simple things, but in real application be very carefully with generated queries.
Have a nice day!

NHibernate add unmapped column in interceptor

I'm trying to save a mapped entity using NHibernate but my insert to the database fails because the underlying table has a column that does not allow nulls and IS NOT mapped in my domain object. The reason it isn't mapped is because the column in question supports a legacy application and has no relevance to my application - so I'd like to not pollute my entity with the legacy property.
I know I could use a private field inside my class - but this still feels nasty to me. I've read that I can use an NHibernate interceptor and override the OnSave() method to add in the new column right before my entity is saved. This is proving difficult since I can't work out how to add an instance of Nhibernate.type.IType to the types parameter of my interceptor's OnSave.
My Entity roughly looks like this:
public class Client
{
public virtual int Id { get; set; }
public virtual int ParentId { get; set; }
public virtual string Name { get; set; }
public virtual string Phone { get; set; }
public virtual string Email { get; set; }
public virtual string Url { get; set; }
}
And my interceptor
public class ClientInterceptor : EmptyInterceptor
{
public override bool OnSave(object entity, object id, object[] state, string[] propertyNames, NHibernate.Type.IType[] types)
{
if (entity is Client)
{
/*
manually add the COM_HOLD column to the Client entity
*/
List<string> pn_list = propertyNames.ToList();
pn_list.Add("COM_HOLD");
propertyNames = pn_list.ToArray();
List<Object> _state = state.ToList();
_state.Add(false);
state = _state.ToArray();
//somehow add an IType to types param ??
}
return base.OnSave(entity, id, state, propertyNames, types);
}
}
Does anyone have any ideas on how to do this properly?
I can't say for sure since I've never actually done this (like Stefan, I also prefer to just add a private property), but can you just add a NHibernate.Type.BooleanType to the types array?
List<IType> typeList = types.ToList();
typeList.Add(new BooleanType());
types = typesList.ToArray();
EDIT
Yes, it looks like you are right; the types have an internal constructor. I did some digging and found TypeFactory:
Applications should use static
methods and constants on
NHibernate.NHibernateUtil if the
default IType is good enough. For example, the TypeFactory should only
be used when the String needs to have a length of 300 instead of 255. At this point
NHibernate.String does not get you thecorrect IType. Instead use TypeFactory.GetString(300) and keep a
local variable that holds a reference to the IType.
So it looks like what you want is NHibernateUtil:
Provides access to the full range of
NHibernate built-in types. IType
instances may be used to bind values
to query parameters. Also a factory
for new Blobs and Clobs.
typeList.Add(NHibernateUtil.Boolean);
Personally I wouldn't do it so complicated. I would add the private property and assign it a default value - finished. You could also consider a default value in the database, then you don't need to do anything else.
private virtual bool COM_HOLD
{
get { return false; }
set { /* make NH happy */ }
}
Before writing a interceptor for that I would consider to write a database trigger. Because with the Interceptor you are "polluting" your data access layer. It could make it unstable and you could have strange problems.